-2

SKAction has waiting for duration abilities, for a period of time on a node. And seems to perform actions on nodes. Like moveTo, etc.

If I don't want that, rather I'd prefer to call functions within GameScene after a period of time, how do I do that with SpriteKit in the GameScene, not on a Sprite or other Node?

Are SKActions the way to do this? The only way to do this?

Yes. This question IS that ridiculously simple. I lack the heuristics and terminology to find an answer. Just keep looping around on how SKAction waits are calls on SKSprites for things like scale, rotation, etc, after time. Which isn't want I want/need.

Update:

Desired outcome, inside GameScene

doSetupStuff() // does some stuff...

waitForAWhile() // somehow wait, perhaps do somethings in here, while waiting

doSomethingElse() // does this after the waitForAWhile has waited

UPDATE 2:

What I think happens, again, inside didMove(to view...)

        func wait(){
            let timeToPause = SKAction.wait(forDuration: 3)
            run(timeToPause)
        }


     let wontwait = SKAction.wait(forDuration: 3)
  run(wontwait)
  thisFunction(willnot: WAIT"it starts immediately")

    wait()
    thisFunction(forcedToWait: "for wait()'s nested action to complete")

UPDATE 3:

Found a way to get the delay without using SKActions. It's a little crude and brutal, but makes more sense to me than SKActions, so far:

DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) {
            print("I waited ten seconds before printing this!")
        }
Confused
  • 6,048
  • 6
  • 34
  • 75
  • GameScene is a node, you can run actions on the scene itself – Knight0fDragon Oct 12 '16 at 17:52
  • Yes, I know it's a node. BUT HOW!!?? – Confused Oct 12 '16 at 17:56
  • same way you do on a node, in your game scene class, you do self.run(action:) – Knight0fDragon Oct 12 '16 at 17:59
  • Search for the 'SKAction Class Reference' documentation - see if anything in there meets your needs. – Steve Ives Oct 12 '16 at 18:07
  • @SteveIves If I'm asking a question here, you can assume I've read pages like that, more than once. And still failed to grok how what I'm asking is done. – Confused Oct 12 '16 at 18:37
  • Yes, that means I'm that dense. And I've never used an SKAction before. So the leap from never using one, to trying to figure out how to use one that calls a function within SKScene is all but bewildering, for me. – Confused Oct 12 '16 at 18:39
  • @SteveIves for example, a "Selector" seems to be ideal. But what is a Selector? What's its syntax? There is NO example in the page you're referencing, nor the page specifically about the use of them in an Action. It could be a direct method name, it could be dot syntax, it could be "A String"... I have no idea. It just is never articulated beyond... "A Selector"... as far as I can see. – Confused Oct 12 '16 at 18:41
  • @SteveIves here's the page that's close to being a total blank, for me: https://developer.apple.com/reference/spritekit/skaction/1417764-perform – Confused Oct 12 '16 at 18:51
  • @Confused - it is. You need to start with the full SKAction class reference : https://developer.apple.com/reference/spritekit/skaction. The 'Declaration' section for the 'perform' page tells you the calling syntax. You're not dense, it just takes practice :-) – Steve Ives Oct 12 '16 at 20:21
  • I'm perhaps asking the question badly. How do I wait before calling a function, in GameScene, from GameScene, inside didMove(to view...)? @SteveIves – Confused Oct 12 '16 at 21:14
  • @SteveIves, again, I've read that page, twice. Today. And I'm still not seeing a pathway to understanding how I setup logic that causes a delay before calling functions I want to do that do a whole heap of changes to the scene. – Confused Oct 12 '16 at 21:16
  • @SteveIves I'm stuck at the "how do I do this", not even at "what's wrong with my code" stage. – Confused Oct 12 '16 at 21:16
  • @SteveIves I'm finding that there's several ways to do this. Probably the most elegant and easy to understand (maybe) is the one I've written out as an "answer to my own question". I'll go bury my head in the sand, again, while I read through more ways to potentially achieve this, and until I can conceive of how to use this for game logic, etc. – Confused Oct 12 '16 at 22:27

4 Answers4

1

You can use below function

#define ANIM_TIME 2

SKAction *customACtion = [SKAction customActionWithDuration: ANIM_TIME actionBlock:^(SKNode *node, CGFloat elapsedTime) {

       // Do Something Here
}];
Guru
  • 21,652
  • 10
  • 63
  • 102
  • what happens if self dies before the delay, does it crash? Also, not recommended due to SpriteKit having is own way to track time (If you are in a paused state, this method still fires) – Knight0fDragon Oct 12 '16 at 18:04
  • @Knight0fDragon, yes. Better to use actions on node to call functions in SpriteKit & Cocos2d – Guru Oct 12 '16 at 18:06
  • Sorry, I can't read this. Objective-C is like hieroglyphics, to me. – Confused Oct 12 '16 at 18:36
  • My translation of this line: make constant of ANIM_TIME = 2, create a customAction that has a duration of ANIM_TIME, then... actionBlock:^ I start getting lost, and I see no need for the SKNode variable, nor do I understand why it's there, or the elapsed time. That's how dense I am. – Confused Oct 12 '16 at 18:44
  • After looking into this more, I don't see how this is an answer to the question. This is designed to repeat a block of code for a period of time, and report the current time each time it repeats the block, for as long as its duration of this action lasts. I think. But I could be wrong. From docs: "When the action executes, the block is called repeatedly until the action’s duration expires. The elapsed time is computed and passed to the block whenever the block is called." – Confused Oct 12 '16 at 22:25
1

An option, as you cited, is to manage this externally. The way I typically manage this sort of thing is to have an externally run update cycle. One that

To drive this updater, you could use either CADisplayLink (which is what I use right now with my OpenGL renderer) or a dispatch source timer (which I have used with my SpriteKit engine). When you use an updated, you want to calculate the delta time. The tick handler could look something like:

func tickHandler() {
    let currTime = NSDate().timeIntervalSince1970
    let dt = lastTime - currTime // lastTime is a data member of the class

    // Call all updaters here, pretend "updater" is a known updater class
    updater.update(dt)
}

And updater's update method would look something like:

func update(deltaTime:NSTimeInterval) {
    // Do your magic
}

I typically have a main overall updater running independent of what people are calling scenes. Example usage would be something like having an attract mode like in old school arcade games. There they show title screen, sample game play, high scores, rinse and repeat. Scenes would be title, game play, high score. Here you can your main updater manage the time and coordinate the construction/destruction/switching of the scenes. Note this implies having an overall scene manager (which is actually quite handy to have).

For your case, you could use this updater to drive the GameScene updater. It's updater could look something like:

func update(deltaTime:NSTimeInterval) {
    switch state {
        case .SetupState:
            // noop?
            println("I'm in setup") // Shown just so you can see there is a setup state
        case .WaitState:
            waitTime += deltaTime
            if waitTime >= kWaitTime {
                // Do whats you gots to do
                doSomethingElse()
                state = .NextState
            }
        case .NextState:
            // blah blah blah blah
    }
}

So the flow to do this call path from your driver (CADisplayLink or dispatch source) would be something like:

tickHandler -> master updater -> game scene updater

Some will def find this is perhaps a little heavy handed. I, on the other hand, find this very helpful. While there is obviously some time management and the loss of being able to fire and forget, it can help provide more control for orchestrating pieces, as well as arbitrarily changing state without having to worry about killing already queued actions. There is also nothing that says you still cannot mix SKAction. When I did use SpriteKit, I did all my updating this way along with some dispatched items. I only used SKAction to update hierarchy. Keep in mind that I used my own animation and physics system. So at least for me I had a lot less dependency on SpriteKit (it effectively was just a renderer for me).

Note you have to have your own means to handle pause and coming to foreground where your timer will need to be resynced (you only need to worry about tickHandler). Breakpoints also will cause time jumps.

Mobile Ben
  • 7,121
  • 1
  • 27
  • 43
  • I'm going to need some heady Scotch to have a hope of digesting and then articulating this in my little game world, and head. WOW! AWESOME!!! – Confused Oct 13 '16 at 02:36
  • But, to one of your points, I can conceive of how this liberates thinking about game logic and state to an elevated state. – Confused Oct 13 '16 at 02:37
  • And, in that sense, this more thoroughly answers my question than my own research was doing. I still am having trouble putting it all together, as in how I move from one state of activity to another for the game world and its members. Very annoying, but this is most helpful in seeing that there's ways and means outside the direct approaches of SKActions and ViewControllers – Confused Oct 13 '16 at 02:39
  • When I see people's code snippets, it generally seems they are using the game scene as a means to drive everything. Which is one of the reason people end up with questions around moving between scenes. When I used SK, I actually only had 1 game scene. The reset were managed by my own rendition of scene. Essentially any data you find needed to control the overall game, you need to get it out of the scene. It can, BTW, be passed into the scene as an object. The scene manager can be used to orchestrate this. – Mobile Ben Oct 13 '16 at 02:44
  • For clarity, I mean "get it out of the scene" as in it is not solely belonging to to the scene. Passing an object into it which gets shared amongst all the scenes, or having it in a singleton/global/however you will work. This way it can be shared. – Mobile Ben Oct 13 '16 at 02:46
  • I'm not quite at the point where I need to extricate all the stuff from the Scene, but it will come. For now I'm just plodding along adding things to it and the classes of things that propagate into the scene. But at some point next week I'll have to break it all out, and think about scene changing etc. – Confused Oct 13 '16 at 02:55
  • I might be approaching the need for a GameManager singleton faster than I thought... can you help with this? http://stackoverflow.com/questions/40015729/access-array-in-gamescene-from-skspritenode-class-instance – Confused Oct 13 '16 at 08:19
1

Another way to make something happen after a certain period of time is to make use of the 'current time' parm passed to update(). The following code will spawn a boss at intervals ranging from 20 to 30 seconds.

In your property definitions:

var timeOfLastBoss: CFTimeInterval = -1  //Indicate no boss yet
var timePerBoss = CFTimeInterval()

. . .

didMoveToView() {
        ...
        timePerBoss = CFTimeInterval(Int.random(20...30))
        '''
        }

. . .

func update(currentTime: CFTimeInterval) {
    ...
    spawnBossForUpdate(currentTime)
    ...
    }

' ' '

func spawnBossForUpdate(currentTime : CFTimeInterval) {
    if ( timeOfLastBoss == -1 ) {timeOfLastBoss = currentTime}
    if (currentTime - timeOfLastBoss < timePerBoss) {return}
    // Rest of 'spawnBoss code
    self.timePerBoss = CFTimeInterval(Int.random(20...30))
    self.timeOfLastBoss = currentTime  
}
Steve Ives
  • 7,894
  • 3
  • 24
  • 55
  • I've stared at this for 10 straight minutes trying to figure out how to get the first IF for the no bosses yet test out of the function for "optimisation". Thanks for the headache! ;) – Confused Oct 14 '16 at 07:57
  • The randomisation element here, and the adaptability of this leads me to thinking about how to make glitch styled motion graphics, with code. Something I'd normally make in AE, where real randomness isn't an option. – Confused Oct 14 '16 at 07:58
  • @Confused - don't bother with optimising it until you have a problem (read up on premature optimisation). If you think about it, no matter how you 'do something after a period of time has passed', the computer has to continually check to see if that period of time has passed, so SKAction, NSTimers etc all require the comptuer to be doing *something* until the time has expired. – Steve Ives Oct 14 '16 at 08:08
  • I was taking the piss out of myself for doing premature optimisation. Which I think is a lesser sin than "premature maintainable architecture consideration", which is where I spend most of my time, in ignorance without bliss ;) – Confused Oct 14 '16 at 08:34
0

One way, using SKActions, in Swift 3.0, looks like this:

DEFINE: aPatientlyWaitingFunction() at the top level of GameScene class.

To cause a delay to happen before calling the above function, inside

didMove(to view...)

three ways I've found to do this using Actions:

All three ways seem to accomplish the exact same thing:

    let timeToWait: TimeInterval = 3 // is seconds in SKAction thinking time

    let waitSomeTime = SKAction.wait(forDuration: timeToWait)

    // 1st way __________________________________________
    // with a completion handler, the function can be called after Action
    run(waitSomeTime) {self.aPatientlyWaitingFunction()}

    // 2nd way __________________________________________
    // as a completion to be done after action, in the run invocation:
    run(waitSomeTime, completion: aPatientlyWaitingFunction)

    // 3rd way __________________________________________
    // alternatively, as part of a sequence of actions...
    // Create a sequence, by making a run action from waitSomeTime and...
    let thenDoThis = SKAction.run(aPatientlyWaitingFunction)

    // then activate sequence, which does one action, then the next
    run(SKAction.sequence([waitSomeTime, thenDoThis]))

    // OR... for something different ____________________
    ////////////////////////////////////////////////////////
    DispatchQueue.main.asyncAfter(deadline: .now() + timeToWait) {
        self.aPatientlyWaitingFunction()
        print("DispatchQueue waited for 3 seconds")
    }
Confused
  • 6,048
  • 6
  • 34
  • 75