Feel free to fix/add documentation to the wiki

cocos2d Best Practices

Improving performance

Xcode Thumb

Turn off thumb compilation in XCode for ARMv6, but turn it on for ARMv7.

  • Thumb compiling creates smaller compiled sizes at the threat to floating point math
  • Thumb code is MUCH slower than the non-thumb code
  • Instructions to change this setting on a project can be found here.

CCDirector

Director:

  • Use DisplayLink. DisplayLink is the best Director, but is only available in SDK 3.1 or higher. If DisplayLink is not present, use MainLoop or ThreadMainLoop.
// must be called before any other call to the director
if( ! [CCDirector setDirectorType:kCCDirectorTypeDisplayLink] )
	[CCDirector setDirectorType:kCCDirectorTypeMainLoop];
  • Or use NSTimer CCDirector with a very low (quick) interval like 1/240
// If you are using "NSTimer" Director you could set a very low interval
[[CCDirector sharedDirector] setAnimationInterval:1/240.0];

Texture Atlas

When possible try to use texture atlas:

  • Make CCSprite from a corresponding CCSpriteSheet spritesheet object.
  • Use CCBitmapFontAtlas or CCLabelAtlas instead of CCLabel
  • Use CCTMXTileMap or CCTileMapAtlas to render tiles

The Atlas versions of these objects provide faster rendering at the expense of code and art complexity. The Atlas versions utilize an AtlasManager that keeps one large image with multiple frames, and the individual Atlas object refer to a frame of that larger image. This saves on texture counts and accelerates OpenGL ES calls.

Atlas = a book of illustration or diagrams. In this context it means you have one big image loaded into an opengl texture which actually holds a series of smaller images. As opposed to all the smaller images each loaded in to their own texture.

Textures

When possible, try to use 2-bit, 4-bit or 16-bit textures

  • 16-bit textures for PNG/GIF/BMP/TIFF images
[CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGBA4444]; // add this line at the very beginning
  • 4-bit or 2-bit textures: Try to use PVRTC textures.
CCSprite *sprite = [CCSprite spriteWithFile: @"sprite.pvr"];
  • Use 32-bit textures as the last resort
  • It is Possible to use 8-bit textures with kCCTexture2DPixelFormat_A8, but it is only valid for grayscale images. Although they are faster than 16-bit textures in 1st and 2nd gen devices, they are MUCH slower than 32-bit textures in 3rd gen devices and on the iPad

Particles

* There are 2 type of particles: Quad and Point particle system. * Point particle system seems to be a little bit faster on 1st and 2nd gen devices, but it is much slower on 3rd gen devices / iPad.

So, either you can check the device at runtime, or, the “lazy” approach would be to use Quad Particle.

Reducing Memory

  • Use 16-bit or 4-bit textures (see Improving performance)
  • Use the CCTextureCache
    • CCTextureCache caches all images
    • Even when the image is not used anymore, it will remain in memory
    • To remove it from memory do:
// textures with retain count 1 will be removed
// you can add this line in your scene#dealloc method
[[CCTextureCache sharedTextureCache] removeUnusedTextures]; // since v0.8
 
// removes a certain texture from the cache
CCTexture2D *texture = [sprite texture];
[[CCTextureCache sharedTextureCache] removeTexture: texture]]; // available in v0.7 too
 
// removes all textures... only use when you receive a memory warning signal
[[CCTextureCache sharedTextureCache] removeAllTextures];    // available in v0.7 too

Timers

  • Try NOT to use Cocoa’s NSTimer. Instead use cocos2d’s own scheduler.
  • If you use cocos2d scheduler, you will have:
    • automatic pause/resume.
    • when the CCLayer (CCScene, CCSprite, CCNode) enters the stage the timer will be automatically activated, and when it leaves the stage it will be automatically deactivated.
    • Your target/selector will be called with a delta time
/**********************************************************/
// OK OK OK OK OK
/**********************************************************/
-(id) init
{
    if( (self=[super init] ) ) {
        // schedule a callback
        [self scheduleUpdate];  // available since v0.99.3
        [self schedule: @selector(tick2:) interval:0.5];
    }
 
    return self;
}
 
-(void) update: (ccTime) dt
{
    // bla bla bla
}
 
-(void) tick2: (ccTime) dt
{
    // bla bla bla
}
 
/**********************************************************/
// BAD BAD BAD BAD
/**********************************************************/
// Why BAD ?
// You can't pause the game automatically.
-(void) onEnter
{
    [super onEnter];
    timer1 = [NSTimer scheduledTimerWithTimeInterval:1/FPS target:self selector:@selector(tick1) userInfo:nil repeats:YES];
    timer2 = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(tick2) userInfo:nil repeats:YES];
}
-(void) onExit
{
    [timer1 invalidate];
    [timer2 invalidate];
    [super onExit];
}
-(void) tick
{
    // bla bla bla
}
 
-(void) tick2
{
    // bla bla bla
}

Draw vs. update

  • try not to update any state variable inside the draw selector.
  • try not to draw anything inside a scheduled selector
  • Instead update the state variables inside a scheduled selector.
  • Instead draw everything inside the draw selector
  • If you update state variables inside the draw selector, the pause/resume won’t work as expected.
  • If you draw something inside a scheduled selector, it can’t be transformed
  • draw is called every frame
  • scheduled selectors can be called with any frame rate, but no more frequently than the application’s FPS rate.
/**********************************************************/
// OK OK OK OK OK
/**********************************************************/
-(void) draw
{
    [item draw];    // OK: DRAW INSIDE DRAW
}
-(void) update:(ccTime) dt
{
    item.position = dt * finalPosition; // OK, UPDATE STATE IN SCHEDULED SELECTOR
}
 
/**********************************************************/
// BAD BAD BAD BAD 1
/**********************************************************/
-(void) draw
{
    dt = [self calculateDelta];         // DONT UPDATE STATE IN DRAW.
    item.position = dt * finalPosition; // Pause won't work
 
    [item draw];
}
 
/**********************************************************/
// BAD BAD BAD BAD 2
/**********************************************************/
-(void) update:(ccTime) dt
{
    item.position = dt * finalPosition;
    [item draw];            // <--- DON'T DRAW IN SCHEDULED SELECTOR
    // because transformations won't alter your image
}

Director flow control

  • If possible try to use replaceScene instead of pushScene
  • pushScene is very handy, but it will put the pushed scene into memory, and memory is a precious resource in the iPhone.
// TRY TO AVOID A BIG STACK OF PUSHED SCENES
-(void) mainMenu()
{
    // etc
    [[CCDirector sharedDirector] pushScene: gameScene];
}
// stack:
//   . game  <-- running scene
//   . mainMenu
 
-(void) game
{
    [[CCDirector sharedDirector] pushScene: gameOverScene];
}
// stack:
//   . gameOver  <-- running scene
//   . game
//   . mainMenu
 
-(void) showGameOver
{
    [[CCDirector sharedDirector] pushScene: hiScoreScene];
}
// stack:
//   . scores  <-- running scene (4 pushed scenes... expensive)
//   . gameOver
//   . game
//   . mainMenu

Creating Nodes (CCSprite, CCLabel, etc..)

  • When possible try to create CCNode objects (CCSprite, CCLabel, CCLayer, etc) or any other kind of object in the init selector and not in the draw and any other scheduled selector
  • The creation of nodes might be expensive, so try to have them pre-created
  • On the other hand, be careful with the memory. Don’t have in memory unnecessary objects.
/**********************************************************/
// OK, MOST OF THE TIME
/**********************************************************/
-(id) init
{
    // etc...
 
    sprite1 = [CCSprite create];     // <-- USUALLY IT IS BETTER TO CREATE OBJECTS IN INIT
 
    // etc...
}
 
-(void) tick: (ccTime) dt
{
    // etc...
    if( someThing ) {
        [sprite1 show];         // <--- BUT IF YOU DON'T USE THEM FREQUENTLY, MEMORY IS WASTED
    }
}
 
/**********************************************************/
// BAD, MOST OF THE TIME
/**********************************************************/
-(void) tick: (ccTime) dt
{
    // etc...
    if( someThing ) {
        sprite = [CCSprite create];      // <--- EXPENSIVE
        [sprite1 show];
 
        //...
 
        [sprite1 release];      // <-- AT LEAST MEMORY IS RELEASED
    }
 
}

Hierarchy of Layers

  • Don’t create a big hierarchy of layers. Try to keep them low when possible.

Actions

  • It is expensive to create certain actions, since it might require a lot of malloc(). For example: A CCSequence of a CCSpawn with a CCRotateBy with a another CCSequence, etc… is very expensive.
  • So try to reuse actions.
  • Once the action is used, save it for later if you know you will execute that type of action again. Then, instead of allocing a new action, you can just initialize it.

Buttons

FIXME this is not a best practice but a Tip

  • Use a Menu with a MenuItemImage and place your menu using menu.position = ccp(x,y). See the MenuTest example for more details.

How does the pause/resume works ?

FIXME this is not a best practice

  • when the director receives the pause message it won’t call any scheduled target/selector.
  • but the draw selector will be called at a rate of 4 FPS (to reduce battery consumption)
  • when the director receives the resume message, the scheduled target/selectors will be called again every frame.