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 🙂
Comments
5 responses to “Cocos2d Tip #2: Using CCTimer in Your iPhone Game”
A millisecond is not 1/60th of a second…
Thanks.
something wrong , i catch..
call when killed, need stopResurrectionTimer , not startResurrectionTimer.
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];
Why don’t just use [self schedule:@selector(backTimer:) interval:0.5]; ?