2

I've been searching for a solution to pause my SpriteKit game when the user "tabs down" the game. So far I found a solution where you use SKAction's instead of NSTimer's, this works as long as the time between actions stays the same. However, my NSTimer's changes in speed. So I need to find another solution.

I have a bunch of NSTimer's located in GameScene -> didMoveToView

NSTimer.scheduledTimerWithTimeInterval(0.2, target: self, selector: Selector("SpawnBullets"), userInfo: nil, repeats: true)

NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("SpawnMeteors"), userInfo: nil, repeats: true)

NSTimer.scheduledTimerWithTimeInterval(timeInterval, target: self, selector: Selector("onTimer:"), userInfo: nil, repeats: true)

Now, how would I simply pause them when the app enters background?

EDIT: Added my timeInterval increase-speed-function

func onTimer(timer: NSTimer) {
    var goodTimes = time / 20

    if (goodTimes > 1.8){
        goodTimes = 1.8
    }

    timer.fireDate = timer.fireDate.dateByAddingTimeInterval(timeInterval - goodTimes)

    self.runAction(SKAction.sequence([SKAction.runBlock(SpawnRocks), SKAction.waitForDuration(goodTimes / 2), SKAction.runBlock(SpawnPowerUp)]))
}
Peter Hornsby
  • 4,208
  • 1
  • 25
  • 44
Glutch
  • 662
  • 1
  • 6
  • 19
  • 2
    What do you mean by "my `NSTimer` changes in speed?" – 0x141E Dec 20 '15 at 19:36
  • Sorry, i added my function for clarification. Also, the "time" variable increases by 1 every second (in another function) – Glutch Dec 20 '15 at 23:52
  • I expect [this answer](http://stackoverflow.com/questions/347219/how-can-i-programmatically-pause-an-nstimer) will get you most of the way there. – TwoStraws Dec 20 '15 at 23:53
  • 1
    You can implement the same functionality with a `waitForDuration` and `runAction` SKAction sequence, where the `runAction` block calls `onTimer`. The difference is you will need to create/run a new action sequence (wait/runAction) each time `onTimer` is called. – 0x141E Dec 21 '15 at 01:41
  • @TwoStraws This seems to be Objective-C right? I would greatly appreciate an example in swift :) @ 0x141E Do you think this guide is up to date and working using SKActions? http://blog.adambardon.com/how-to-pause-spritekit-game-in-swift/ (Also including for future readers having the same problem) – Glutch Dec 21 '15 at 08:51
  • @Glutch: That blog post looks perfect to me. – TwoStraws Dec 21 '15 at 09:20
  • @TwoStraws Awesome. Is the SKAction the best way to go efficiency-wise? It seems hard on the fps to recreate the runAction every time, maybe this is a dumb question. I really appreciate your help! – Glutch Dec 21 '15 at 10:18
  • 1
    I suspect an `SKAction` timer approach will have little or no effect on FSP compared to an `NSTimer` implementation. Using an `SKAction` timer will 1) pause/resume appropriately when you pause/resume the scene or view, 2) automatically pause/resume when you press the home button and relaunch your app, and 3) automatically stop and release memory when you transition to a new scene. Alternatively, you will need to manually invalidate each `NSTimer` when you transition to a new game level. – 0x141E Dec 22 '15 at 02:36

2 Answers2

3

Pausing of NSTimer is not a native feature in objective-c or swift. To combat this, you need to create an extension, which I happen to have created and will share for you. This will work for both OS X and iOS

import Foundation
import ObjectiveC

#if os(iOS)
    import UIKit
#else
    import AppKit
#endif


private var pauseStartKey:UInt8 = 0;
private var previousFireDateKey:UInt8 = 0;

extension NSTimer
{
    private var pauseStart: NSDate!{
        get{
            return objc_getAssociatedObject(self, &pauseStartKey) as? NSDate;

        }
        set(newValue)
        {
            objc_setAssociatedObject(self, &pauseStartKey,newValue,objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN);
        }
    }

    private var previousFireDate: NSDate!{
        get{
            return objc_getAssociatedObject(self, &previousFireDateKey) as? NSDate;

        }
        set(newValue)
        {
            objc_setAssociatedObject(self, &previousFireDateKey,newValue,objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN);
        }
    }


    func pause()
    {
        pauseStart = NSDate();
        previousFireDate = self.fireDate;
        self.fireDate = NSDate.distantFuture() ;
    }

    func resume()
    {
        if(pauseStart != nil)
        {
            let pauseTime = -1 * pauseStart.timeIntervalSinceNow;
            let date = NSDate(timeInterval:pauseTime, sinceDate:previousFireDate );
            self.fireDate = date;
        }

    }
}

Then when you need to use it, simply call timer.pause() and timer.resume() You of course have to keep track of your timers in your gamescene object to do this, so make sure that timer is a variable accessible for the entire class, and when making your timer, you do timer = NSTimer.schedule...

At the beginning of your class:

var spawnBulletsTimer : NSTimer?;
var spawnMeteorsTimer : NSTimer?;
var onTimer: NSTimer?;

When creating the timers:

spawnBulletsTimer = NSTimer.scheduledTimerWithTimeInterval(0.2, target:     self, selector: Selector("SpawnBullets"), userInfo: nil, repeats: true)

spawnMeteorsTimer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("SpawnMeteors"), userInfo: nil, repeats: true)

onTimer = NSTimer.scheduledTimerWithTimeInterval(timeInterval, target: self, selector: Selector("onTimer:"), userInfo: nil, repeats: true)

Then when you need to pause:

onTimer?.pause() 
spawnBulletsTimer?.pause()
spawnMeteorTimer?.pause()

Then when you need to resume:

onTimer?.resume() 
spawnBulletsTimer?.resume()
spawnMeteorTimer?.resume()
Knight0fDragon
  • 16,609
  • 2
  • 23
  • 44
  • Hello (again) Knight0fDragon! :) I'm struggling a tiny bit with this. "make sure that timer is a variable accessible for the entire class". So i put timer inside "class GameScene: SKScene". But what should it be equal to? I tried timer = NSTimer(). But that didn't seem to be the right choice. – Glutch Dec 21 '15 at 19:45
  • I have added everything, no errors showing up, i see nothing strange except xcode forcing me to put either "!" or "?" at onTimer"?".pause(), spawnBulletsTimer"?".pause(). It doesn't seem to work though, the when i re-open the game theres a lot of meteors spawning. Can i upload my code somewhere and you take a look? – Glutch Dec 21 '15 at 20:16
  • 1
    the ? is what you want, sorry I write in many languages and I always leave out the optional symbols. I would assume a lot of meteors happen, 10 a second in fact,if you are getting more than this, then you are creating more timers than you should be – Knight0fDragon Dec 21 '15 at 20:35
  • Thanks for the update, however, it doesn't work. It still spawns a bunch of meteors when the game returns to foreground. I see no errors or such, as i said in my earlier comment. The 0.1 timer was just an example, in reality its much less frequent :') Edit for clarification. My meteors spawns with my onTimer function (SpawnRocks). They spawn approx 1 each second. When i enter background, and re-enters foreground after a while, theres a huge amount of Meteors(Rocks) in my game, so the timers did in fact not stop running – Glutch Dec 21 '15 at 20:39
  • check in with me in 2 hours, I will be able to look at your code then – Knight0fDragon Dec 21 '15 at 20:53
  • Should the code be added to AppDelegate.swift or GameScene.swift? I found some interesting functions in AppDelegate.swift and moved the code there. Doesnt seem to work though :D – Glutch Dec 21 '15 at 23:05
  • 1
    What code? The extension? Make it its own swift file – Knight0fDragon Dec 21 '15 at 23:17
  • Okey, haha! It all works now. Huge thanks, i appreciate your patience – Glutch Dec 21 '15 at 23:22
0

Thanks @KnightOfDragon code, here's a more modern swift version:

import Foundation

private var pauseStartKey:UInt8 = 0;
private var previousFireDateKey:UInt8 = 0;

extension Timer{
    
    private var pauseStart: Date!{
        
        get{
            
            return objc_getAssociatedObject(self, &pauseStartKey) as? Date
        }
        
        set(newValue){
            
            objc_setAssociatedObject(self, &pauseStartKey,newValue,objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
        }
    }

    private var previousFireDate: Date! {
        
        get{
            
            return objc_getAssociatedObject(self, &previousFireDateKey) as? Date
        }
        
        set(newValue){
            
            objc_setAssociatedObject(self, &previousFireDateKey,newValue,objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
        }
    }


    func pause() {
        
        pauseStart = Date()
        previousFireDate = fireDate
        fireDate = Date.distantFuture
    }

    func resume() {
        
        if pauseStart != nil {
            
            let pauseTime = -1 * pauseStart.timeIntervalSinceNow;
            let date = Date(timeInterval: pauseTime, since: previousFireDate)
            fireDate = date;
        }
    }
}
Bejil
  • 412
  • 5
  • 18