Cocos2d Tip #2: Using CCTimer in Your iPhone Game

If you’re writing almost any type of game, from a puzzler to a FPS to an RTS, tracking time is critical element of the game play. (Except for Angry Birds. You can ponder an Angry Birds level until your iPhone battery runs dry without penalty.)

Cocos2d-iPhone provides several means for tracking time in your game and scheduling methods to be called at both regular heartbeats and at arbitrary points in the future. The easiest way to manage time in a Cocos2d-based game is to use a CCLayer’s scheduleUpdate or schedule methods. Both methods are explained nicely in the timers section of the  Cococ2d Best Practices guide. The guide also explains why you should try to avoid using iOS’s NSTimer class. (Your game will miss out on automatic pause and resume if do use NSTimer.)

But what if you can’t use scheduleUpdate or schedule in your game because you’re putting your game logic into a custom class instead of a CCLayer or CCSprite?

For the sake of simplicity and portability I don’t subclass CCSprite in my games. Instead, I create custom classes to represent my game objects and call update:(ccTime)delta on them from CCLayer objects to make stuff happen. I needed a nice timer class and started to write my own. About halfway though this project I ran into Cocos2d’s CCTimer. Since I’m trying to write as little code as possible I abandoned my timer. CCTimer is a nicely written, lightweight timer class that uses NSInvoker to create callbacks and still integrates will with the rest of the Cocos2d-iPhone framework.

Here’s how it use it…

First, I store a reference to a timer in my non-Cocos2d class:

// Zombie.h
#import "cocos2d.h"
@interface Zombie : NSObject {
    BOOL _resurrectable;
    ccTime _resurrectionCountdown;
    CCTimer* _resurrectionTimer; // own
    float _rebirthPenalityPercent;
}
@property (readwrite, nonatomic, assign) BOOL resurrectable;
@property (readwrite, nonatomic, assign) ccTime resurrectionCountdown;
@property (readwrite, nonatomic, retain) CCTimer* resurrectionTimer; // own
@property (readwrite, nonatomic, assign) float rebirthPenalityPercent;
- (void) startResurrectionTimer;
- (void) stopResurrectionTimer;
@end
// Zombie.m
#import Zombie.h
@implementation Zombie
@synthesize resurrectable = _resurrectable;
@synthesize resurrectionCountdown = _resurrectionCountdown;
@synthesize resurrectionTimer = _resurrectionTimer;
@synthesize rebirthPenalityPercent = _rebirthPenalityPercent;
+ (id) getInstance {
    CCLOG(@"%@: %@", NSStringFromSelector(_cmd), self);
    return [[[self alloc] init] autorelease];
}

- (id) init: {
    CCLOG(@"** %@: %@", NSStringFromSelector(_cmd), self);
    if ((self = [super init])) {
        self.resurrectable = YES;
        self.resurrectionCountdown = 30.0f; // half a second
        self.resurrectionTimer = nil;
        self.rebirthPenalityPercent = 0.10f; // 10% less health
    }
}
@end

Second, I define a call back method to stop the timer:

// later on in Zombie.m
- (void) stopResurrectionTimer {
    CCLOG(@"%@: %@", NSStringFromSelector(_cmd), self);
    if (self.resurrectionTimer != nil) {
    // the only way stop a timer is to deallocate it
        [_resurrectionTimer release];
        _resurrectionTimer = nil;
    }
    // do stuff when the zombie dies...
}

Third, I define a method to start the timer:

- (void) startResurrectionTimer {
    CCLOG(@"%@: %@", NSStringFromSelector(_cmd), self);
    if (self.resurrectionTimer != nil) {
         // if there is an old resurrectionTimer then deallocate it
         // (this is the only way to stop a timer)
        [_resurrectionTimer release];
        _resurrectionTimer = nil;
    }
    //  allocate a new timer with a built-in factory method
    // the stopResurrectionTimer method is called when
    // the time is up!
    self.resurrectionTimer = [CCTimer timerWithTarget:self 
                                             selector:@selector(stopResurrectionTimer)
                                             interval:self.resurrectionCountdown];
}

Fourth, I add a call to update the timer in my CCLayer’s update method:

// ZombieGameLayer.m
- (void) update:(ccTime)delta {
    [self.zombie.ressurrectionTimer update:delta];
}

Finally, Somewhere deep in my game logic I start the timer at the right dramatic moment.

// somewhere else in Zombie.m
- (void) die {
    // call when killed
     CCLOG(@"%@: %@", NSStringFromSelector(_cmd), self);

    // if resurrectable start resurrectionTimer
    if (self.resurrectable) {
        [self startResurrectionTimer];
    }
}

It’s important to note that at the time of this blogging, the CCTimer documentation claims the interval value used to determine the length is in seconds, but really it’s in milliseconds (that is 1/60 of a second). If want your timer’s call back to run in 2 minutes set the CCTimer interval to 160.0f.

It’s also important to note that CCTimer is independent of frame rate: This might be obvious but sometime you want to execute an action every frame an sometime you want to execute it every few milliseconds.

If things aren’t working make sure you’ve told your layer to schedule updates in it’s init method and make sure you are updating your timers from your layer 🙂


Posted

in

,

by

Comments

5 responses to “Cocos2d Tip #2: Using CCTimer in Your iPhone Game”

  1. Zac Avatar
    Zac

    A millisecond is not 1/60th of a second…

  2. ACE Avatar

    something wrong , i catch..
    call when killed, need stopResurrectionTimer , not startResurrectionTimer.

  3. kusichan Avatar
    kusichan

    Actually, the interval value is in seconds. At least in my Cocos version. I also have had some troubles with the timer initialisation and used

    _activityTimer = [CCTimer alloc];
    [_activityTimer initWithTarget:self selector:@selector(stopActivityTimer) interval:_activityDuration repeat:1 delay:0];

  4. Sir-j Avatar

    Why don’t just use [self schedule:@selector(backTimer:) interval:0.5]; ?