mardi 29 janvier 2013

Cocos2D : CCSpriteBatchNode & TexturePacker

Every time a texture is drawn on the screen, the graphics hardware has to prepare the rendering, render the graphics, and clean up after rendering. There is an inherent overhead caused by starting and ending the rendering of a single texture. This can be alleviated by asking the graphics hardware to render an entire group of sprites. In that case, the graphics hardware will perform the preparation and cleanup steps only once for that group of sprites.


It's in that purpose that the CCSpriteBatchNode appears. All the CCSprite added to the CCSpriteBatchNode are drawn in one single OpenGL ES draw call, known as a batch draw. The more CCSprite nodes you can group together, the greater the benefits of using a CCSpriteBatchNode. There are limitations though, all the CCSprite nodes added to a CCSpriteBatchNode will be drawn at the same z-order. To create a CCSpriteBatchNode, you can use the class method:
+ (id)batchNodeWithFile:(NSString*)imageFile;
The reason this method takes an image file as an argument is that all the CCSprite nodes added to the CCSpriteBatchNode must use the same texture! This argument is here to check that you are not accidentally adding a CCSprite with a different texture. You gonna tell me: If I can use a CCSpriteBatchNode only to draw several times the same image, it's not so useful. To that I will answer with those two words: Texture Atlas.

Texture Atlas

A texture atlas is plainly an image that contains smaller images. With that simple idea comes a lot of advantages. Indeed, texture atlases help conserve memory and speed up the rendering of sprites. As you may know, textures width and height always have to be a power of two, for example an image with dimensions of 130x520 becomes a texture with dimensions of 256x1024 in memory, wasting a lot of a precious memory. The amount of wasted memory become significant if you have several such images and you load them into individual textures. So an amendment to the previous definition: a texture atlas is an image, already aligned to a power-of-two dimension, that contains multiple images, sparing the waste of memory. You can create a sprite from a texture atlas by using, on the CCSprite class, the method:
+ (id)spriteWithSpriteFrame:(CCSpriteFrame*)spriteFrame;
This method takes a CCSpriteFrame as an argument, that you can create with its class method:
+ (id)frameWithTexture:(CCTexture2D*)texture rect:(CGRect)rect;
Thanks to a texture atlas, you can add multiple images to a CCSpriteBatchNode. But as you may think, packing images into a texture atlas and noting the rectangular sprite frames would be a monumental task, that's where TexturePacker comes in.


I'll introduce here TexturePacker, a great 2D sprite-packing tool. It's available in both free and paid versions. The free version is sufficient for basic needs, but the paid version, with a reasonably low price, contains advanced features such as optimizing the graphics to save memory, high-resolution data for retina displays, scaling down images for non-retina displays, etc.

In TexturePacker you simply drag and drop your images in the right pane, after doing that your images immediately appear in the center pane, which is a real-time preview of your texture atlas.

As you may see in the above-image, TexturePacker will create an optimal packing rate by trimming the transparent border pixels of each images, and by rotating some images. Doing this will reduce the texture size and speeds up the rendering of the sprites. In case you're wondering about those modifications, once back in your project, Cocos2D will automatically handle the rotation and the shifting of those sprites.

Before saving the texture atlas, you need to configure properly the output settings. First you have to make sure that the data format is set to Cocos2D because TexturePacker can be used with a lot of other game engines, such as Unity3D, OGRE, Corona, AppGameKit and so on. Then you have to locate where to save the plist file, which will contain the name, position, size and orientation of each image. Make sure the file has the -hd suffix, TexturePacker will, as expected by Cocos2D, save the HD texture and will omit the -hd suffix for the SD version (Yes, TexturePacker will automatically create HD and SD versions of the texture atlas by down-scaling your retina images). You can then choose the texture format, a compressed version of the iPhone's native PVR format is recommended. Finally the image format allows you, once again, to optimize the size of the generated texture atlas. For example the RGBA4444 image format is a good compromise between quality, rendering speed and memory usage, it provides 4 bits per color and 4 more bits for the alpha channel. If you don't need any transparency, you may be interested by the RGB565 image format, which uses 5 bits for the red, 6 bits for green, and 5 bits for blue channel. Once the configuration is done, you just have to click the publish button, and TexturePacker will write the HD and SD textures and the accompanying plist files.

Back to the code, in order to show you how to use those plist files generated by TexturePacker, let me introduce you CCSpriteFrameCache. It's a singleton, designed to handle the loading of the sprite frames. First you have to add your file to that cache:
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"atlas.plist"];
Then you can load a sprite from your texture atlas with that simple line:
CCSprite* sprite = [CCSprite spriteWithSpriteFrameName:@"mySprite.png"];
Yep, that's all! Amazing isn't it ?

In summary, TexturePacker is a excellent tool that, combined to a CCSpriteBatchNode, help you to optimize your app from both memory usage and rendering speed perspectives.

Aucun commentaire:

Enregistrer un commentaire