2

I have created a simple timer in Xcode for a game (SpriteKit):

The variables timer, seconds and score are initialized in the class.

func scoreTimer() {
     timer = Timer.scheduledTimer(timeInterval: 0.05, target: self, selector: #selector(countUp), userInfo: nil, repeats: true)
 }

@objc func countUp() {
     seconds += 0.05
     seconds = (seconds * 100).rounded() / 100
     score = score + 0.015
     updateScore()
 }

 func updateScore() {
     scoreLabel.text = "\(score)"
 }

The timer is started in didMove() when the GameScene has been loaded.

I try to call countUp() every 0.5 seconds. In this function the variable seconds is increased and rounded plus the score variable is increased. From there I would like to call the updateScore function to update the scoreLabel.text - but this doesn't work. The function is called, but the scoreLabel is not updated although when I try to print the score variable, it shows that it is increased constantly.

How can I achieve this and what's is wrong here?

Tyler A.
  • 3,048
  • 1
  • 23
  • 27
RjC
  • 827
  • 2
  • 14
  • 33

2 Answers2

4

Timers should be avoided with SpriteKit unless you are working with technologies outside of SpriteKit. The reason is because SpriteKit has its own time management, and a timer may not coincide with it. Example, in your case, timer updates score. What if you pause the game? Your timer still adds score unless you remember to stop it.

Instead, rely on the actions to do what you need Below is how you do what you are looking for.

let wait = SKAction.wait(forDuration: 0.5)
let update = SKAction.run(
{
        seconds += 0.05
        seconds = (seconds * 100).rounded() / 100
        score = score + 0.015
        updateScore()
}
)
let seq = SKAction.sequence([wait,update])
let repeat = SKAction.repeatForever(seq)
run(repeat)

@RonMyschuk mentioned a thought that I have missed, so I will add it in my answer as well.

If you need to create timers that you can individually pause or pause in a group, then add in extra SKNodes and attach the timer action to the node instead of the scene. Then when you need to pause the timer, you can just pause the node. This will allow your scene update to continue while your timers are paused.

Knight0fDragon
  • 16,609
  • 2
  • 23
  • 44
  • Can I have more than one running parallel? One SKAction updating the score every 0.5 seconds and one Action playing a random function every 30 seconds for instance ow would this be done differently with just one SKAction? – RjC Aug 10 '17 at 18:50
  • yes, as long as you do not assign 2 actions the same key, they will run in parallel unless you specifically put it in a sequence – Knight0fDragon Aug 10 '17 at 18:52
  • Thanks. Do I put this into "override func update(_ currentTime: TimeInterval)" - if so with what TimeInterval? – RjC Aug 10 '17 at 18:55
  • No, you only call it once when you need it, putting it in update would make in add a new timer every loop – Knight0fDragon Aug 10 '17 at 18:56
  • Thanks, all works very well. I suppose that when the GameScene changes (ie. a GameOverScene or back to the MenuScene), because of the update function the timer stops automatically and I don't have to stop or reset it? – RjC Aug 10 '17 at 21:49
  • If you go to another scene, it will enter into a pause state, so the actions will automatically stop. If nothing is holding on to the scene in memory, it will delete too – Knight0fDragon Aug 10 '17 at 21:50
  • This is likely to cause a retain loop because the block will retain self (call to `updateScore()`), and self retains the action when you call `run` on it. Something to beware of. – Tim Jul 16 '19 at 23:56
  • I don’t have a `self.updateScore`, but yes, if you are using self in the block, you need to use a weak version – Knight0fDragon Jul 17 '19 at 00:12
4

I second the statement that Timers should be avoided in Spritekit.

You can also update your label using the update loop.

Controlling the time through your update loop allows you to pause the "timer" if the game is stopped, without having to remove the action from the scene.

You could also have multiple timers running with this method

private var updateTime: Double = 0
private var updateTime2: Double = 0

override func update(_ currentTime: CFTimeInterval) {

   //this will be some sort of variable dictating the state of your game
   guard gameState == .playing else { 
       updateTime = 0
       updateTime2 = 0
       return 
   }

   if updateTime == 0 {
       updateTime = currentTime
   }

   if updateTime2 == 0 {
       updateTime2 = currentTime
   }

   if currentTime - updateTime > 0.5 {
        //do something every half second
        updateTime = currentTime

        score = score + 0.015
        updateScore()
    }

   if currentTime - updateTime2 > 30 {
        //do something every 30 seconds
        updateTime2 = currentTime

        someFunc() 
    }
}
Ron Myschuk
  • 6,011
  • 2
  • 20
  • 32
  • I am a little confused as to what you mean by "pause the "timer" if the game is stopped, without having to remove the action from the scene." If the scene is paused, then your actions are paused as well. – Knight0fDragon Aug 10 '17 at 20:02
  • not all stoppage in gameplay would be necessarily handled by pausing the scene. The way this is setup would dictate which game states the timer would tick down for, and any other game states the timer wouldn't tick. I personally like using this method and not pausing the scene even when I pause a game, because pausing the scene doesn't allow me to do SKActions like a slide in a pause menu. – Ron Myschuk Aug 10 '17 at 20:11
  • Ahh ok, that is why i always recommend having a worldNode, it allows you to split the behavior of your game from other elements that you may have on a scene – Knight0fDragon Aug 10 '17 at 20:13
  • I do as that as well (I use gameLayer as my main node), I didn't get into that in my answer because I didn't really want to make this too complicated for the OP – Ron Myschuk Aug 10 '17 at 20:14
  • I understand, just wanted to understand what you were referring to by game is stopped, you just meant if the game was in a pause state, I thought you were referring to switching apps – Knight0fDragon Aug 10 '17 at 20:16