swift: SKSpriteNode not rotating in update function - ios

I'm trying to rotate a bone image which is a SKSpriteNode. I created it and on update, I tried running a SKAction which would rotate it. This doesn't do anything. I even tried to decrease the zRotation each time on update by 10 degrees and still nothing happened. I'm sure that the application is not frozen as when I do a println on update, text keeps rolling. Also, other parts of the app are responding. Its just the bone thats not rotating.
Code:
Declaration on start of class
let bone = SKSpriteNode(imageNamed: "bone")
let rotateBone = SKAction.rotateByAngle(30, duration: 1)
Setting up at didMoveToView
bone.anchorPoint = CGPointMake(0.5, 0.5)
bone.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
bone.zRotation = CGFloat(arc4random())
self.addChild(bone)
Attempting to rotate
override func update(currentTime: NSTimeInterval)
{
bone.runAction(rotateBone)
}

You are re-running the action on every update (every frame):
override func update(currentTime: NSTimeInterval)
{
bone.runAction(rotateBone)
}
Actions run over time. Run it once and let it do its job. Restarting it repeatedly will only replace the previous action and essentially reset it.

Related

SpriteKit: how to smoothly animate SKCameraNode while tracking node but only after node moves Y pixels?

This question and others discuss how to track a node in SpriteKit using a SKCameraNode.
However, our needs vary.
Other solutions, such as updating the camera's position in update(_ currentTime: CFTimeInterval) of the SKScene, do not work because we only want to adjust the camera position after the node has moved Y pixels down the screen.
In other words, if the node moves 10 pixels up, the camera should remain still. If the node moves left or right, the camera should remain still.
We tried animating the camera's position over time instead of instantly, but running a SKAction against the camera inside of update(_ currentTime: CFTimeInterval) fails to do anything.
I just quickly made this. I believe this is what you are looking for?
(the actual animation is smooth, just i had to compress the GIF)
This is update Code:
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
SKShapeNode *ball = (SKShapeNode*)[self childNodeWithName:#"ball"];
if (ball.position.y>100) camera.position = ball.position;
if (fabs(ball.position.x-newLoc.x)>10) {
// move x
ball.position = CGPointMake(ball.position.x+stepX, ball.position.y);
}
if (fabs(ball.position.y-newLoc.y)>10) {
// move y
ball.position = CGPointMake(ball.position.x, ball.position.y+stepY);
}
}
I would not put this in the update code, try to keep your update section clutter free, remember you only have 16ms to work with.
Instead create a sub class for your character node, and override the position property. What we are basically saying is if your camera is 10 pixels away from your character, move towards your character. We use a key on our action so that we do not get multiple actions stacking up and a timing mode to allow for the camera to smoothly move to your point, instead of being instant.
class MyCharacter : SKSpriteNode
{
override var position : CGPoint
{
didSet
{
if let scene = self.scene, let camera = scene.camera,(abs(position.y - camera.position.y) > 10)
{
let move = SKAction.move(to: position, duration:0.1)
move.timingMode = .easeInEaseOut
camera.run(move,withKey:"moving")
}
}
}
}
Edit: #Epsilon reminded me that SKActions and SKPhysics access the variable directly instead of going through the stored property, so this will not work. In this case, do it at the didFinishUpdate method:
override func didFinishUpdate()
{
//character should be a known property to the class, calling find everytime is too slow
if let character = self.character, let camera = self.camera,(abs(character.position.y - camera.position.y) > 10)
{
let move = SKAction.move(to: character.position, duration:0.1)
move.timingMode = .easeInEaseOut
camera.run(move,withKey:"moving")
}
}

Track the position of SKSpriteNode while doing a SKAction moveTo

I'm trying to figure a way to track a postions for multiple nodes, that spwan randomly on the screen so i can make a changes to them while moving when the reach random postion.
the nodes just move along the x axis and i want to be able to generate random number from 0 to postion.x of the ball, and change the color when it reachs the postion
override func update(currentTime: CFTimeInterval)
i tried tacking changes in update method but as soon as new node appers i lost track of the previos one
i also tried
let changecolor = SKAction.runBlock{
let wait = SKAction.waitForDuration(2, withRange: 6)
let changecoloratpoint = SKAction.runBlock { self.changecolorfunc(self.ball)}
let sequence1 = SKAction.sequence([wait, changecoloratpoint])
self.runAction(SKAction.repeatAction(sequence1, count: 3))
}
but it doesn't give me any control over the random postion
You have already all you needed.
Suppose you have a reference for your node:
var sprite1: SKSpriteNode!
And you want to spawn it to a random position (an example method..):
self.spawnToRandomPos(sprite1)
And suppose you want to moveTo your sprite1:
self.sprite1.runAction( SKAction.moveToY(height, duration: 0))
Everytime you check his position you know where is it:
print(sprite1.position)
So to know always your sprite1 position you could do this code and you see all it's movements:
override func update(currentTime: CFTimeInterval) {
print(sprite1.position)
}
P.S.:
In case you dont have references about your object spawned you can also give a name to a generic object based for example by a word followed to a counter (this is just an example):
for i in 0..<10 {
var spriteTexture = SKTexture(imageNamed: "sprite.png")
let sprite = SKSpriteNode(texture: spriteTexture, size: spriteTexture.size)
sprite.name = "sprite\(i)"
self.addChild(sprite)
}
After this to retrieve/re-obtain your sprite do:
let sprite1 = self.childNodeWithName("sprite1")
There are probably a hundred different ways of doing this. Here is how I would do it.
in your update func
checkForCollisions()
this will just scroll through "obstacles" that you randomly generate, and place whoever you want the color trigger to happen. They can be anything you want just change the name to match. if they overlap your "ball" then you can color one or the other.
func checkForCollisions() {
self.enumerateChildNodesWithName("obstacles") { node, stop in
if let obstacle: Obstacle = node as? Obstacle {
if self.ball.intersectsNode(obstacle) {
//color node or ball whichever you want
}
}
}
}

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*/)

How do SKAction durations work in relation to frame rates?

I am trying to understand how SKActions work. Specifically with SKAction.runAction(), one of the parameters is a duration for the animation. However it seems that whatever I put in as the duration, the animation will render every frame. I have tried to put the SKAction.runAction() line in other places, but it seems to only work in the update() override method in SKScene. I have tried the SKAction.repeatActionForever, but this does not seem to be able to run parallel to other processes.
My question is, if there is a required parameter for the duration of the animation, where would one place the SKAction.runAction() method to have the animation run based on the given duration rather then on the frame rate?
This is my code:
GameScene.swift (The Player class is a subclass of the Character class. Refer to the code below.)
let player = Player()
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
//Code here to recognize user inputs
scene?.addChild(player.sprite)
}
override func update(currentTime: CFTimeInterval) {
player.sprite.runAction(SKAction.runBlock(player.move))
}
}
CharachterClass.swift move() function
func move(){
switch(direction){
case "up":
sprite.position = CGPoint(x: sprite.position.x, y: sprite.position.y + speed)
case "down":
sprite.position = CGPoint(x: sprite.position.x, y: sprite.position.y - speed)
case "right":
sprite.position = CGPoint(x: sprite.position.x + speed, y: sprite.position.y)
case "left":
sprite.position = CGPoint(x: sprite.position.x - speed, y: sprite.position.y)
default:
break
}
SKAction.moveTo(sprite.position, duration: 1)
}
Note: the direction variable is the direction that the sprite is moving in and speed variable is the amount of pixels the sprite should move each time the SKAction is run.
I don't know what you intend to do with this:
override func update(currentTime: CFTimeInterval) {
player.sprite.runAction(SKAction.runBlock(player.move))
}
But in effect you can quite simply do the following instead:
override func update(currentTime: CFTimeInterval) {
player.move()
}
The difference between the two methods is that runAction will schedule the block execution and depending on when the scheduling occurs, it may not run in the current frame (update cycle) but in the next one. So quite possibly the run block solution introduces a 1-frame delay.
Next, this will run a move action for the duration of 1 second (not: 1 frame):
SKAction.moveTo(sprite.position, duration: 1)
Since you run this in every update method, you effectively null and void the SKAction's primary purpose: to run a task over a given time (duration).
Worse, you run a new move action every frame so over the duration of 1 second you may accumulate about 60 move actions that run simultaneously, with undefined behavior.
If you were to call runAction with the "key" parameter you could at least prevent the actions from stacking, but it would still be rather pointless because replacing an action every frame that is supposed to run over the time of 1 second means it will have little effect, if any.
Coincidentally, with the way the code is set up right now, you could as well just assign to position directly ... but ... wait, you already do that:
sprite.position = CGPoint(x: sprite.position.x - speed, y: sprite.position.y)
But immediately afterwards you run this move action:
SKAction.moveTo(sprite.position, duration: 1)
So all in all, you try to run an action over the course of 1 second that ends up being replaced every frame (60 times per second) - and that action does ... nothing. It will not do anything because you've already set the sprite to be at the position that you then pass into the move action as the desired target position. But the sprite is already at that position, leaving the action nothing to do. For one second - or perhaps it may quit early, that depends on SK implementation details we don't know about.
No matter how you look at it, this move action is over-engineered and has no effect. I suggest you (re-)read the actions article in the SK Programming Guide to better understand the timing and other action behaviors.

Resources