9
votes

I have been trying to make a forever running SKAction that I can stop whenever I want to. I have done it like this:

override func didMove(to view: SKView) {

    run(SKAction.repeatForever (
        SKAction.sequence ([
            SKAction.run(drawFrame),
            SKAction.wait(forDuration: 0.01),
            ])), withKey: "frameDrawing"
    )
}

Then in the drawFrame function I stop the SKAction like this:

func drawFrame() {
    //(code)
    if stop {
        removeAction(forKey: "frameDrawing")
    }
}

For some reason the SKAction only stops when it has run 3 or 4 more times after stop became true. I want it to stop instantly when stop is set to true, not after 3 or 4 more repeats.

If anyone knows how to fix this, please tell me because I've tried many things and they never fix the issue. Thanks!

Edit: Here is my code:

var drawingFrame = SKAction()
class GameScene: SKScene, SKPhysicsContactDelegate {

override func didMove(to view: SKView) {

    drawingFrame = SKAction.repeatForever (
        SKAction.sequence ([
            SKAction.run(drawFrame),
            SKAction.wait(forDuration: 0.03),
            ]))
    run(drawingFrame)
}

func drawFrame() {
   //(code)
    if stop {
        drawingFrame.speed = 0.0
    }
}

If you're wondering why I have set the SKAction drawingFrame to an empty SKAction at the start, it is because I needed to define the SKAction before both functions. Otherwise, it would be not defined for both functions.

EDIT FOR ANYONE WITH THE SAME PROBLEM: I have fixed the problem using my own thinking and @appzYourLife's solution. The most efficient way which works every time is to only run the code if stop equals false. But, make sure that the if statement that stops the program is outside of that bracket so the SKAction will eventually stop. This is the working code:

var drawingFrame = SKAction()
class GameScene: SKScene, SKPhysicsContactDelegate {

override func didMove(to view: SKView) {

    drawingFrame = SKAction.repeatForever (
        SKAction.sequence ([
            SKAction.run(drawFrame),
            SKAction.wait(forDuration: 0.03),
            ]))
    run(drawingFrame)
}

func drawFrame() {
    if stop = false {
       //(code)
    }
    if stop {
        removeAllActions()
    }
}

You may use an if else statement for the stop = false statement if you prefer that.

3
yeah, I do this same "trick" of top level declaration so I can access it. I don't know if this is the best way to do it, but it works.Confused
The way I read this (and that's not a credible reading), this should work. The Action should stop.Confused
Yeah that's what I thought too. It's a bit odd. Anyway thanks for the tips.J.Treutlein
@J.Treutlein Just one question...Where (how) do you set stop = true ? Why don't you update your question with a fully workable example because I think what you have posted, actually works as your previous example, means when you set stop = true, multiple drawFrame() calls will be executed right after stopping...Whirlwind
It checks if a variable is less than or equal to a number. If I posted everything associated with that variable and how it is changed I'd be posting most of my code so about 100 lines. This is why I avoided it. Anyway, my new solution works perfectly and how I wanted it to work so it doesn't need changing for me. Thanks for your interest and help though.J.Treutlein

3 Answers

5
votes

I don't have an exact explanation why drawFrame() is called multiple times at the moment (but I will try to discover that :D)... Anyways, try this code:

import SpriteKit

class GameScene: SKScene, SKPhysicsContactDelegate {



    var stop = false

    override func didMove(to view: SKView) {

        run(SKAction.repeatForever (
            SKAction.sequence ([
                SKAction.run({[unowned self] in
                    if self.stop {
                        self.action(forKey: "frameDrawing")?.speed = 0.0
                    }else{
                        self.drawFrame()
                    }

                }),
                SKAction.wait(forDuration:0.03),
                ])), withKey: "frameDrawing"
        )
    }

    func drawFrame() {
        //(code)
        print("drawFrame")

    }
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        stop = !stop
        print(stop ? "stopped": "running")
        if !stop {
              self.action(forKey: "frameDrawing")?.speed = 1.0
        }
    }
}

Use touchesBegan to toggle paused mode.

4
votes

You can remove all the actions running on the current node. This will remove immediately not only the sequence action but also the embedded actions.

func drawFrame() {
    if stop {
        removeAllActions()
    }
}
0
votes

Try creating a reference to the SKAction, and calling a change of speed (to 0.0) on that, when you want it to stop. Both of these will be quicker than the need to look up the name of the action and then remove it. But at 0.01 you're already repeating it faster than the frame rate (0.01666), so you're always going to get at least one extra iteration of the action, no matter how well you stop it.

something like:

let myAction = SKAction.repeatForever (
        SKAction.sequence ([
            SKAction.run(drawFrame),
            SKAction.wait(forDuration: 0.01),
            ]))

//when you want to run it
run(myAction)

// when you want to stop it:
myAction.speed = 0.0