NSView scale animation is not smooth - cocoa

I am trying to scale an NSView that is 56x56. The animation is applying scale of 0.95 and an alpha of 0.75, autoreverse and repeats infinitely. I have the animation working however the animation is extremely chunky (not smooth).
How can I use CAAnimationGroup and CABasicAnimation to animate these properties smoothly?
You can see the chunky animation in this gif
The animation code looks like the following
private func transformWithScale(_ scale: CGFloat) -> CATransform3D {
let bounds = squareView.bounds
let scale = scale != 0 ? scale : CGFloat.leastNonzeroMagnitude
let xPadding = 0.5*bounds.width
let yPadding = 0.5*bounds.height
let translate = CATransform3DMakeTranslation(xPadding, yPadding, 0.0)
return scale == 1.0 ? translate : CATransform3DScale(translate, scale, scale, 1.0)
}
func startAnimation() {
let layer = squareView.layer!
layer.removeAllAnimations()
layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let scaleAnimation = CABasicAnimation(keyPath: "transform")
scaleAnimation.fromValue = transformWithScale(1.0)
scaleAnimation.toValue = transformWithScale(0.95)
let alphaAnimation = CABasicAnimation(keyPath: "opacity")
alphaAnimation.fromValue = 1.0
alphaAnimation.toValue = 0.75
let group = CAAnimationGroup()
group.duration = 0.8
group.autoreverses = true
group.timingFunction = CAMediaTimingFunction(name: .easeIn)
group.repeatCount = .infinity
group.animations = [scaleAnimation, alphaAnimation]
layer.add(group, forKey: "scaleAndAlpha")
}

Try this one:
let container = NSView(frame: NSRect(x: 0, y: 0, width: 300, height: 300))
container.wantsLayer = true
container.layer?.backgroundColor = NSColor.blue.cgColor
let content = NSView(frame: NSRect(x: 0, y: 0, width: 150, height: 150))
content.wantsLayer = true
content.layer?.backgroundColor = NSColor.red.cgColor
container.addSubview(content)
let layer = content.layer!
let scaleAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.transform))
scaleAnimation.fromValue = CATransform3DScale(CATransform3DIdentity, 1, 1, 1)
scaleAnimation.toValue = CATransform3DScale(CATransform3DIdentity, 0.85, 0.85, 1)
let alphaAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
alphaAnimation.fromValue = 1.0
alphaAnimation.toValue = 0.75
let group = CAAnimationGroup()
group.duration = 0.8
group.autoreverses = true
group.timingFunction = CAMediaTimingFunction(name: .easeOut)
group.repeatCount = .infinity
group.animations = [scaleAnimation, alphaAnimation]
let center = CGPoint(x: container.bounds.midX, y: container.bounds.midY)
layer.position = center
layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
layer.add(group, forKey: nil)
PlaygroundPage.current.liveView = container

Related

In SpriteKit how can I have code for tracking the textures of two nodes at a time? As well code to track if all the nodes were matched in time?

I would like to have one piece of code in my app where I can track the textures of two nodes at a time. If the textures of the two nodes don't match I would like them reset back to their original state before the user touched them. On top of that i would like another piece of code for tracking if the particular node's textures were matched under a particular time frame like 90 seconds. Any tips on how I can do something like this for my app? Thanks.
Here is my current code:
import Foundation
import SpriteKit
class EasyScreen: SKScene {
override func didMove(to view: SKView) {
var background = SKSpriteNode(imageNamed: "Easy Screen Background")
let timerText = SKLabelNode(fontNamed: "Arial")
timerText.fontSize = 40
timerText.fontColor = SKColor.white
timerText.position = CGPoint(x: 20, y: 400)
timerText.zPosition = 1
var counter:Int = 90
timerText.run(SKAction.repeatForever(SKAction.sequence([SKAction.run {
counter-=1
timerText.text = " Time: \(counter)"
print("\(counter)")
if counter <= 0{
let newScene = TryAgainScreen(fileNamed: "Try Again Screen")
newScene?.scaleMode = .aspectFill
self.view?.presentScene(newScene)
}
},SKAction.wait(forDuration: 1)])))
background.position = CGPoint(x: 0, y: 0)
background.size.width = self.size.width
background.size.height = self.size.height
background.anchorPoint = CGPoint(x: 0.5,y: 0.5)
let matchCardOne = SKSpriteNode(imageNamed: "Fruit Match Card")
let matchCardTwo = SKSpriteNode(imageNamed: "Fruit Match Card")
let matchCardThree = SKSpriteNode(imageNamed: "Fruit Match Card")
let matchCardFour = SKSpriteNode(imageNamed: "Fruit Match Card")
matchCardOne.name = "FruitMatchCard1"
matchCardTwo.name = "FruitMatchCard2"
matchCardThree.name = "FruitMatchCard3"
matchCardFour.name = "FruitMatchCard4"
matchCardOne.size = CGSize(width: 150, height: 300)
matchCardTwo.size = CGSize(width: 150, height: 300)
matchCardThree.size = CGSize(width: 150, height: 300)
matchCardFour.size = CGSize(width: 150, height: 300)
matchCardOne.zPosition = 1
matchCardTwo.zPosition = 1
matchCardThree.zPosition = 1
matchCardFour.zPosition = 1
matchCardOne.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardTwo.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardThree.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardFour.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardOne.position = CGPoint(x: -125, y: 60)
matchCardTwo.position = CGPoint(x: -125, y: -260)
matchCardThree.position = CGPoint(x: 70, y: 60)
matchCardFour.position = CGPoint(x: 70 , y: -260)
addChild(background)
addChild(matchCardOne)
addChild(matchCardTwo)
addChild(matchCardThree)
addChild(matchCardFour)
addChild(timerText)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view?.isMultipleTouchEnabled = false
let touch = touches.first
let positionInSceneOne = touch!.location(in: self)
let tappedNodes = nodes(at: positionInSceneOne)
for node in tappedNodes{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard1" {
tappedCard.texture = SKTexture(imageNamed: "Apple")
}
}
let touchTwo = touches.first
let positionInSceneTwo = touch!.location(in: self)
let tappedNodesTwo = nodes(at: positionInSceneTwo)
for node in tappedNodesTwo{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard2" {
tappedCard.texture = SKTexture(imageNamed: "Apple")
}
}
let touchThree = touches.first
let positionInSceneThree = touch!.location(in: self)
let tappedNodesThree = nodes(at: positionInSceneThree)
for node in tappedNodesThree{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard3" {
tappedCard.texture = SKTexture(imageNamed: "Grapes")
}
}
let touchFour = touches.first
let positionInSceneFour = touch!.location(in: self)
let tappedNodesFour = nodes(at: positionInSceneFour)
for node in tappedNodesFour{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard4" {
tappedCard.texture = SKTexture(imageNamed: "Grapes")
}
}
}
}
}
}
}
}

Spritekit FPS drop problems

An app I'm building drops the FPS when sprite nodes are created, I have searched posts far and wide and cannot figure out why, if anyone had any ideas I would appreciate it!
The issues occur on the simulator and device.
the code for the creating of the nodes is below.
Thank you.
#objc func createEnemy(){
let randomDistribution = GKRandomDistribution(lowestValue: -350, highestValue: 350)
let sprite = SKSpriteNode(imageNamed: "Virus")
sprite.position = CGPoint(x: 700, y: randomDistribution.nextInt())
sprite.name = "Virus"
sprite.zPosition = 1
sprite.size = CGSize(width: 70, height: 70)
addChild(sprite)
sprite.physicsBody = SKPhysicsBody(texture: sprite.texture!, size: sprite.size)
sprite.physicsBody?.velocity = CGVector(dx: -500, dy: 0)
sprite.physicsBody?.linearDamping = 0
sprite.physicsBody?.contactTestBitMask = 1
sprite.physicsBody?.categoryBitMask = 0
sprite.physicsBody?.affectedByGravity = false
createBonus()
}
func createBonus(){
let randomDistribution = GKRandomDistribution(lowestValue: -350, highestValue: 350)
let sprite = SKSpriteNode(imageNamed: "Vaccine")
sprite.position = CGPoint(x: 700, y: randomDistribution.nextInt())
sprite.name = "Vaccine"
sprite.size = CGSize(width: 70, height: 70)
sprite.zPosition = 1
addChild(sprite)
sprite.physicsBody = SKPhysicsBody(texture: sprite.texture!, size: sprite.size)
sprite.physicsBody?.velocity = CGVector(dx: -500, dy: 0)
sprite.physicsBody?.linearDamping = 0
sprite.physicsBody?.contactTestBitMask = 1
sprite.physicsBody?.categoryBitMask = 0
sprite.physicsBody?.collisionBitMask = 0
sprite.physicsBody?.affectedByGravity = false
}
Did you try to preload the textures?
let image = SKTexture(imageNamed: "nodeImage")
override func didMove(to view: SKView) {
image.preload{
print("image has been preloaded")
}
}

Capturing MTKView texture to UIImageView efficiently

I want to capture the contents of the portion of an MTKView that has most recently updated into an UIImageView. I the following piece of code to accomplish this task:
let cicontext = CIContext(mtlDevice: self.device!) // set up once along with rest of renderPipeline
...
let lastSubStrokeCIImage = CIImage(mtlTexture: lastDrawableDisplayed.texture, options: nil)!.oriented(CGImagePropertyOrientation.downMirrored)
let bboxChunkSubCurvesScaledAndYFlipped = CGRect(...) // get bounding box of region just drawn
let imageCropCG = cicontext.createCGImage(lastSubStrokeCIImage, from: bboxStrokeAccumulatingScaledAndYFlipped)
// Now that we have a CGImage of just the right size, we have to do the following expensive operations before assigning to a UIIView
UIGraphicsBeginImageContextWithOptions(bboxStrokeAccumulating.size, false, 0) // open a bboxKeyframe-sized context
UIGraphicsGetCurrentContext()?.translateBy(x: 0, y: bboxStrokeAccumulating.height)
UIGraphicsGetCurrentContext()?.scaleBy(x: 1.0, y: -1.0)
UIGraphicsGetCurrentContext()?.draw(imageCropCG!, in: CGRect(x: 0, y: 0 , width: bboxStrokeAccumulating.width, height: bboxStrokeAccumulating.height))
// Okay, finally we create a CALayer to be a container for what we've just drawn
let layerStroke = CALayer()
layerStroke.frame = bboxStrokeAccumulating
layerStroke.contents = UIGraphicsGetImageFromCurrentImageContext()?.cgImage
UIGraphicsEndImageContext()
strokeView.layer.sublayers = nil // empty out strokeView
strokeView.layer.addSublayer(layerStroke) // add our hard-earned drawing in its latest state
So, this code works, but is not very efficient and makes the app lag when bboxStrokeAccumulating gets very large. Can anyone suggest more efficient alternatives?
For swift 4.0,
let lastDrawableDisplayed = metalView?.currentDrawable?.texture
if let imageRef = lastDrawableDisplayed?.toImage() {
let uiImage:UIImage = UIImage.init(cgImage: imageRef)
}
extension MTLTexture {
func bytes() -> UnsafeMutableRawPointer {
let width = self.width
let height = self.height
let rowBytes = self.width * 4
let p = malloc(width * height * 4)
self.getBytes(p!, bytesPerRow: rowBytes, from: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0)
return p!
}
func toImage() -> CGImage? {
let p = bytes()
let pColorSpace = CGColorSpaceCreateDeviceRGB()
let rawBitmapInfo = CGImageAlphaInfo.noneSkipFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue
let bitmapInfo:CGBitmapInfo = CGBitmapInfo(rawValue: rawBitmapInfo)
let selftureSize = self.width * self.height * 4
let rowBytes = self.width * 4
let releaseMaskImagePixelData: CGDataProviderReleaseDataCallback = { (info: UnsafeMutableRawPointer?, data: UnsafeRawPointer, size: Int) -> () in
return
}
let provider = CGDataProvider(dataInfo: nil, data: p, size: selftureSize, releaseData: releaseMaskImagePixelData)
let cgImageRef = CGImage(width: self.width, height: self.height, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: rowBytes, space: pColorSpace, bitmapInfo: bitmapInfo, provider: provider!, decode: nil, shouldInterpolate: true, intent: CGColorRenderingIntent.defaultIntent)!
return cgImageRef
}
}
you can set the uiImage to your uiImageView

SpriteKit joint physics changing physics body

I am working with the joint method in spritekit, but once I join two sprites it seems to change the physics bodies. I have two rectangles joined at a corner and when they fold, I would expect the blue rectangle to sit on top of the white triangle, but this is the result. How do I maintain their physics bodies, so they don't overlap?
import SpriteKit
class GameScene: SKScene {
let character = SKSpriteNode(color: SKColor.whiteColor(), size: CGSizeMake(150, 30))
let character2 = SKSpriteNode(color: SKColor.blueColor(), size: CGSizeMake(150, 30))
let character3 = SKSpriteNode(color: SKColor.greenColor(), size: CGSizeMake(50, 300))
let screenSize: CGRect = UIScreen.mainScreen().bounds
override func didMoveToView(view: SKView) {
// let scene = GameScene(size: SKView.frame)
let borderBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
borderBody.friction = 5
self.physicsBody = borderBody
self.physicsWorld.gravity = CGVectorMake(0.0, -1.0)
character.position = CGPointMake(550, 400)
character2.position = CGPointMake(400, 400)
character3.position = CGPointMake(400, 100)
character.physicsBody = SKPhysicsBody(rectangleOfSize: character.size)
character2.physicsBody = SKPhysicsBody(rectangleOfSize: character2.size)
character3.physicsBody = SKPhysicsBody(rectangleOfSize: character3.size)
self.character.physicsBody!.dynamic = true
self.character2.physicsBody!.dynamic = true
self.character3.physicsBody!.dynamic = true
self.addChild(character)
self.addChild(character2)
self.addChild(character3)
var myJoint = SKPhysicsJointPin.jointWithBodyA(character2.physicsBody, bodyB: character.physicsBody, anchor: CGPoint(x: CGRectGetMaxX(self.character2.frame), y: CGRectGetMinY(self.character.frame)))
self.physicsWorld.addJoint(myJoint)
var myJoint2 = SKPhysicsJointPin.jointWithBodyA(character2.physicsBody, bodyB: character.physicsBody, anchor: CGPoint(x: CGRectGetMaxX(self.character2.frame), y: CGRectGetMaxY(self.character.frame)))
//self.physicsWorld.addJoint(myJoint2)
}

Scrolling background with 3 images

My let´s:
let background = SKSpriteNode(imageNamed: "background1")
let background2 = SKSpriteNode(imageNamed: "background2")
let background3 = SKSpriteNode(imageNamed: "background3")
This is how i make the background scroll:
func makeBackground(){
var backgroundTexture = SKTexture(imageNamed: "background1")
var moveBackgroundByX = SKAction.moveByX(-backgroundTexture.size().width, y: 0, duration: 10)
var replaceBackground = SKAction.moveByX(backgroundTexture.size().width, y: 0, duration: 0)
var moveBackgroundForever = SKAction.repeatActionForever(SKAction.sequence([moveBackgroundByX, replaceBackground]))
for var i:CGFloat = 0; i < 3; i++ {
let background = SKSpriteNode(texture: backgroundTexture)
background.position = CGPoint(x: backgroundTexture.size().width/2 + (backgroundTexture.size().width * i), y: CGRectGetMidY(self.frame))
background.size.height = self.frame.height
background.runAction(moveBackgroundForever)
self.addChild(background)
}
}
Like this, it loops only background1, but i want it to scroll in order: "background1", "background2", "background3", and then "background1" again, and like that.

Resources