13

I am currently trying to implement a timer for my sprite kit game, but I don't get it working. The initial value of the timer always remains the same.

I am assuming I need to update the label somehow/somewhere, but I don't know HOW and WHERE :? I don't get the point. Any ideas?

Here is my code within my GameScene Class

    let levelTimerLabel = SKLabelNode(fontNamed: "Chalkduster")
var levelTimerValue: Int  = 500

var levelTimer = NSTimer()

func startLevelTimer() {

    levelTimerLabel.fontColor = SKColor.blackColor()
    levelTimerLabel.fontSize = 40
    levelTimerLabel.position = CGPoint(x: size.width/2, y: size.height/2 + 350)
    addChild(levelTimerLabel)

    levelTimer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: Selector("levelCountdown"), userInfo: nil, repeats: true)

    levelTimerLabel.text = String(levelTimerValue)

}

func levelCountdown(){

    levelTimerValue--

}
Whirlwind
  • 14,286
  • 11
  • 68
  • 157
boehmatron
  • 713
  • 5
  • 15

2 Answers2

16

I would stick to SKActions for these kind of tasks in SpriteKit due to fact that NSTimer is not affected by scene's, or view's paused state, so it might lead you into troubles. Or at least, it will require from you to implement a pause feature in order to pause your timers in certain situations, like when user pause the scene, or receive a phone call etc. Read more here about SKAction vs NSTimer vs GCD for time related actions in SpriteKit.

import SpriteKit

class GameScene: SKScene {

var levelTimerLabel = SKLabelNode(fontNamed: "ArialMT")

//Immediately after leveTimerValue variable is set, update label's text
var levelTimerValue: Int = 500 {
    didSet {
        levelTimerLabel.text = "Time left: \(levelTimerValue)"
    }
}

override func didMoveToView(view: SKView) {



levelTimerLabel.fontColor = SKColor.blackColor()
levelTimerLabel.fontSize = 40
levelTimerLabel.position = CGPoint(x: size.width/2, y: size.height/2 + 350)
levelTimerLabel.text = "Time left: \(levelTimerValue)"
addChild(levelTimerLabel)

    let wait = SKAction.waitForDuration(0.5) //change countdown speed here
    let block = SKAction.runBlock({
        [unowned self] in

        if self.levelTimerValue > 0{
            self.levelTimerValue--
        }else{
            self.removeActionForKey("countdown")
        }
    })
    let sequence = SKAction.sequence([wait,block])

    runAction(SKAction.repeatActionForever(sequence), withKey: "countdown")
}

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

    //Stop the countdown action

    if actionForKey("countdown") != nil {removeActionForKey("countdown")}
}
}
Community
  • 1
  • 1
Whirlwind
  • 14,286
  • 11
  • 68
  • 157
  • How does this have a key of ("countdown")? – Confused Oct 13 '16 at 05:17
  • @Confused In this example, action is executed on a scene with a "countdown" key. – Whirlwind Oct 13 '16 at 05:25
  • So it removes a whole scene? – Confused Oct 13 '16 at 05:26
  • @Confused No. You check if there is an action (running on a scene) associated with "countdown" key, and if true, you remove that action. Also, using keys can be handy when you want to grab a certain action and pause it / unpause it or speed it up ... – Whirlwind Oct 13 '16 at 05:32
  • That's another part I don't get. How does it have a key? And why are you searching for the key of a scene to remove an action that's on a Scene with a key of that name? Surely that means if the scene has the key "countdown", then all actions will be removed from the scene. CONFUSED!!! – Confused Oct 13 '16 at 05:34
  • @Confused Nope. Don't get me wrong, but you should really start with docs or basic tutorials. Let me try to explain this once again (but everything is already in the code): You create an action instance ( you can call it action object of type SKAction)... And you run that action on the scene. Also you have associated that action with a "countdown" key. Later on, you can use that key to access the action associated with it. Make sense? It is actually all in the code. You have to read more carefully. Okay gtg now :) Good luck with your app! – Whirlwind Oct 13 '16 at 05:43
  • I don't see the point where you associated the key "countdown" with anything. I see you using it, many times. That's the first question: "How does this have a key of ("countdown"). That's all I'm asking. The confusion comes from constantly not seeing how this is associated with anything, only that it's used. – Confused Oct 13 '16 at 05:47
  • @Confused How would you stop an action if there is no key associated with it? Imagine that you have more actions running on the scene at the same time, but you want to stop only one scene. So, how would you do it without a key? – Whirlwind Oct 13 '16 at 05:51
  • Where and when did you associate the key with the action? – Confused Oct 13 '16 at 05:52
  • The first time, in the above code, I see use of that key is here: self.removeActionForKey("countdown") // This uses the key, it's already associated with the Action, somehow, somewhere. I'm asking how, where, when did you you associate this key with the action? – Confused Oct 13 '16 at 05:53
  • @Confused Please look at the code. Look at the runAction(withKey:) method call. – Whirlwind Oct 13 '16 at 05:54
  • The only thing i can think, is that this line is not USING the key, this line could be assigning the key: runAction(SKAction.repeatActionForever(sequence), withKey: "countdown") – Confused Oct 13 '16 at 05:54
  • argh, we're saying the same thing at the same time. But my confusion is that this looks like usage of the key, not assignment of the key to the action. – Confused Oct 13 '16 at 05:55
  • It's bad API design, in my language, I'd have called this "withKey:" something else, something like "assignKey:" so that it's clear "withKey" isn't a search. – Confused Oct 13 '16 at 05:56
  • 1
    The first 10x I read through this, I thought this line meant, "Find the action with this name, and this key, and run it", and I wrongly assumed it was some kind of protection against another action having the same name. But it's apparent (now) that this is the assignment of the key to the action, right?... runAction(SKAction.repeatActionForever(sequence), withKey: "countdown") – Confused Oct 13 '16 at 05:58
  • And my sincere apologies if my utter confusion caused any distress. And THANK YOU. I now know withKey: is both assignment and search and destroy. Previously thought it was just search. – Confused Oct 13 '16 at 06:28
  • @Confused Yes,it is the assignment of a key to the action. As I said, it could be handy if you don't have a reference to the certain action but you want to "grab" it and manipulate with it. – Whirlwind Oct 13 '16 at 11:00
  • why did you initialize with 'levelTimerLabel.text' with didSet and then again in didmoveto? – μολὼν.λαβέ Nov 18 '17 at 20:39
  • @μολὼν.λαβέ `didSet` observer is executed every time a related property is set to a new value. If you check the code above, that happens every 0.5 seconds. On the other side, `didMove(toView:)` method, is called only once, and that is the place where the initial value of a label is set. I don't really get what confuses you, but it seems that you haven't really understood what this code does. – Whirlwind Nov 18 '17 at 21:00
  • no i don't see the need to intialize in two places with old style of obj-c getters/setters, my code i initialize things once without options or ambiguity. – μολὼν.λαβέ Nov 25 '17 at 07:13
  • It is not initialized twice. Say that you dont use levelTimerValue and set label’s text directly... How would you extract the value of a timer? By taking the label’s text value, and parsing it into int or double? I mean, it is possible, but I like my way more. – Whirlwind Nov 25 '17 at 08:07
4

Sounds like you need to update the label everytime levelTimerValue is changed. The easiest way would be something like this.

func levelCountdown(){
    levelTimerValue--
    levelTimerLabel.text = String(levelTimerValue)
}
Craig Siemens
  • 12,942
  • 1
  • 34
  • 51