10

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.

J.Treutlein
  • 963
  • 8
  • 23
  • 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 Dec 17 '16 at 09:28
  • The way I read this (and that's not a credible reading), this should work. The Action should stop. – Confused Dec 17 '16 at 09:30
  • Yeah that's what I thought too. It's a bit odd. Anyway thanks for the tips. – J.Treutlein Dec 17 '16 at 11:20
  • @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 Dec 17 '16 at 22:03
  • 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 Dec 17 '16 at 22:08
  • @J.Treutlein Hm, just put a print statement before that check and you will see what I am talking about. I just tried your example. I start with stop = false, and set it to true in touchesBegan. After setting the stop variable to true, I see drawFrame printed twice :) It doesn't work like you described. Try it for yourself. It behaves the same. My example will prevent drawFrame() to be executed multiple times. – Whirlwind Dec 17 '16 at 22:11
  • I'm having exactly the same problem even when I use `removeAllActions()` – SaganRitual Aug 10 '17 at 14:11
  • @GreatBigBore How I solved the issue was not removing the delay, but working around it. When I wanted the loop to stop I set stop = true. The code in the loop only ran if !stop so instead the program tried to removeAllActions. Eventually everything will stop even though the loop would just keep calling removeAllActions and not doing the actual code. It is a work around that avoids the issue and works well. – J.Treutlein Aug 10 '17 at 21:29

3 Answers3

5

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.

Whirlwind
  • 14,286
  • 11
  • 68
  • 157
  • Thanks!! That works fantastically! Although, it is hard to directly implement to fix my issue. However, I will probably be using this as a pause system for my program so thanks so much! – J.Treutlein Dec 17 '16 at 22:15
  • @J.Treutlein Actually, the point is that you should always grab an action by the key and then pause it (speed = 0.0), or remove it. That you have precise control. – Whirlwind Dec 17 '16 at 22:17
  • Thanks for the help I really appreciate it. It works better than the other method with removeAllActions() because it is far more precise so thanks so much. – J.Treutlein Dec 17 '16 at 22:20
  • cool. i didnt kno you could toggle bool like that, or use ternary in a pram (without closure) – Fluidity Dec 17 '16 at 22:26
  • @J.Treutlein No problem :) If I somehow find out why drawFrame() were called multiple times in your case, I will update my answer. This is probably related to how instantaneous and non-instantaneous actions works, as well as the fact that actions are postponed for the execution to the next frame. Also, I would say it has something with a delay (its duration). And when you mix all that with a docs that actually (IMO) lacking about explaining actions sequences in a detail, then it is hard to figure out what is really going and give an appropriate answer when people run into issues like this :) – Whirlwind Dec 17 '16 at 22:27
  • Initially, no. I had it in the wrong spot, like creating a sequence. DOH. Got it working now... see here: http://stackoverflow.com/questions/41602596/how-to-move-an-object-towards-a-direction-without-stopping – Confused Jan 12 '17 at 14:53
4

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()
    }
}
Luca Angeletti
  • 58,465
  • 13
  • 121
  • 148
  • Thanks for that. The iteration still runs 2 more times after stop is set to true, but that it is the most efficient method currently. I will probably use this one since it's the best that I've tried. – J.Treutlein Dec 17 '16 at 20:34
0

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
Confused
  • 6,048
  • 6
  • 34
  • 75
  • Thanks for the quick reply. – J.Treutlein Dec 17 '16 at 06:23
  • I tried it in my project but now it just continues the same forever. – J.Treutlein Dec 17 '16 at 06:24
  • I'm not sure why it doesn't' work, and I have used prints to make sure the stop detection works but the speed doesn't change. – J.Treutlein Dec 17 '16 at 06:25
  • My code isn't that short, it is about 350 lines, so should I just cut the stuff that isn't related and post it? – J.Treutlein Dec 17 '16 at 08:00
  • Yeah, exactly, just show the stuff that creates the action, and the assignment of it, and then the bit where you call it, and the bit where you stop it. And let me know where they all are in terms of which classes they're in, etc. – Confused Dec 17 '16 at 08:22
  • I'm NOT a good coder. I'm a designer, with a lot of experience in animation, hence my interest in anyone doing something in 0.01 of a second ;) @J.Treutlein – Confused Dec 17 '16 at 08:22
  • I have edited my original post with my code. By the way, an animation designer would probably help me more in this situation than a coder so thanks so much for the help. – J.Treutlein Dec 17 '16 at 09:24
  • in some ways... you would chortle endlessly if you saw how slowly I read code. – Confused Dec 17 '16 at 09:35
  • @Fluidity There is actually no difference between removingAllActions() and pausing a certain action in OP's example. It works the same way. I tried both. Also, using removeAllActions() is not always possible and convenient way to go. I think that accessing action by the key is always more appropriate because gives you a precise control. – Whirlwind Dec 17 '16 at 22:08
  • 3
    @Confused I just played a bit with a debugger, and figured out why your example didn't worked (why the action didn't stop after setting drawingFrame's speed property to zero). The debugger showed that when you run the action, it gets another address in memory, like it is copied aside from an original property and then run. So, when you change the speed of a property, you actually don't change the speed of an running action. But, if you grab the action by the key, then you get the real instance, so you can affect on it. Also, documentation lacks in describing this behavior... – Whirlwind Dec 17 '16 at 22:20
  • @Whirlwind thatsvaluable info for sure. ty – Fluidity Dec 17 '16 at 22:28
  • seems like the action object is just a template, not a reference ... each node that uses it copys (value) from it, yes? @Whirlwind This is why id think removing actions from the NODE would inta-stop – Fluidity Dec 17 '16 at 22:31
  • @Fluidity Well there are a lot of facts about actions...And probably we are missing something... Something, probably deeply burrowed in a dark sections of docs :) But eventually, somebody will came up with an explanation because everything has its answer :) – Whirlwind Dec 17 '16 at 22:38
  • @Fluidity Yeah. It seems like that copies of that property (of drawingFrame:SKAction) is used when you say self.run(myAction) – Whirlwind Dec 17 '16 at 22:39
  • @Whirlwind we should make an iOS app called "The missing DocSet" and also have a wiki page on GitHub... And why the F are there not stickies on SO? A lot of things could be sticky material... – Fluidity Dec 18 '16 at 00:19
  • @Fluidity theMissingDocsSet++ – Whirlwind Dec 18 '16 at 00:21
  • 1
    Um, pardon... theMissingDocsSet += 1 – Whirlwind Dec 18 '16 at 00:21
  • Astonishing. I'm guilty of not testing this, just assumed it would work. Baffled that it doesn't. Apple... – Confused Dec 18 '16 at 03:07