endavid endavid - 6 months ago 73
Swift Question

SpriteKit: How to stop actions with removeAllActions immediately

According to the documentation, removeAllActions cancels the actions immediately, but the state of the object is getting updated for at least one tick.

I have a simple scale animation,

let scaleUp = SKAction.scaleBy(2.0, duration: time)
sprite.runAction(scaleUp)


and an event that removes all actions and resets the scale. The event gets called during the touchesBegan cycle,

sprite.removeAllActions()
sprite.setScale(1.0)


The animation stops, but the sprite is still in the wrong scale. If I call the same event again, then the scale is properly reset.

What's the exact timing of these actions? The documentation doesn't seem to mention any of this timing issues. Otherwise I would expect to have an "onCancel" callback that could be passed to runAction and called after removing it, analogous to "completion".

Edit:
The issue was a bit of my code that tried to reset the size of the sprite before removing the actions (I'm updating its texture, and the aspect ratio might change, so the size needs updating)

Repro case based on Whirlwind's answer below.

class GameScene: SKScene {
let sprite = SKSpriteNode(color: .whiteColor(), size: CGSize(width: 123, height: 123))
override func didMoveToView(view: SKView) {
sprite.alpha = 0.5
sprite.position = CGPoint(x: 448, y: 223)
addChild(sprite)
}
private func scaleTest() {
let scaleUp = SKAction.scaleBy(2.0, duration: 0.5)
let scaleDown = SKAction.scaleTo(1.0, duration: 3)
sprite.runAction(SKAction.sequence([scaleUp, scaleDown]))
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if sprite.hasActions() {
sprite.size = CGSize(width: 123, height: 123)
sprite.removeAllActions()
sprite.setScale(1.0)
} else {
scaleTest()
}
}
}


The fix:

sprite.removeAllActions()
sprite.setScale(1.0)
sprite.size = CGSize(width: 123, height: 123)

Answer

I can't reproduce what you are saying. For me, everything works fine. Take a look at this example (I didn't changed the size of a scene so it is 1024x768 which is default):

import SpriteKit

class GameScene: SKScene {

    let sprite = SKSpriteNode(color: .whiteColor(), size: CGSize(width: 123, height: 123))


    override func didMoveToView(view: SKView) {

        sprite.alpha = 0.5

         let sprite1 = SKSpriteNode(color: .redColor(), size: CGSize(width: 123, height: 123))

        let scaleUp = SKAction.scaleBy(2.0, duration: 7)
        sprite.runAction(scaleUp)
        sprite.position = CGPoint(x: 448, y: 223)
        addChild(sprite)

        sprite1.position = sprite.position
        addChild(sprite1)
    }


    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {


        sprite.removeAllActions()
        sprite.setScale(1.0)
    }

}

Notice how white sprite is scaled to its original size when screen is tapped (at least in my case).

A fact worth of mentioning about how SKActions work is that actions are queued and always processed in the next frame. Also, you can't run an action on a node which is not added to the scene. The point is that what you have posted should work, and if it doesn't work, then something else is wrong, but not this part of the code.

Comments