Cocos2d Tip#3: Making Your iPhone Game Fast

My first iPhone game, that might actually make it to the App Store, is just about done. (Not done done but almost ready for testing and tweaking.)

With the idea that other people beside me might actually play my game I’ve started to do a very dangerous and high risk activity: Optimization!

The all the famous Computer Scientists and Software Architects agree: Optimizing your program is a necessary evil, like the death penalty or bug tracking systems, and to be avoided at all costs.

Well, that’s not quite true. It’s premature optimization that is the bad guy here. Don’t start out programming with performance or scalability in mind, least you optimize code that doesn’t need it and miss optimizing code that really needs it. (It’s the same for bug tracking systems–don’t put all your junk in Jira or Bugzilla–just the verifiable, repeatable bugs that you might actually fix one day.)

When I started my game I knew my ideas were going to evolve and I didn’t want to hard code myself into a corner. I consciously wrote code that I knew would be slow but flexible.

For example I used expensive string comparisons instead of testing for cheap numeric constants. During the initial development phases of my game the strings were self-documenting (I could read them like little notes to myself) and easy to change. Later, when my game solidified and the string comparison were easy to find using Xcode’s Find in Project command (which is now Xcode 4’s Find in Workspace command)…

// before optimization
if ([goodGuy.role isEqualToString(@"tank") {
   // aggro something
} else if ([goodGuy.role isEqualToString(@"healer") {
   // fix something
}
// after optimization
switch (goodGuy.role) {
   case CharacterRole_tank:
      // aggro something
      break;
   case CharacterRole_healer:
      // fix something
      break;
   default:
      // oops! This should not happen
      CCLOG(@"unknown character role in %@",NSStringFromSelector(_cmd));
      break;
}

Changing the type of Character’s role property from NSString* to CharacterRole (a typedef’ed int) sped up my game in update methods which were called with every frame change. It was a pain to add all the constants but at least I only had to do it once–even though my conception of a Character and a role changed quite a bit.

// Character.h
typedef enum {
    CharacterRole_tank = 0,
    CharacterRole_healer = 1,
    CharacterRole_melee = 2,
    CharacterRole_ranged = 3,
    CharacterRole_boss = 4,
    CharacterRole_add1 = 5,
    CharacterRole_add2 = 6,
} CharacterRole;

// Character.m
@interface Character : NSObject  {
   NSString* _name;
   CharacterRole _role;
   // ...
}
@property (readwrite, copy, nonatomic) NSString* name;
@property (readwrite, assign, nonatomic) CharacterRole role;

Another big optimization I did was to replace for-loops and with Cocos2d’s fast enumeration macros…

// before optimization
Character* goodGuy = nil;
for (int i = 0; i < self.goodGuys.count; i++) {
   goodGuy = [self.goodGuys objectAtIndex:i];
   // do something with good guy
}

// after optimization
Character* goodGuy = nil;
CCARRAY_FOREACH(self.goodGuys, goodGuy) {
   // do something with good guy
}

My list of good guys changes during the game so I stored them in a CCArray–Cocos2d’s version of a NSMutuableArray. CCARRAY_FOREACH is a Cocos2d macro supercharged for fast array access. I could have used the Objective-C version of a fast numerator but I like to use as much of Cocos2d as I can. That way when I port my game to Cocos2d-x (C++ cross-platform version) it will be less work 🙂

One of the biggest optimizations I did was to upgrade from Cocos2d 0.99.5-rc1 to 1.0.0-rc. You have to be smart about when you upgrade to a new version of a framework. I don’t agree with the idea of living on the bleeding edge–stability of the platform allows me to focus on my own bugs!

Upgrading from Xcode 3 to Xcode 4 also seemed to help, or at least not hurt, performance. The new version of Cocos2d seems to work better with Xcode 4. I’m still getting used to the shiny new Xcode–it acts weird and slow on my Mac Mini.

To make framework and Xcode upgrades easy I put very little code into my app delegate class. All I have to do is to comment out…

//[[CCDirector sharedDirector] runWithScene: [HelloWorldLayer scene]];

and replace it with…

[[CCDirector sharedDirector] runWithScene: [HomeScreen scene]];

There are lots more optimization I can do if my performance isn’t where I want it to be: Unroll my loops and get rid of Character lists altogether. But I’ll lose a lot of abstraction and flexibility so I’m not going to optimize anything more unless my beta testers complain!

 

 


Posted

in

,

by