SlippyFlippy 1.1: How to make a Countdown Timer in Sprite Kit.

icon2_120
SlippyFlippyPenguin

The next item on the update project list is making a countdown timer.  For those who haven’t read earlier episodes of this blog, there is a bug I cannot find. For a very small number of users, the game ends immediately when starting in hard mode. This might just be a user problem not moving the penguin fast enough, which I did get some reports from users. To solve that, we’ll add a three-second delay between pressing the button to start the game and running the game. For feedback during that time,  I am building a countdown timer.  I tried to do this in a method to drop directly into what we already have, but found nothing worked.  The problem is update:  and the frame cycle.

As you can find in Apple’s Sprite Kit Programming Guide there is a cycle of methods every frame. As developers, we can override three methods that update the frame in different ways.  The first is update:, where a lot of the actions take place. Actions are then evaluated by the system. Next, didEvaluateActions: allows us to handle the scene after the action for that frame.  The physics engine then kicks in. The evaluations of the physics engine can then be handled just before Sprite Kit renders the scene in the view.

I tried making a few methods that would run the countdown timer using SKAction. The above cycling caused them not to work: They dumped all the timing labels in a few frames, piled on top of each other.  In order to have both a timer and changes to the label with the countdown, we have to put our code in update:  and not out of it. Essentially, we will update the label with the current time, which conveniently is update: ‘s parameter.

I made a playbox to look at this without distractions. Starting with a new Sprite Kit template, I cleaned out the spaceship, and “Hello World” label.

I’ll add three instance variables:

  •  SKLabelNode,for the timer display
  •  BOOL flag, to let us know to start the countdown
  •  NSTimeInterval ,to let us have a start time.

Add this to the code:

@implementation SLMyScene{
SKLabelNode *countDown;
BOOL startGamePlay;
NSTimeInterval startTime;
}

Add to initWithSize: the following to make the label:

countDown = [SKLabelNode labelNodeWithFontNamed:"Futura-Medium"];
countDown.fontSize = 70;
countDown.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMaxY(self.frame)*0.85);
countDown.verticalAlignment =SKVerticalAlignmentCenter;
countDown.fontColor = [SKColor whiteColor ];
countDown.name = @"countDown";
countDown.zPosition = 100;
[self addChild:countDown];

Add the following to update:

countDown.text = [NSString stringWithFormat:@"%i", (int)currentTime];

This casts current time as an integer, and then prints it as one. Now when we run, we get the system time and date in seconds.

Let’s expand on this. For the playbox, we will set reset the timer to 0 when the user touches the label. This is a simple way to make a label into a button BTW.

Set up touchesBegan: to toggle the flag

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
     for (UITouch *touch in touches) {
          CGPoint location = [touch locationInNode:self];
          if (CGRectContainsPoint(countDown.frame, location)){
               startGamePlay = YES;
          }
     }
}

Then change update: to the following:

//reset counter if starting
if (startGamePlay){
     startTime = currentTime;
     startGamePlay = NO;
}
countDown.text = [NSString stringWithFormat:"%i", (int)(currentTime-startTime)];

Build and run. Now when we tap the timer, it resets to 0.Screenshot 2014-04-10 06.17.03

We are still counting up. Let’s count down from 5. Modify update this way:

int countDownInt = 5.0 -(int)(currentTime-startTime);
countDown.text = [NSString stringWithFormat:@"%i", countDownInt];

Build and run. That will give us negative numbers, which we don’t want. Modify update: again to only show positive numbers. When we get a negative, stop counting.

int countDownInt = 5.0 -(int)(currentTime-startTime);
if(countDownInt>0){ //if counting down to 0 show counter
     countDown.text = [NSString stringWithFormat:@"%i", countDownInt];
}else { //if not show message, dismiss, whatever you need to do.
     countDown.text=@"GO!";
}

Screenshot 2014-04-10 06.20.04And that works!! I then place this code into SlippyFlippy penguin. I made a few adjustments to fit into SlippyFlippy. I  set the timer to three seconds, and aligned the timer and flash labels a bit better.  It works great — with two exceptions.

The flash labels are slightly annoying and clunky looking when they show up. I Need to set timing better on when to show them, and I need a method to remove them from the screen if we get a game over status while they are still showing.Screenshot 2014-04-10 06.20.36

Then I try to test the hard mode. The bug which I couldn’t find is now very front and center. If a user taps the screen after hitting the hard mode but before the penguin appears, the game ends. Next time, we begin our search and destroy mission for The Bug.

The playbox code for the countdown timer:

//
//  SLMyScene.m
//  CountdownTimer
//
//  Created by Steven Lipton on 4/8/14.
//  Copyright (c) 2014 Steven Lipton. All rights reserved.
//

#import "SLMyScene.h"

@implementation SLMyScene{
    SKLabelNode *countDown;
    BOOL startGamePlay;
    NSTimeInterval startTime;
}

-(id)initWithSize:(CGSize)size {    
    if (self = [super initWithSize:size]) {
        /* Setup your scene here */
        
        //Make a label for the timer
        self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
        countDown = [SKLabelNode labelNodeWithFontNamed:@"Futura-Medium"];
        countDown.fontSize = 50;
        countDown.position = CGPointMake(CGRectGetMidX(self.frame),
                                             CGRectGetMaxY(self.frame)*0.85);
        countDown.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter; //good for positioning with other sprites
        countDown.fontColor = [SKColor whiteColor ];
        countDown.name = @"countDown";
        countDown.zPosition = 100;
        [self addChild:countDown];
    }
    return self;
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    /* Called when a touch begins */
    
    for (UITouch *touch in touches) {
        CGPoint location = [touch locationInNode:self];
        if (CGRectContainsPoint(countDown.frame, location)){
            startGamePlay = YES;
        }
    }
}

-(void)update:(CFTimeInterval)currentTime {
    /* Called before each frame is rendered */
    /*code for beginning animation before game play ,and the start game */
    
    //reset counter if starting
    if (startGamePlay){
        startTime = currentTime;
        startGamePlay = NO;
    }
    
    int countDownInt = (int)(currentTime-startTime);
    if(countDownInt>0){  //if counting down to 0 show counter
        countDown.text = [NSString stringWithFormat:@"%i", countDownInt];
    }else { //if not show message, dismiss, whatever you need to do.
        countDown.text=@"GO!";
        /* code here runs every frame -- 
         Try not to put animations here, unless
         you can fire them once only or fire them through good timing.
         */
    }
}

@end

12 thoughts on “SlippyFlippy 1.1: How to make a Countdown Timer in Sprite Kit.”

    1. One way would be to use a flag. I was going to cover that, but Swift got my attention, though check out the stopwatch code in my “life without storyboards” series which does the same thing essentially. Initialize a Bool as a flag with NO. the in touchesBegan: set it to true. in update: only advance your clock when the flag is true.

  1. Thank you I tried and It works good!
    I have a question.
    I am developing a game and I want to add a few seconds (lets say 2 seconds)to countDownInt when each time the player node contacts with enemy node. How do I call this method and where. In the update method or didBeginContact ?
    Thank you again

  2. It works. Thank you,
    I have a question. I am developing a game. Each time when the player node and the enemy node contacts I want to add a few seconds (lets say 2 seconds) to countdown timer.
    How and where do I call this method? In the update or didBeginContact?

    1. If you have a existent timer doing a countdown for something else, yes at contact you can add that to your count down variable whihc should be doing the countdown at update. If you are making a simple delay of a countdown just for the contact (for example an enemy freezes a player temporarily) set a flag in the contact handlers and do the countdown itself in update.

      The key is keeping timing events coordinated in update. If you don’t you will get some strange behavior.

  3. Thank you, this helped. But I can’t figure out how to reset the timer when it reaches, maybe a certain score number. Please show how the code looks like.

    1. In the whole code, see lines 51-63. Line 58 is the conditional that stops the timer when when we reach zero. You can change that conditional to whatever you are testing for, depending if you want to go up or down. We use the startGamePlay flag to reset the values.

      You might also check out this and this for more on timers.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s