ios8 Swift SpriteKit - Pause and Resume NSTimers i

2019-08-10 09:47发布

问题:

I have searched many times on the internet but could not find the answer to this question. I know how to pause and resume NSTimers by using the invalidate functions - timer.invalidate. and I know how to resume them. But I have a SpriteKit game. When I pause my game, I stop everything and the timers. I know that I can stop them using .invalidate but when I invalidate them:

For example lets say I have a 5 second timer that runs continously that spawns one block.

When the timer reaches second 3 of the cycle and when I paused the game, and invalidate the timers. When I resume, Now the timers second goes back to 0 and I must wait another 5 seconds. I want it to continue from where it left off, 3 , and wait 2 seconds for the block to spawn.

            blockGenerator.generationTimer?.invalidate()

            self.isGamePaused = true
            self.addChild(self.pauseText)

            self.runAction(SKAction.runBlock(self.pauseGame))

e`

and when I resume it:

blockGenerator.generationTimer = ...

I have to wait another 5 seconds, I want the timer to continue from where it left off

If you can help me, I appreciate it thank you.

回答1:

There is a way to pause/resume NSTimer instances, because using repeating timers we know the next fire date.

This is a simple class Timer and a protocol TimerDelegate


Protocol TimerDelegate

protocol TimerDelegate {
  func timerWillStart(timer : Timer)
  func timerDidFire(timer : Timer)
  func timerDidPause(timer : Timer)
  func timerWillResume(timer : Timer)
  func timerDidStop(timer : Timer)
}

Class Timer

class Timer : NSObject {

  var timer : NSTimer!
  var interval : NSTimeInterval
  var difference : NSTimeInterval = 0.0
  var delegate : TimerDelegate?

  init(interval: NSTimeInterval, delegate: TimerDelegate?)
  {
    self.interval = interval
    self.delegate = delegate
  }

  func start(aTimer : NSTimer?)
  {
    if aTimer != nil { fire() }
    if timer == nil {
      delegate?.timerWillStart(self)
      timer = NSTimer.scheduledTimerWithTimeInterval(interval, target: self, selector: "fire", userInfo: nil, repeats: true)
    }
  }

  func pause()
  {
    if timer != nil {
      difference = timer.fireDate.timeIntervalSinceDate(NSDate())
      timer.invalidate()
      timer = nil
      delegate?.timerDidPause(self)
    }
  }

  func resume()
  {
    if timer == nil {
      delegate?.timerWillResume(self)
      if difference == 0.0 {
        start(nil)
      } else {
        NSTimer.scheduledTimerWithTimeInterval(difference, target: self, selector: "start:", userInfo: nil, repeats: false)
        difference = 0.0
      }
    }
  }

  func stop()
  {
    if timer != nil {
      difference = 0.0
      timer.invalidate()
      timer = nil
      delegate?.timerDidStop(self)
    }
  }

  func fire()
  {
    delegate?.timerDidFire(self)
  }

Make your class conform to the protocol TimerDelegate and initialize a Timer instance with

var timer : Timer!
timer = Timer(interval: 5.0, delegate: self)

Methods

start() calls the delegate method timerWillStart and starts the timer.

pause() saves the difference between the current date and the next fire date, invalidates the timer and calls the delegate method timerDidPause.

resume() calls the delegate method timerWillResume, creates a temporary one shot timer with the saved difference time interval. When this timer fires the main timer will be restarted.

stop() calls the delegate method timerDidStop and invalidates the timer.

When the timer fires, the delegate method timerDidFire is called.



回答2:

First, let me say this - it is not possible to do with just NSTimer, there is no inbuilt function to do that (you can build logic around that as the answer from Vadian suggests). BUT.

Why NSTimer is not good idea

Lets stop and think for a little. For game objects and precise spawning, you should never use NSTimer in the first place. The problem is implementation of NSTimer (quoting the docs):

Because of the various input sources a typical run loop manages, the effective resolution of the time interval for a timer is limited to on the order of 50-100 milliseconds. If a timer’s firing time occurs during a long callout or while the run loop is in a mode that is not monitoring the timer, the timer does not fire until the next time the run loop checks the timer. Therefore, the actual time at which the timer fires potentially can be a significant period of time after the scheduled firing time.

There are other problems with NSTimer but that is out of scope of that question.

Solution

What you can do instead, you should listen to delta time change in each update call

let delta = currentPreciseTime - previousPreciseTime

Now, when you have that delta, you can have your counter : Double, and on each update, you increase counter by delta.

let counter : Double
counter += delta

Now that your "timer" is running properly, you can check with simple condition if your period of time already passed, or do whatever you want with it:

let SPAWN_OBJECT_AFTER : Double = 5.0
if counter > SPAWN_OBJECT_AFTER {

    // Do something on fire event
    self.spawn()

    // This line effectively restarts timer
    counter -= SPAWN_OBJECT_AFTER
}

You can easily build your own, very easy timer class to do it. Also! This way you have control over what happens in your update call, which is where the update logic belongs. Timer breaks that model by allowing method execution outside that - it might be intended, but usually is not).

I built a games running in production every day and this is I'd say most common solution for periodic events, as it saves the most resources when used appropriately. Obviously not fitting for everything but definitely fits your need.

Hope it helps!



回答3:

I don't believe there is a way to pause/resume a NSTimer in the way you are talking about. You must use timer.invalidate() and timer.fire(). However, perhaps you can use an int (that starts at 5 and goes down every second) to keep track of how many seconds the initial timer has before fires again and once the times fires again, make sure the new int value is passed to start the initial timer from the correct point in time.