Feel free to fix/add documentation to the wiki
cocos2d Best Practices
Improving performance
- Use this guide as reference: performance tests
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.DisplayLinkis the best Director, but is only available in SDK 3.1 or higher. IfDisplayLinkis not present, useMainLooporThreadMainLoop.
// must be called before any other call to the director if( ! [CCDirector setDirectorType:kCCDirectorTypeDisplayLink] ) [CCDirector setDirectorType:kCCDirectorTypeMainLoop];
- Or use NSTimer
CCDirectorwith 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
CCSpritefrom a correspondingCCSpriteSheetspritesheet object. - Use
CCBitmapFontAtlasorCCLabelAtlasinstead ofCCLabel - Use
CCTMXTileMaporCCTileMapAtlasto 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
CCTextureCacheCCTextureCachecaches 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
replaceSceneinstead ofpushScene pushSceneis 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
CCNodeobjects (CCSprite,CCLabel,CCLayer, etc) or any other kind of object in theinitselector and not in thedrawand 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: ACCSequenceof aCCSpawnwith aCCRotateBywith a anotherCCSequence, 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
this is not a best practice but a Tip
- Use a Menu with a
MenuItemImageand place your menu using menu.position = ccp(x,y). See theMenuTestexample for more details.
How does the pause/resume works ?
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.
Trace: » best_practices