3

I have a paused SKScene. When the user asks to do so, I would like to resume the scene. However, I would like to give the user a couple of seconds to prepare before the game begins. To do so, when the user asks to resume the game, I would like to first countdown from 3, and then resume the scene.

I currently have a SKLabel to indicate the count. When the user clicks on resume, I use an NSTimer to countdown from 3, updating the contents of the label every second, and resuming the game when the count is up.

However, because the game is paused, the SKLabel doesn't update every second; it only updates once at the very end, once the game is resumed. I am looking for a way around this.

Farhad
  • 516
  • 3
  • 14
  • About possible issues when using NSTimer with SpriteKit http://stackoverflow.com/q/23978209/3402095 – Whirlwind Aug 26 '15 at 09:25
  • @Whirlwind That's not an issue in this case; it's *the reason* why I'm using a `NSTimer` instead of a `SKAction`. I *need* the method to ignore the game's paused state. – Farhad Aug 26 '15 at 21:16
  • I just said "possible" to make you aware of issues coming along with it. Generally using NSTimer with SpriteKit is kind of a bad idea. Search SO to see why (NSTimers are also related to memory leaks). Anyways, to do this, you can un-pause the scene, and delay game start by using SKAction with completion block (or just use sequence)... In that completion block, change the value of some custom variable (isGamePaused like suggested) and start the game when countdown is ended. – Whirlwind Aug 26 '15 at 21:30
  • Also, properly invalidating of timer will save you from potential leaking, but still, SKActions are safer way IMO. – Whirlwind Aug 26 '15 at 21:43

4 Answers4

0

Use a common variable in your GameScene that indicates if the game is paused, for example isGamePaused. In your update: method, you will have:

if(!isGamePaused){
    //Do all game logic
}

You can use isGamePaused to pause and unpause the game. Now, let's make the countdown. I would make a subclass of SKNode, add the SKLabel there and set a delegate so we know when the CountDown finished. For example, in CountDown.h:

@protocol SKCountDownDelegate <NSObject>

-(void)countDown:(id)countDown didFinishCounting:(BOOL) didFinishCounting;

@end

@interface SKCountDown : SKNode

@property (assign, atomic) id<SKCountDownDelegate> delegate;

-(instancetype)initCountDownWithSeconds:(int)seconds andColor:(UIColor *) color andFontName:(NSString *)fontName;

@end

And in CountDown.m:

#import "SKCountDown.h"

@interface SKCountDown()

@property int secondsToGo;
@property SKLabelNode *numberLabel;
@property NSTimer *countTimer;

@end

@implementation SKCountDown

-(instancetype)initCountDownWithSeconds:(int)seconds andColor:(UIColor *)color andFontName:(NSString *)fontName{
    if (self = [super init]) {
        self.numberLabel = [[SKLabelNode alloc] initWithFontNamed:fontName];
        [self.numberLabel setFontColor:color];
        [self.numberLabel setFontSize:110];
        [self.numberLabel setText:@"3"];
        self.secondsToGo = seconds;

        [self addChild:self.numberLabel];

        self.countTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(count) userInfo:nil repeats:YES];
    }
    return self;
}

-(void)count{
    if (self.secondsToGo > 1) {
        self.secondsToGo -= 1;
        self.numberLabel.text = [NSString stringWithFormat:@"%i", self.secondsToGo];
    }else{
        [self.countTimer invalidate];
        self.countTimer = nil;
        self.numberLabel.text = @"GO!";
        [self performSelector:@selector(finish) withObject:nil afterDelay:1];
    }
}

-(void)finish{
    [self removeFromParent];
    [self.delegate countDown:self didFinishCounting:YES];
}

@end

So, anywhere you want to add this CountDown, you can do the following:

-(void)startCountDown{
    self.countDown = [[SKCountDown alloc] initCountDownWithSeconds:3 andColor:self.countdownFontColor andFontName:self.countdownFontName];
    self.countDown.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
    self.countDown.delegate = self;
    self.countDown.zPosition = 20;
    [self addChild:self.countDown];
}

-(void)countDown:(id)countDown didFinishCounting:(BOOL)didFinishCounting{
    isGamePaused = NO;
}

This is an example of a way to do it. Hope it helps!

aramusss
  • 2,391
  • 20
  • 30
0

Pausing an entire SKScene during gameplay seems a bit too blunt for me, because you typically want to update something in the node hierarchy.

A simple solution would be to make a distinction between game node and UI nodes. You can simply add two nodes (game and ui) to the scene and whenever the scene would call [self addChild] choose which node to add them to. Later, you can pause the game node and still be able to update the UI.

SKScene
   - SKNode "game" // <-- pause this one
       - SKSpriteNode "goodGuy"
       - SKSpriteNode "badGuy"
       - SKSpriteNode "mehGuy"
       - ...
   - SKNode "ui"
       - SKLabelNode "countdownLabel"
       - SKSpriteNode "resumeButton"
       - SKSpriteNode "pauseButton"
       - ...

Do take note that the paused property only determines if actions are processed on the node and its descendants. If you are performing physics this will not suffice. Pausing physics can be done by pausing the SKView but that is definitely not what you're looking for. You can always try setting the speed of the physicsWorld to 0 (I haven't tested this, though).

CloakedEddy
  • 1,965
  • 15
  • 27
0

I prefer to simply pause sections of the update method instead of pausing the whole scene. Create a BOOL property:

@property (nonatomic) BOOL runUpdateMethod;

Structure your update method to determine what pieces get paused:

-(void)update {

    if(runUpdateMethod) {

        // code to be paused
    }

    // code not to be paused
}

You can use a SKAction block to run a countdown and resume regular updates:

-(void)countdown {

    __block SKLabelNode *labelNode0;

    SKAction *wait0 = [SKAction waitForDuration:1.0];

    SKAction *block0 = [SKAction runBlock:^{

        labelNode0 = [SKLabelNode labelNodeWithFontNamed:@"Arial"];
        labelNode0.text = @"5";
        labelNode0.fontSize = 100;
        labelNode0.fontColor = [SKColor redColor];
        labelNode0.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;
        labelNode0.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;
        labelNode0.position = CGPointMake(self.screenWidth/2, self.screenHeight/2);
        labelNode0.zPosition = 955;
        labelNode0.alpha = 0.4;
        [self addChild:labelNode0];
    }];

    SKAction *block1 = [SKAction runBlock:^{
        labelNode0.text = @"4";
    }];

    SKAction *block2 = [SKAction runBlock:^{
        labelNode0.text = @"3";
    }];

    SKAction *block3 = [SKAction runBlock:^{
        labelNode0.text = @"2";
    }];

    SKAction *block4 = [SKAction runBlock:^{
        labelNode0.text = @"1";
    }];

    SKAction *block5 = [SKAction runBlock:^{
        [labelNode0 removeFromParent];
        self.runUpdateMethod = YES;
    }];

    [self runAction:[SKAction sequence:@[block0, wait0, block1, wait0, block2, wait0, block3, wait0, block4, wait0, block5]]];
}
sangony
  • 11,636
  • 4
  • 39
  • 55
0

Instead of pausing the game, I set SKScene's speed property to 0, along with setting SKPhysicsWorld's speed property to 0. I also had to modify my update function slightly by only executing certain portions of the code if the game isn't paused.

This created the same effect as the entire game being paused, while also allowing me to update the content of labels.

Farhad
  • 516
  • 3
  • 14