How to animate the whole screen when colision? - swift

I want to make the screen shake when an enemy colide with the player. I have been searching for a posible answer but I don find anything. If someone can help me, thanks.

try this
func shakeFrame(scene: SKScene) {
let animation: CABasicAnimation = CABasicAnimation(keyPath: "position")
animation.duration = 0.05
animation.repeatCount = 4
animation.autoreverses = true
animation.fromValue = NSValue(CGPoint: CGPointMake(scene.view!.center.x - 4.0, scene.view!.center.y))
animation.toValue = NSValue(CGPoint: CGPointMake(scene.view!.center.x + 4.0, scene.view!.center.y))
scene.view!.layer.addAnimation(animation, forKey: "position")
}
in your case try this
func shakeFrame(scene: SKScene) {
let animation: CABasicAnimation = CABasicAnimation(keyPath: "position")
animation.duration = 0.05
animation.repeatCount = 4
animation.autoreverses = true
animation.fromValue = NSValue(CGPoint: CGPointMake(scene.view!.center.x - 4.0, scene.view!.center.y))
animation.toValue = NSValue(CGPoint: CGPointMake(scene.view!.center.x + 4.0, scene.view!.center.y))
scene.view!.layer.addAnimation(animation, forKey: "position")
}
if hits < 2 && circuloPrincipal.color != enemigo.color {
shakeFrame()
}

Related

Why I can't animate the SCNnode on eulerAngle using CAKeyframeAnimation

I'm try to animate an SCNnode using CAKeyframeAnimation, I been able to change the position of my object without problem but I can't animate the change of EulerAngle property.
The following code change the position of the node.
func animatePlaneKey(nodeToAnimate: SCNNode){
let pos = nodeToAnimate.position
let animation = CAKeyframeAnimation(keyPath: "position")
let pos1 = SCNVector3(pos.x, pos.y, pos.z)
let pos2 = SCNVector3(pos.x + 1 , pos.y, pos.z)
let pos3 = SCNVector3(pos.x + 1 , pos.y, pos.z + 1)
animation.values = [pos1,pos2, pos3]
animation.keyTimes = [0,0.5,1]
animation.calculationMode = .linear
animation.duration = 10
animation.repeatCount = 1
animation.isAdditive = true
nodeToAnimate.addAnimation(animation, forKey: "position")
}
and the following one should change the eulerAngles property but does't work.
any idea why I can't animate the rotation?
func animatePlaneKey(nodeToAnimate: SCNNode){
let animation2 = CAKeyframeAnimation(keyPath: "rotation")
let angles = nodeToAnimate.eulerAngles
let rot0 = SCNVector3(angles.x, angles.y, angles.z)
let rot1 = SCNVector3(angles.x, angles.y + Float(deg2rad(45)) , angles.z)
animation2.values = [rot0, rot1]
animation2.keyTimes = [0, 1]
animation2.duration = 2
animation2.repeatCount = .infinity
animation2.isAdditive = true
nodeToAnimate.addAnimation(animation2, forKey: "rotation")
}
Thanks for the help.
You have to use SCNVector4 type (a.k.a. Quaternion) instead of SCNVector3 type (a.k.a. Euler).
And a key "spin around" instead of "rotation".
let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!
let animation = CABasicAnimation(keyPath: "rotation")
let start = SCNVector4(0, 0, 0, 0)
let end = SCNVector4(0, 1, 0, CGFloat(Float.pi * 6))
animation.fromValue = start
animation.toValue = end
animation.duration = 10
animation.repeatCount = .infinity
ship.addAnimation(animation, forKey: "spin around")

Animate roundedrect with CoreAnimation

Guys I have the following code to produce a bezierpath
firstLine.path = UIBezierPath(roundedRect: CGRect(x: frameSize.width / 2.2, y: frameSize.height / 2.6, width: 4, height: 4), cornerRadius: 10).cgPath
I try to animate the firstLine by moving it to the right of the screen by x value and back to the original position. But I don't know what to use for the keyPath in the animation.
let animation = CABasicAnimation(keyPath: "??")
animation.toValue = // this should be the position to move?
animation.duration = 0.5 // duration of the animation
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) // animation curve is Ease Out
animation.fillMode = kCAFillModeBoth // keep to value after finishing
animation.isRemovedOnCompletion = false // don't remove after finishing
animation.autoreverses = true
animation.repeatCount = HUGE
Can someone help me out on this :)
From: https://developer.apple.com/reference/quartzcore/cabasicanimation
You create an instance of CABasicAnimation using the inherited init(keyPath:) method, specifying the key path of the property to be animated in the render tree.
Example of what you want:
let animation = CABasicAnimation(keyPath: "position")
animation.fromValue = [startX, startY]
animation.toValue = [destinationX, destinationY]

CAShapelayer Duplicates

I'm trying to add a CAShapelayer once every 20ms to given x and y coordinates. I would like the shape to fade away over a second (like a tracer). The function I have created works, the shape is created in the correct location and fades away. But I am getting extra shapes left behind cluttering up the screen.
func shadowBall (x: CGFloat, y: CGFloat){
let xpos : CGFloat = ((self.frame.width/2) + x)
let ypos : CGFloat = ((self.frame.height/2) + y)
let shadowBall = CAShapeLayer()
let shadowBalllRadius :CGFloat = 4
let shadowBallPath : UIBezierPath = UIBezierPath(ovalInRect: CGRect(x: xpos, y: ypos, width: CGFloat(shadowBalllRadius*2), height: CGFloat(shadowBalllRadius*2)))
shadowBall.path = shadowBallPath.CGPath
shadowBall.fillColor = UIColor.clearColor().CGColor
shadowBall.strokeColor = UIColor.whiteColor().CGColor
shadowBall.lineWidth = 0.5
let animation: CABasicAnimation = CABasicAnimation(keyPath: "strokeColor")
animation.fromValue = UIColor.whiteColor().CGColor
animation.toValue = UIColor.clearColor().CGColor
animation.duration = 1.0;
animation.repeatCount = 0;
animation.removedOnCompletion = true
animation.additive = false
self.layer.addSublayer(shadowBall)
shadowBall.addAnimation(animation, forKey: "strokeColor")
}
The problem is that when the animation finishes, it restores the strokeColor to the original color. You should really set the strokeColor of the original shape layer to be clearColor(), that way, when you finish animating from whiteColor() to clearColor(), it will remain at clearColor().
You can also set the layer's fillMode to kCAFillModeForwards and set removedOnCompletion to false and that will have the layer preserve its "end of animation" state. But I personally would just set the strokeColor as outlined above, as using removedOnCompletion of true interferes with animationDidStop (see below).
Also, I might suggest that you also remove the layer once it's done with the animation so it doesn't continue to consume memory although it's no longer visible.
func shadowBall (x: CGFloat, y: CGFloat) {
let xpos: CGFloat = ((self.frame.width/2) + x)
let ypos: CGFloat = ((self.frame.height/2) + y)
let shadowBall = CAShapeLayer()
let shadowBalllRadius: CGFloat = 4
let shadowBallPath = UIBezierPath(ovalInRect: CGRect(x: xpos, y: ypos, width: CGFloat(shadowBalllRadius*2), height: CGFloat(shadowBalllRadius*2)))
shadowBall.path = shadowBallPath.CGPath
shadowBall.fillColor = UIColor.clearColor().CGColor
shadowBall.strokeColor = UIColor.clearColor().CGColor
shadowBall.lineWidth = 0.1
let animation = CABasicAnimation(keyPath: "strokeColor")
animation.fromValue = UIColor.whiteColor().CGColor
animation.toValue = UIColor.clearColor().CGColor
animation.duration = 1.0
animation.repeatCount = 0
animation.removedOnCompletion = true
animation.additive = false
animation.delegate = self
animation.setValue(shadowBall, forKey: "animationLayer")
self.layer.addSublayer(shadowBall)
shadowBall.addAnimation(animation, forKey: "strokeColor")
}
override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
if let layer = anim.valueForKey("animationLayer") as? CALayer {
layer.removeFromSuperlayer()
}
}
See How to remove a CALayer-object from animationDidStop?

How to animate borderColor change in swift

For some reason this isn't working for me:
let color = CABasicAnimation(keyPath: "borderColor")
color.fromValue = sender.layer.borderColor;
color.toValue = UIColor.redColor().CGColor;
color.duration = 2;
color.repeatCount = 1;
sender.layer.addAnimation(color, forKey: "color and width");
I'm not getting any animation to occur.
Swift 4 UIView extension:
extension UIView {
func animateBorderColor(toColor: UIColor, duration: Double) {
let animation = CABasicAnimation(keyPath: "borderColor")
animation.fromValue = layer.borderColor
animation.toValue = toColor.cgColor
animation.duration = duration
layer.add(animation, forKey: "borderColor")
layer.borderColor = toColor.cgColor
}
}
And then just use it by writing:
myView.animateBorderColor(toColor: .red, duration: 0.5)
You have to use the same key name. You also forgot to add a border width and color to your layer before animating it. Try like this:
let color = CABasicAnimation(keyPath: "borderColor")
#IBAction func animateBorder(sender: AnyObject) {
color.fromValue = UIColor.greenColor().CGColor
color.toValue = UIColor.redColor().CGColor
color.duration = 2
color.repeatCount = 1
sender.layer.borderWidth = 2
sender.layer.borderColor = UIColor.greenColor().CGColor
sender.layer.addAnimation(color, forKey: "borderColor")
}
(Swift 5, Xcode 11, iOS 13)
For anyone who is wanting to change border color and width at the same time, the following code is working for me & the animation looks very smooth. Maybe someone else knows of a way to combine both into one?
let borderColorAnimation: CABasicAnimation = CABasicAnimation(keyPath: "borderColor")
borderColorAnimation.fromValue = layer.borderColor
borderColorAnimation.toValue = toColor.cgColor
borderColorAnimation.duration = animationDuration
layer.add(borderColorAnimation, forKey: "borderColor")
layer.borderColor = toColor.cgColor
let borderWidthAnimation: CABasicAnimation = CABasicAnimation(keyPath: "borderWidth")
borderWidthAnimation.fromValue = layer.borderWidth
borderWidthAnimation.toValue = toWidth
borderWidthAnimation.duration = animationDuration
layer.add(borderWidthAnimation, forKey: "borderWidth")
layer.borderWidth = toWidth
I created a Swift 4 function extension to CALayer for this, to which you can pass the starting and ending UIColors as well as the duration for the animation:
extension CALayer {
func animateBorderColor(from startColor: UIColor, to endColor: UIColor, withDuration duration: Double) {
let colorAnimation = CABasicAnimation(keyPath: "borderColor")
colorAnimation.fromValue = startColor.cgColor
colorAnimation.toValue = endColor.cgColor
colorAnimation.duration = duration
self.borderColor = endColor.cgColor
self.add(colorAnimation, forKey: "borderColor")
}
Note that for this to work, you should have already set a borderWidth and then call animateBorderColor:
yourView.layer.borderWidth = 1.5
yourView.layer.animateBorderColor(from: UIColor.green, to: UIColor.red, withDuration: 2.0)
I don't know why, but for some reason calling:
color.fromValue = sender.layer.borderColor
doesn't work. The color isn't being read correctly or something. I changed it to:
let color = CABasicAnimation(keyPath: "borderColor");
color.fromValue = UIColor.greenColor().CGColor;
color.toValue = UIColor.redColor().CGColor;
color.duration = 2;
color.repeatCount = 1;
sender.layer.addAnimation(color, forKey: "color and width");
And then things started working as expected.

CAKeyframeAnimation delay before repeating

I have a CAKeyframeAnimation animation that I would like to repeat forever using repeatCount = HUGE_VALF. The animation's duration is 2 seconds, but I would like to have a 3 seconds pause before each cycle.
The only 2 ways I can think of doing that are:
Make the whole animation last 5 seconds and add extra keyTimes and values so that I get the pause I'm looking for during the last 3s of the 5s animation. This feels kinda hacky.
Have the animation only repeat once and then add use something like performSelector:afterDelay:2 to run the animation again, and so on and so on. This feels dirty as well. Also would mean that I need to call addAnimation: every 5 seconds, which I'm not sure is optimal in terms of performance.
Is there another option I might be missing? Is one of those 2 methods better than the other?
By dumping the animations of Apple's MKUserLocationView, I was able to see how they were doing it. Turns out that this is what CAAnimationGroup is for. By encapsulating a 2 seconds animation into a 5 seconds animation group, you'll end up with a 2 seconds animation followed by a 3 seconds delay:
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.duration = 5;
animationGroup.repeatCount = INFINITY;
CAMediaTimingFunction *easeOut = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale.xy"];
pulseAnimation.fromValue = #0.0;
pulseAnimation.toValue = #1.0;
pulseAnimation.duration = 2;
pulseAnimation.timingFunction = easeOut;
animationGroup.animations = #[pulseAnimation];
[ringImageView.layer addAnimation:animationGroup forKey:#"pulse"];
samvermette's answer in Swift 3:
let animationGroup = CAAnimationGroup()
animationGroup.duration = 5;
animationGroup.repeatCount = .infinity
let easeOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
let pulseAnimation = CABasicAnimation(keyPath: "transform.scale.xy")
pulseAnimation.fromValue = 0
pulseAnimation.toValue = 1.0
pulseAnimation.duration = 2
pulseAnimation.timingFunction = easeOut
animationGroup.animations = [pulseAnimation]
ringImageView.layer.add(animationGroup, forKey: "pulse")
Tested on Swift 4.2
Just implement the following extension and then you can get a nice pulse Animation on Any UI component
//MARK:- Pulse animation
extension UIView {
func applyPulse(_ apply: Bool = true) {
if apply {
let animation = CABasicAnimation(keyPath: "transform.scale")
animation.toValue = 1.5
animation.duration = 0.8
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
animation.autoreverses = true
animation.repeatCount = Float.infinity
self.layer.add(animation, forKey: "pulsing")
} else {
self.layer.removeAnimation(forKey: "pulsing")
}
}
}
Usage:
To start
yourUiComponent.applyPulse()
To Stop
yourUiComponent.applyPulse(false)