How do I make my node rotate at a slower duration? - ios

I have this node and when I press and hold a button I want the node to rotate slower. I changed the duration parameter from 1 to 50 and it still rotates the node the same speed. What am I doing wrong?
let rotateRate = (SKAction.rotateByAngle(CGFloat(-M_PI_2), duration: 50.0))
let repeatRotate = SKAction.repeatActionForever(rotateRate)
heroNode.runAction(repeatRotate)

Once you create an action, you can't modify its duration parameter... So you can't affect on speed of an action in the way you are expecting. But you have a few options:
to re-create the action (you probably want to run an action with key for this)
to change the speed of that action:
if let action = node.actionForKey("aKey"){
action.speed = 1.5
}
Probably some more, but this will give an idea what is going on.

I would recommend Whirlwind's option first, this should be used 99% of the time, but in a case where changing the speed is not an option, just apply another action of rotateBy in the opposite direction at a smaller interval.
let rotateRate = (SKAction.rotateByAngle(CGFloat(-M_PI_2), duration: 50.0))
let repeatRotate = SKAction.repeatActionForever(rotateRate)
heroNode.runAction(repeatRotate)
...
func slowDown()
{
let rotateRate = (SKAction.rotateByAngle(CGFloat(M_PI_4), duration: 50.0))
let repeatRotate = SKAction.repeatActionForever(rotateRate)
heroNode.runAction(repeatRotate, forKey:"slowdown")
}
func removeSlowDown()
{
heroNode.removeActionForKey("slowdown")
}

Related

How to add a pause/wait in the middle of a nested SCNTransaction chain

This question must have a very simple answer, but I couldn't find it after searching for hours. Unless I just need to manually add some timer to wait.
Basically, I have an animation sequence to move a node to a certain place, here I want to wait 5 seconds and then I have another animation sequence to bring the node back to its original position.
Initially, I used SCNActions, grouping some actions and then sequencing them all. Here I just added an SCNAction.wait(duration: 5) and that did the trick.
However, one of the actions is to move the node 90 degrees around the X-axis and I need to simultaneously rotate around another axis as well. The result is incorrect and I have a feeling I've run into the gimbal lock issue.
So instead of using SCNAction.rotate(by: ) I decided to rotate using quaternions which don't have the gimbal lock problem, but then I needed to switch to using an SCNTransaction.
I'm nesting these transaction in the completionBlock of the previous SCNTransaction.
For the pause between the 2 animations, I don't have a wait action here, but I thought that just adding a transaction with a duration that does nothing will at least wait for as long. But it doesn't. The transaction immediately skips to the 3rd step.
So is there a simple command to tell the animation transaction to wait?
I did try to add DispatchQueue.main.asyncAfter(deadline: .now() + 5) { <3rd SCNTransaction here> } and that worked, but is that really the way to do it? I have a feeling there's something else that I just don't know that replaces SCNAction.wait().
Here's my current code which does the animations correctly, except it doesn't wait where I want it to (this is an excerpt of the relevant code only):
[...]
let moveSideQuat = simd_quatf(angle: -currentYRotation, axis: simd_float3(0, 0, 1))
let moveAwaySimd = simd_float3(-20 * sinf(currentYRotation), 0, -20 * cosf(currentYRotation))
let moveUpQuat = simd_quatf(angle: .pi/2, axis: simd_float3(1, 0, 0))
let moveDownQuat = simd_quatf(angle: -.pi/2, axis: simd_float3(1, 0, 0))
let moveTowardsQuat = simd_float3(0, 0, pivotZLocation)
let moveBackSideQuat = simd_quatf(angle: currentYRotation, axis: simd_float3(0, 0, 1))
SCNTransaction.begin()
SCNTransaction.animationDuration = 1
baseNode?.simdOrientation = moveSideQuat * baseNode!.simdOrientation
baseNode?.simdPosition = moveAwaySimd
baseNode?.simdOrientation = moveUpQuat * baseNode!.simdOrientation
SCNTransaction.completionBlock = {
SCNTransaction.begin()
SCNTransaction.animationDuration = 5 // <<<--- I WANT TO WAIT HERE
SCNTransaction.completionBlock = {
SCNTransaction.begin()
SCNTransaction.animationDuration = 1
self.baseNode?.simdOrientation = moveDownQuat * self.baseNode!.simdOrientation
self.baseNode?.simdPosition = moveTowardsQuat
self.baseNode?.simdOrientation = moveBackSideQuat * self.baseNode!.simdOrientation
SCNTransaction.commit()
}
SCNTransaction.commit()
}
SCNTransaction.commit()
[...]
In the 2nd completion block, after the animationDuration = 5 I even tried to add a simple assignment for the node's simdPosition, basically leaving it in the same position in the hope to trick the sequence to take 5 seconds to basically do nothing, but it still skipped to the next completionBlock.
So is the asyncAfter actually the way to go or am I missing something else?
Thanks!
Using asyncAfter to wait is fine. That is the purpose of this method, after all.
Creating an empty transaction with the duration of 5 seconds doesn't work because SceneKit can easily see that you haven't changed anything, and knows that the animation can complete immediately. You can hide a node somewhere in the scene and animate that node for 5 seconds, but that feels way more like a hack.
If you want, you can still use SCNActions for the rest of your animations, and just use SCNTransaction for the ones that need the quaternions. For example:
let step1 = SCNAction.group([
SCNAction.run {
SCNTransaction.begin()
SCNTransaction.animationDuration = 1
// do rotations here with quaternions...
SCNTransaction.commit()
},
SCNAction.move(to: /* destination */, duration: 1)
])
let step2 = SCNAction.wait(duration: 5)
let step3 = SCNAction.group([
SCNAction.run {
SCNTransaction.begin()
SCNTransaction.animationDuration = 1
// rotate back...
SCNTransaction.commit()
},
SCNAction.move(to: /* origin */, duration: 1)
])
let entireAction = SCNAction.sequence([
step1, step2, step3
])

Does adding new action for the scene remove automatically the one before in SpriteKit?

I want to know if I made a sequence action and while this sequence is running a new action added to the scene , Does the new action stop the sequence ?
If yes , how i can make both of them working if the new action is added in swift ?
If you just give it a try, I'm sure you'll find the answer yourself.
But anyway, I tried it for you:
node.run(SKAction.sequence([SKAction.moveBy(x: 100, y: 0, duration: 3), SKAction.moveBy(x: 0, y: 100, duration: 3)]))
node.run(SKAction.rotate(byAngle: CGFloat(M_PI * 2), duration: 6))
And what I see is that the node both moves and rotates. So each subsequent action you tell a node to run will be run simultenuously.
Another way to run actions at the same time is to use SKAction.group.
The only time a new action will interfere with any running actions is if both actions share the same key. If you do not assign a key, then every time you add an action, it gets added to the action pool and will run concurrently.
I've follow your comment, seems you are in the situation of overlap of the actions.
When you have a node and you want to launch one or more action, especially a sequence of actions where your node are involved in movements, you should be sure that these actions are finished.
To do it, for example to self:
let seq = SKAction.sequence([action1,action2,..])
if self.action(forKey: "moveToRoof") == nil {
self.run(seq, withKey:"moveToRoof")
}
You can also do:
let group1 = SKAction.group([action1, action2,..])
let group2 = SKAction.group([action1, action2,..])
let addNewNode = SKAction.run{
self.addChild(node)
}
let seq = SKAction.sequence([action1, group1, action2, addNewNode, group2,..])
if self.action(forKey: "moveToGround") == nil {
self.run(seq, withKey:"moveToGround")
}
In your case seems you want to add nodes to a node that following the position of his parent..
override func update(_ currentTime: TimeInterval) {
if let child = myNode1, let parent = child.parent { // if exist follow the parent position
child.position = parent.position
}
}

How do I use SpriteKit's SKAction to create a time interval for an SKSpriteNode?

I have a function, basketball. I want to be able to spawn my basketBall node at CGPoint(515,700), the top of the screen, in a regular time interval. The only method I knew that would accomplish waiting a few seconds was sleep(); however, sleep() apparently doesn't allow SpriteNodes to remain on screen, so I need an alternative.
I discovered NSTimeInterval, but I would prefer to refrain from importing Foundation. I think that SKAction allows time to pass through waitForDuration(), but I am very confused as to how that works. Can someone shed some light on SpriteKit's SKAction?
override func didMoveToView(view: SKView) {
let basketBall = SKSpriteNode(imageNamed: "basketball")
let waitForObjects = SKAction.waitForDuration(3)
let basketBallFalls = SKAction.runBlock({
self.Basketball()
})
let action = SKAction.sequence([waitForObjects, basketBallFalls])
SKAction.runAction(action, onChildWithName: "basketball")
SKAction.repeatActionForever(action)
//Basketball()
}
func Basketball(){
basketBall.position = CGPointMake(515, 700)
basketBall.size = CGSizeMake(50, 50)
basketBall.size = CGSize(width: 50.0, height: 50.0)
basketBall.zPosition = 10
basketBall.physicsBody = SKPhysicsBody(circleOfRadius: 25.0)
basketBall.physicsBody?.mass = 0.8
basketBall.physicsBody?.restitution = 0.6
basketBall.physicsBody?.dynamic = true
self.addChild(basketBall)
}
Try to use SKAction.waitForDuration(_:).
Ex. something like this:
let waitDuration = NSTimeInterval(arc4random_uniform(20))
let waitAction = SKAction.waitForDuration(waitDuration)
let ballAction = SKAction.runBlock(self.Basketball) //call your function
runAction(SKAction.sequence([waitAction, ballAction]))
There are two straightforward ways to spawn a new node at a regular interval. They both involve your scene.
The scene's update(_:) method is called every frame, and the argument is the current time. Add an NSTimeInterval property to your scene class which will store the last time you created a basketball. In update(_:), subtract the current time from the last spawn time. If the difference is greater than the interval that you want, spawn a new node and keep the new current time.
Your scene can also run actions, like any other node. SKAction has a waitForDuration(_:) method which creates an action that does, well, exactly what its name says. You can create another action using SKAction.runBlock(_:) which will perform the spawning. Then create a sequence action that runs the delay action followed by the spawn action. Wrap that in a repeatActionForever(_:), and finally tell your scene to run the repeater action.

How can I stop a SKAction.repeatActionForever action at a action boundary?

I have a sequence of SpriteKit actions that I create and then repeat forever on a node, but that I want to stop eventually. My sequence rotates a disk left, right, and left returning to the original rotation before starting again. However, when I remove the action, it stops without completing and so the original rotation is not restored.
I could save the original rotation state and restore it, but I want to know is there is a way to tell SpriteKit to only interrupt the action at the sequence boundary?
func wiggle() -> SKAction {
let wiggleLeft = SKAction.rotateByAngle(+0.04, duration: 0.1)
let wiggleRight = SKAction.rotateByAngle(-0.08, duration: 0.2)
let wiggleBack = SKAction.rotateByAngle(+0.04, duration: 0.1)
let wiggle = SKAction.sequence([wiggleLeft, wiggleRight, wiggleBack])
let wiggleForever = SKAction.repeatActionForever(wiggle)
return wiggleForever
}
disk.runAction(wiggle(), withKey: "wiggle")
...
disk.removeActionForKey("wiggle") // unfortunately stops mid-wiggle
Add the following code after disk.removeActionForKey("wiggle"):
disk.runAction.rotateToAngle(/*desired final angle of rotation*/)

Animate object size within GameScenes.swift (Swift)

We need to animate an object's size within GameScene.swift. Other Stack Overflow posts suggest using UIView.animateWithDuration, but this isn't available inside GameScene.swift. We need to animate inside GameScene.swift because we also need access to SKAction to run an action forever.
Right now, we are using the code below, but it is too clunky. The hope is animation will smooth out the appearance of the object as it shrinks.
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(shrinkItem),
SKAction.waitForDuration(0.5)
])
))
func shrinkItem() {
let curWidth = item.size.width
if curWidth < 15 {
return
}
item.size = CGSize( width: CGFloat(item.size.width - 20 ), height: CGFloat(bird.size.height - 20) )
}
What you are trying to do is to shrink your bird, right ? That correspond to scale it down.
Why are you doing it manually inside a runBlock ?
You might want to give a look at the SKAction Class Reference : http://goo.gl/ycPYcF.
Inside which you'll see all the possible actions, and scaleBy:duration: (or another one) might be what you're looking for.
let shrinkAction = SKAction.scaleBy(0.5, duration: 1.0)
let waitAction = SKAction.waitForDuration(0.5)
let sequenceAction = SKAction.sequence([shrinkAction, waitAction])
let repeatAction = SKAction.repeatActionForever(sequenceAction)
self.yourBirdNode.runAction(repeatAction)
Depending on what you'll do next, take note that some action are automatically reversible (through reversedAction) and some aren't.

Resources