Animation to Remove Sprite - ios

I am currently making a Sprite and I want it to animate before it disappears.
For example: I want it to animate it in the sense that it disappears from the top of the block until the bottom. To put it another way, I want to the size to decrease slowly until there is nothing left. But I want to give it the appearance that it is disappearing rather than scaling to nothing.
let hand = SKSpriteNode(imageNamed: "hand")
hand.size = CGSize(width: size.width/10, height: size.height/30)
hand.position = CGPoint(x: CGFloat(posX-2)*size.width/10+offsetX, y:CGFloat(posY)*size.height/30+offsetY)
addChild(hand)
tl;dr is it possible to make this sort of effect using SpriteKit in Swift.
Ideal Animation: https://ibb.co/sPsffmK

SKAction is an animation that is executed by a node in the scene. Actions are used to change a node in some way (like move its position over time), but you can also use actions to change the scene, like doing a fadeout.
In your case, you can apply multiple animations:
let move_action = SKAction.moveBy(x: -100, y: 0, duration: 1.0)
let scale_action = SKAction.scale(to: 0.0, duration: 1.0)
let fade_action = SKAction.fadeAlpha(to: 0.0, duration: 1.0)
hand.run(move_action)
hand.run(scale_action)
//hand.run(fade_action)
In the previous example, the hand runs the move and scale animation at the same time. But you can also make the hand to move to the position, and after it reaches, to scale it.
let sequence = SKAction.sequence([move_animation,scale_animation])
hand.run(sequence)
There are lots of animations that SKAction has, here you can find the complete list.

Related

Change "field of view" in SceneKit

I have some camera with perspective projection and object in the (0,0,0).
I computed "bounding frame"(size in pixels on screen) of this object according to this camera and scaled it to fit the screen size. And now it in the middle of screen.
Frame of the scnView is equal to the screen size and I have some UI components above this view(e.g. navigation bar and some big transparent view in the bottom of view). The scnView has some fullscreen background and this object can be scaled/moved/... therefore I need scnview to be fullscreen.
Then I move this object in the middle of "clear area" (the area with absence of UIComponents) and see the bottom of this object (as if it's above me), because my projection is perspective.
I want to move this object in the center of clear area and see like it's in front of me without any distortion. How can I achieve that?
I see 2 solutions.
1) Draw my object offscreen in texture and after that draw this texture on screen in required position.
2) create scnView a bit taller. For example set frame size to = (0, (bottomHeight - topHeight) / 2, width, height + (bottomHeight - topHeight) / 2) to move the center of scnView in required position.
But I don't like first solution due to addition draw call and second solution even sounds crappy.
P.S. sorry for that pictures
Update: Basically I want to use perspective projection and move 3D object like a 2D image in SceneKit.
Sorry if I'm not be understanding correctly, but can you strafe camera up and center it, or will that not be clear enough?
func strafeY(vAmount: XFloat)
{
nPosition = cgVecAdd(v1: nPosition, v2: cgVecScalarMult(v: nTarget, s: vAmount))
}
Try this... generate a new scenekit game project, then replace the default rotation with the code below.
Rotate X slightly as you move the object up to to "somewhat" maintain the relationship to the eye.
You could create a similar but separate routine to do smaller increments to smooth it out.
ship.runAction(SCNAction.rotateBy(x: CGFloat(GLKMathDegreesToRadians(-90)), y: 0, z: 0, duration: 0))
let vRotateAmount: Float = 3
let vAction1 = SCNAction.move(to: SCNVector3Make(0, 1, 0), duration: 1)
let vAction1a = SCNAction.rotateBy(x: CGFloat(GLKMathDegreesToRadians(vRotateAmount)), y:0, z:0, duration: 1)
let vAction2 = SCNAction.move(to: SCNVector3Make(0, 2, 0), duration: 1)
let vAction2a = SCNAction.rotateBy(x: CGFloat(GLKMathDegreesToRadians(vRotateAmount)), y:0, z:0, duration: 1)
let vAction3 = SCNAction.move(to: SCNVector3Make(0, 1, 0), duration: 1)
let vAction3a = SCNAction.rotateBy(x: CGFloat(GLKMathDegreesToRadians(-vRotateAmount)), y:0, z:0, duration: 1)
let vAction4 = SCNAction.move(to: SCNVector3Make(0, 0, 0), duration: 1)
let vAction4a = SCNAction.rotateBy(x: CGFloat(GLKMathDegreesToRadians(-vRotateAmount)), y:0, z:0, duration: 1)
let seq = SCNAction.sequence([vAction1, vAction1a, vAction2, vAction2a, vAction3, vAction3a, vAction4, vAction4a])
let allSeq = SCNAction.repeatForever(seq)
ship.runAction(allSeq)
Solved it by changing camera projectionTransform. Smth like:
let newMatrix = SCNMatrix4Mult(initialProjectionCameraMatrix, translationMatrix)

Swift Game Scene alter vertically moving background with time?

I have a moving background which is 1500 x 600 pixels and constantly moves vertically down the screen using this code:
let bgTexture = SKTexture(imageNamed: "bg.png")
let moveBGanimation = SKAction.move(by: CGVector(dx: 0, dy: -bgTexture.size().height), duration: 4)
let shiftBGAnimation = SKAction.move(by: CGVector(dx: 0, dy: bgTexture.size().height), duration: 0)
let moveBGForever = SKAction.repeatForever(SKAction.sequence([moveBGanimation, shiftBGAnimation]))
var i: CGFloat = 0
while i < 3 {
bg = SKSpriteNode(texture: bgTexture)
bg.position = CGPoint(x: self.frame.midX, y: bgTexture.size().height * i)
bg.size.width = self.frame.width
bg.zPosition = -2
bg.run(moveBGForever)
self.addChild(bg)
i += 1
}
I now want a new background to come onto the screen after x amount of time to give the feel the player is moving into a different part of the game.
Could I put this code into a function and trigger it with NSTimer after say 20 seconds but change the start position of the new bg to be off screen?
The trouble with repeatForever actions is you don't know where they are at a certain moment. NSTimers are not as precise as you'd like, so using a timer may miss the right time or jump in too early depending on rendering speeds and frame rate.
I would suggest replacing your moveBGForever with a bgAnimation as a sequence of your move & shift actions. Then, when you run bgAnimation action, you run it with a completion block of { self.cycleComplete = true }. cycleComplete would be a boolean variable that indicates whether the action sequence is done or not. In your scene update method you can check if this variable is true and if it is, you can run the sequence action once again. Don't forget to reset the cycleComplete var to false.
Perhaps it sounds more complex but gives you control of whether you want to run one more cycle or not. If not, then you can change the texture and run the cycle again.
Alternatively you may leave it as it is and only change the texture(s) after making sure the sprite is outside the visible area, e.g. its Y position is > view size height.
In SpriteKit you can use wait actions with completion blocks. This is more straightforward than using a NSTimer.
So, to answer your question - when using actions for moving the sprites on-screen, you should not change the sprite's position at any time - this is what the actions do. You only need to make sure that you update the texture when the position is off-screen. When the time comes, obviously some of the sprites will be displayed, so you can't change the texture of all 3 at the same time. For that you may need a helper variable to check in your update cycle (as I suggested above) and replace the textures when the time is right (sprite Y pos is off-screen).

Swift: How to set horizontal back and forth movements on a sprite?

I'm looking to add an image (shape) back and forth on the screen forever.
I want the image to stop when you tap on the screen.
I found some codes but they are all in objective C and I even tried to make it to swift but couldn't figure it out.
Any help would be appreciated,
If you are using SpriteKit then you can create actions that move the sprite from the right to the left and back forever.
let moveLeft = SKAction.moveTo(CGPoint(x: xPosition, y: yPosition), duration: duration)
let moveRight = SKAction.moveTo(CGPoint(x: xPosition, y: yPosition), duration: duration)
sprite.runAction(SKAction.repeatActionForever(SKAction.sequence([moveLeft, moveRight])))
Then to stop it from moving you can use this code:
sprite.removeAllActions()

CALayers: A) Can I draw directly on them and B) How to make a width adjustment on a superlayer affect a sublayer

So my goal is to make a sort of sliding door animation in response to a swipe gesture. You can see a GIF of my current animation here (ignore the fact that the gesture behaves opposite to what you'd expect).
Here's how I'm currently accomplishing this: I have a subclass of UIView I'm calling DoorView. DoorView has three CALayers: the base superlayer that comes with every UIView; a sublayer called doorLayer which is the white rectangle that slides; and another sublayer called frameLayer which is the "doorframe" (the black border around doorLayer). The doorLayer and the frameLayer have their own separate animations that are triggered in sequence.
Here's what I need to add to DoorView: a simple rectangle that represents a door handle. At the moment I don't plan to give the door handle its own animation. Instead, I want it to simply be "attached" to the doorLayer so that it animates along with any animations applied to doorLayer.
This is where my first question comes in: I know that I can add another layer (let's call it handleLayer) and add it as a sublayer to doorLayer. But is there a way to simply "draw" a small rectangle on doorLayer without needing an extra layer? And if so, is this preferable for any reason?
Now for my second question: so at the moment I am in fact using a separate layer called handleLayer which is added as a sublayer to doorLayer. You can see a GIF of the animation with the handleLayer here.
And here is the animation being applied to doorLayer:
UIView.animateWithDuration(1.0, animations: { () -> Void in
self.doorLayer.frame.origin.x = self.doorLayer.frame.maxX
self.doorLayer.frame.size.width = 0
}
This animation shifts the origin of doorLayer's frame to the door's right border while decrementing its width, resulting in the the appearance of a door sliding to the right and disappearing as it does so.
As you can see in the above GIF, the origin shift of doorLayer is applied to its handleLayer sublayer, as desired. But the width adjustment does not carry over to the handleLayer. And this is good, because I don't want the handle to be getting narrower at the same rate as the doorLayer.
Instead what is desired is that the handleLayer moves with the doorLayer, but retains its size. But when the doorLayer disappears into the right side of the doorframe, the handle disappears with it (as it would look with a normal door). Any clue what the best way to accomplish this is?
Currently in my doorLayer's animation, I added this line:
if self.doorLayer.frame.size.width <= self.handleLayer.frame.size.width {
self.handleLayer.frame.size.width = 0
}
But that results in this, which isn't quite right.
Thanks for any help!
From a high level, you would need to
Make your sliding layer a child of your outline layer
Make your outline layer masks its bounds
Animate your sliding layer's transform using a x translation
On completion of the animation, animate your outline layer's transform using a scale translation
Reverse the animations to close the door again
Your doorknob layer is fine as is and no need to animate it separately.
I took a shot at it for fun and here's what I came up with. I didn't use a swipe gesture, but it could just as easily by added. I trigger the animation with a tap on the view. Tap again to toggle back.
func didTapView(gesture:UITapGestureRecognizer) {
// Create a couple of closures to perform the animations. Each
// closure takes a completion block as a parameter. This will
// be used as the completion block for the Core Animation transaction's
// completion block.
let slideAnimation = {
(completion:(() -> ())?) in
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
CATransaction.setAnimationDuration(1.0)
if CATransform3DIsIdentity(self.slideLayer.transform) {
self.slideLayer.transform = CATransform3DMakeTranslation(220.0, 0.0, 0.0)
} else {
self.slideLayer.transform = CATransform3DIdentity
}
CATransaction.commit()
}
let scaleAnimation = {
(completion:(() -> ())?) in
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
CATransaction.setAnimationDuration(1.0)
if CATransform3DIsIdentity(self.baseLayer.transform) {
self.baseLayer.transform = CATransform3DMakeScale(2.0, 2.0, 2.0)
} else {
self.baseLayer.transform = CATransform3DIdentity
}
CATransaction.commit()
}
// Check to see if the slide layer's transform is the identity transform
// which would mean that the door is currently closed.
if CATransform3DIsIdentity(self.slideLayer.transform) {
// If the door is closed, perform the slide animation first
slideAnimation( {
// And when it completes, perform the scale animation
scaleAnimation(nil) // Pass nil here since we're done animating
} )
} else {
// Otherwise the door is open, so perform the scale (down)
// animation first
scaleAnimation( {
// And when it completes, perform the slide animation
slideAnimation(nil) // Pass nil here since we're done animating
})
}
}
Here's how the layers are setup initially:
func addLayers() {
baseLayer = CALayer()
baseLayer.borderWidth = 10.0
baseLayer.bounds = CGRect(x: 0.0, y: 0.0, width: 220, height: 500.0)
baseLayer.masksToBounds = true
baseLayer.position = self.view.center
slideLayer = CALayer()
slideLayer.bounds = baseLayer.bounds
slideLayer.backgroundColor = UIColor.whiteColor().CGColor
slideLayer.position = CGPoint(x: baseLayer.bounds.size.width / 2.0, y: baseLayer.bounds.size.height / 2.0)
let knobLayer = CALayer()
knobLayer.bounds = CGRect(x: 0.0, y: 0.0, width: 20.0, height: 20.0)
knobLayer.cornerRadius = 10.0 // Corner radius with half the size of the width and height make it round
knobLayer.backgroundColor = UIColor.blueColor().CGColor
knobLayer.position = CGPoint(x: 30.0, y: slideLayer.bounds.size.height / 2.0)
slideLayer.addSublayer(knobLayer)
baseLayer.addSublayer(slideLayer)
self.view.layer.addSublayer(baseLayer)
}
And here's what the animation looks like:
You can see a full Xcode project here: https://github.com/perlmunger/Door

Moving SCNLight with SCNAction

I have a spotlight, created with the code beneath, casting shadows on all of my nodes:
spotLight.type = SCNLightTypeSpot
spotLight.spotInnerAngle = 50.0
spotLight.spotOuterAngle = 150.0
spotLight.castsShadow = true
spotLight.shadowMode = SCNShadowMode.Deferred
spotlightNode.light = spotLight
spotlightNode.eulerAngles = SCNVector3(x: GLKMathDegreesToRadians(-90), y: 0, z: 0)
spotlightNode.position = levelData.coordinatesForGridPosition(column: 0, row: playerGridRow)
spotlightNode.position.y = 1.5
rootNode.addChildNode(spotlightNode)
The scene is moving along the z axis, and the camera has an infinite animation that makes it move:
let moveAction = SCNAction.moveByX(0.0, y: 0.0, z: CGFloat(-GameVariables.segmentSize / 2), duration: 2.0)
cameraContainerNode.runAction(SCNAction.repeatActionForever(moveAction))
As the camera moves though, the light doesn't, so after a while, the whole scene is dark. I want to move the light with the camera, however if I apply to the light node the same moving animation, all the shadows start to flicker. I tried to change the SCNShadowMode to Forward and the light type to Directional, but the flickering is still there. With directional, I actually loose most of my shadows. If I create a new light node later on, it will seem that I have two "suns", which of course is impossible. The final aim is simply to have an infinite light that shines parallel to the scene from the left, casting all the shadows to the right. Any ideas?
Build a node tree to hold both spotlight and camera.
Create, say, cameraRigNode as an SCNNode with no geometry. Create cameraContainerNode and spotlightNode the same way you are now. But make them children of cameraRigNode, not the scene's root node.
Apply moveAction to cameraRigNode. Both the camera and the light will now move together.

Resources