7

Is there anyway to prevent SpriteKit from automatically unpausing a scene when entering foreground/becoming active?

I set paused = true and want it to remain so even when the app becomes active again after having been sent to the background.

I should add that I'm doing this in swift, though I would not have expected the behaviour to be different in this regard.

Pinxaton
  • 437
  • 4
  • 13
  • I'm not familiar with the frameworks, but wouldn't `paused = false` *unpause* the scene? – BergQuester Aug 17 '14 at 17:43
  • I meant paused = true - corrected – Pinxaton Aug 17 '14 at 17:53
  • If you pause your SKView with self.scene.view.paused = YES prior to entering background mode, it will remain paused when your app becomes active. Are you sure you are pausing your view correctly? – 0x141E Aug 17 '14 at 19:39
  • I'm fairly positive I'm doing it correctly. In the AppDelegate's applicationWillResignActive() function I'm simply doing 'scene.view.paused = true' (swift). If I do nothing else upon app becoming active I see that any SKActions that were paused are resumed and the update() function is still being called and doing it's thing. I would have expected that in the absence of me explicitly setting scene.view.paused to 'false', bring the app back into the foreground will keep the scene in a paused state but that's clearly not what is happening. – Pinxaton Aug 18 '14 at 11:19

5 Answers5

5

Not sure if it is the same in objective C, but in swift I had to "override" a callback function that SKView calls behind the scenes,

func CBApplicationDidBecomeActive()
{

}

This function was causing paused to be reset.

(note override keyword should not be applied)

In some cases where you want to just retain the state of pause, make a new variable instead and override the isPaused method.

class GameScene:SKScene
{
  var realPaused = false
  {
     didSet
     {
         isPaused = realPaused
     }
  }
  override var isPaused : Bool
  {
    get
    {
       return realPaused
    }
    set
    {
      //we do not want to use newValue because it is being set without our knowledge
      paused = realPaused
    }
  }
}
Yi Jiang
  • 49,435
  • 16
  • 136
  • 136
Knight0fDragon
  • 16,609
  • 2
  • 23
  • 44
  • Exactly how did you come by this secret function? And how did you implement it? – mogelbuster Oct 28 '16 at 13:10
  • 1
    I found it by looking at stack traces, just plop the function in your GameScene class exactly as I have it, This will override the function being called from SKScene. The reason you do not use override is because we do not have access to this function via the header files, so we do not know it exists – Knight0fDragon Oct 28 '16 at 13:46
  • 1
    It didn't get called in the GameScene, but it did get called in an SKView extension and in an SKView subclass. It works just like you said, but this seems dangerous. I mean what else does this function do besides setting isPaused? I would like to call super's implementation, then reset the isPaused, but I couldn't figure out how to use super as we didn't technically override it. – mogelbuster Oct 28 '16 at 18:23
  • no idea what else it could be doing, but technically it should not be doing anything. Yes, you are right SKView lol I even said that in my answer, don't know why my brain said GameScene. The other option you can do is override the isPaused variable on GameScene, and keep another variable called isRealPaused that tracks it when you want to track it – Knight0fDragon Oct 28 '16 at 18:28
  • sorry, @mogelbuster forgot to tag you, not sure if you get notified being the last sender – Knight0fDragon Oct 28 '16 at 18:35
5

I made some adaptions to the solution from Knight0fDragon to make it work for me. This makes it so that isPaused will always be equal to realPaused. In order to pause the game, the "realPaused" variable should then only be altered, which changes automatically also the isPaused variable.

var realPaused: Bool = false {
    didSet {
        self.isPaused = realPaused
    }
}
override var isPaused: Bool {
    didSet {
        if (self.isPaused != self.realPaused) {
            self.isPaused = self.realPaused
        }
    }
}

Unfortunately, this will prevent the scene from pausing when the application is running in the background. In order to prevent that, I changed the condition to: "self.isPaused != self.realPaused && self.isPaused == false" so that the scene will still pause automatically when the app is put to the background but will only re-actiate if realPaused is also true:

var realPaused: Bool = false {
    didSet {
        self.isPaused = realPaused
    }
}
override var isPaused: Bool {
    didSet {
        if (self.isPaused == false && self.realPaused == true) {
            self.isPaused = true
        }
    }
}
  • I applied this to my subclasses for SKNodes, because they contained some SKActions that I didn't want to be triggered from isPaused==true and the scene remained unpaused. – Nick Greg Mar 20 '21 at 20:58
  • Great answers from you and @knight0fdragon. This seems to get around overriding `CBApplicationDidBecomeActive` as well. @pinxaton you should accept one of these answers if they helped. – muZero Jan 09 '22 at 23:12
3

this question may have been quite old, but I encountered the same problem today and I think I have found a pretty good solution to it:

In the AppDelegate, I do the following:

- (void)applicationDidBecomeActive:(UIApplication *)application 
{
    SKView *view = (SKView *)self.window.rootViewController.view;

    if ([view.scene isKindOfClass:[GameScene class]]) 
    {
        XGGameScene *scene = (GameScene *)view.scene;
        [scene resumeGame];
    }
}

And then in the GameScene class itself, I update a BOOL to reflect that the app has just resumed from background:

- (void)resumeGame
{
    //  Do whatever is necessary

    self.justResumedFromBackground = YES;
}

And finally, in the update: loop, I run the following:

- (void)update:(NSTimeInterval)currentTime
{
    // Other codes go here...

    // Check if just resumed from background.
    if (self.justResumedFromBackground) 
    {
        self.world.paused = YES;
        self.justResumedFromBackground = NO;
    }

    // Other codes go here...
}

Hope this helps!

Yi Jiang
  • 49,435
  • 16
  • 136
  • 136
2

Pinxaton you are right but you can paused application by adding a small delay

 (void)theAppIsActive:(NSNotification *)note 
{
    self.view.paused = YES;
       SKAction *pauseTimer= [SKAction sequence:@[
                                                [SKAction waitForDuration:0.1],
                                                [SKAction performSelector:@selector(pauseTimerfun)
                                                                 onTarget:self]

                                                ]];
    [self runAction:pauseTimer withKey:@"pauseTimer"];
}

-(void) pauseTimerfun
{
     self.view.paused = YES;


}
dragoneye
  • 703
  • 6
  • 14
1

You should make use of your app delegate, specifically the applicationDidBecomeActive method. In that method send a notification that your SpriteKit view listens for.

So in the applicationDidBecomeActive method your code should look something like this:

// Post a notification when the app becomes active
[[NSNotificationCenter defaultCenter] postNotificationName:@"appIsActive" object:nil];    

Now in your didMoveToView method in your SKScene file put the following:

// Add a listener that will respond to the notification sent from the above method
 [[NSNotificationCenter defaultCenter] addObserver:self 
                                          selector:@selector(theAppIsActive:) 
                                              name:@"appIsActive" object:nil];

Then just add this method to your SKScene file:

//The method called when the notification arrives
(void)theAppIsActive:(NSNotification *)note 
{
    self.view.paused = YES;
}
Scooter
  • 4,068
  • 4
  • 32
  • 47
  • Thanks for the suggestion. I tried this but it didn't seem to have the desired effect. When application becomes active, the scene pauses for a fraction of a second then resumes again. I think SpriteKit must unpause after the notification is received and handled, possibly in a different thread? – Pinxaton Aug 17 '14 at 18:47
  • Try changing self.view.paused to self.scene.view.paused. You could also simply set a bool to true in the above method. Then in your update method which fires once per frame simply check that bool to see if you should pause or not. – Scooter Aug 17 '14 at 19:41
  • Btw there is already `UIApplicationDidBecomeActiveNotification` so no need to post your own. – bio Jan 20 '16 at 12:00