How to Create and Optimize Sprite Sheets in Cocos2D with Texture Packer and Pixel Formats

How to Create and Optimize Sprite Sheets in Cocos2D with Texture Packer and Pixel Formats

Optimize texture usage with Texture Packer and Pixel Formats!

In Cocos2D, it’s important to combine your sprites into large images called sprite sheets, in order to get the best preformance for your games.

If you’ve used Cocos2D for a while, you may have used a tool called Zwoptex that helps you make sprite sheets. Zwoptex is a great tool – I have used it in many apps and it has saved me a ton of time!

However, there’s a new kid in town calledTexture Packer. It’s similar to Zwoptex in that it can create sprite sheets, but it has some amazing new features that come in really handy!

This article will start with a tutorial showing you how to use Texture Packer in your Cocos2D games. Along the way, you’ll learn how to use pixel formats and Texture Packer wisely to make sure your games launch quickly, run smoothly, and use as little memory as possible – while still looking good!

Full disclosure: I received a complimentary license key to check out Texture Packer from its author, Andreas Low. I didn’t promise a blog post in return, but after using it a bit to help reduce the memory load for one of my apps I’ve fallen in love with the tool, so wanted to tell you guys about it!

This tutorial is intended for those who are already familiar with Cocos2D. If you are new to Cocos2D, you should start with the How To Create A Simple iPhone App series and some of the other cocos2d tutorials.

Getting Started

First, make sure that you have the latest version of Cocos2D installed (at the time of writing this article, Cocos2D v0.99.5-rc1). It’s important to have the latest version, because the new version adds support for a new image format which you’ll be using later on in this article.

Once you’ve got that installed, start up XCode and make a new project with the Cocos2D Application template, and name the project TextureFun.

Next, you’ll need some images to make some sprite sheets with. Download this sample artwork I gathered together, and after you unzip it, drag the entire directory as a subfolder of your TextureFun project folder, as shown in the image below.

Ok, now that you have a project template and some sample art to work with, time to make a sprite sheet using Texture Packer!

Creating a Sprite Sheet with Texture Packer

The first thing you need to do is download a free copy of Texture Packer. Note you don’t have to buy anything – for the purposes of this tutorial you can use the free download.

When you download the DMG, click on “TexturePacker.mpkg” that appears in the window, and follow the prompts to install Texture Packer on your machine.

After you finish installing, find Texture Packer in your Applications folder and run it. When you see the first popup that appears, choose “Use Essential” (the free version) to continue.

Now, click the “Add Folder” button in the top toolbar and choose the TextureFun/Art/sprites folder. Texture Packer will load the images and intelligently lay them out within the sprite sheet as follows:

On the right hand side, you can see all of the images imported into Texture Atlas and click on each one to see the bounding box – another handy feature! You can also hover over the sprites to see if an alias was created (aliases are images that are actually the same after cropping).

A few notes about adding images by the “Add Folder” option by the way. First, when you add sprites by folder like this, Texture Packer doesn’t add references to the individual sprites, it adds a reference to the folder. This means if you add more sprites to the folder, next time you run Texture Packer it will pick up any new sprites in the folder – pretty handy!

Also note you don’t necessarily have to have all of your sprites in the same root folder, you could organize them in subfolders if you want (such as sprites/animals, sprites/monsters), and when retrieving them from Cocos2D you can refer to them by their relative path.

Finally, note that you can include more than one folder of sprites if you want – this can be handy for larger games where you have the same items on multiple sprite sheets/levels.

Ok, now take a look at the options on the left hand side. There you can configure the size, layout, and output options for the sprite sheet. Let’s quickly go through the size and layout options first:

  • Autosize is checked by default – this picks the smallest possible “power of two fit” for the sprites you added to the sprite sheet. This is a really handy feature, because it saves you time having to figure that out yourself.
  • Min/max size lets you specify a maximum size for a sprite sheet. This can be handy if you want to limit your spirte sheet to a certain size (to certain devices, or just if you want to make sure you don’t go over certain limits).
  • Scale lets you save the sprite sheet out in a larger or smaller scale than the images within. This can be handy if you want to load “2x” images in your sprite sheet (created for Retina-display devices or iPad), but actually save them out at half size (for use on iPhones/iPod touches without a Retina display).
  • Algorithm The only algorithm in Texture Packer right now is MaxRects, but it works pretty well so there’s no need to mess with it.
  • Border/shape padding The space between the border of the sprite sheet and the sprites themseves. If you see artifacts from neighboring sprites in your game, you may want to increase the values here to add more of a separation between the edges/sprites.
  • Extrude This repeats the pixels around the borders of sprites. This is the opposite of adding padding – if you see transparent “gaps” around the edges of your sprites, you may wish to set this to a higher value.
  • Trim Removes the transparent borders around sprites to pack them into the sprite sheets more efficiently. Don’t worry, the transparent areas are just removed for packing purposes – when you read the sprites back from Cocos2D the transparent regions will still be there (in case you need them for placement purposes, etc.)
  • Shape outlines Useful to turn on to see the edges of your sprites for debugging purposes.

For this sprite sheet, you don’t need to change any of the above values from their defaults – they’re fine as-is. However, you will be changing the values in the output section. But before you get to that, let’s talk about pixel formats in Cocos2D.

Pixel Formats and Cocos2D

It’s important to understand pixel formats in Cocos2D, because pixel formats affect how much memory it takes to load an image in your game. Since games usually load a lot of images, you want to make sure that you make the best use of the limited memory available on a mobile device as you can.

By default, when you load images in Cocos2D, it uses 4 bytes per pixel – 1 byte (8 bits) each for red, green, blue, and 1 byte (8 bits) for alpha transparency. This is known in shorthand as RGBA8888.

So if you load an image in the default pixel format (RGBA888), you can calculate the memory it will take to store with the following calculation:

width x height x bytes per pixel = size in memory

In this case, you have a 512×512 image, so if you load it with the default pixel format it would be:

512 x 512 x 4 = 1MB (!)

Wow! Considering the iPhone 3G only has 128MB total, that’s a big chunk for just one small sprite sheet. Imagine if you had several (even bigger) spritesheets, like games often do!

This is where pixel formats come to the rescue. You can save images with less bytes per pixel (2 bytes per pixel, or 16 bites per pixel), which trades off image quality for a reduction in memory.

In general, your goal should be to load at the minimum possible level while still having your game look good. Backgrounds are often a good candidate for 8-bit or 16-bit formats, and sprites are usually 16-bit or 32-bit. For a great writeup of the various pixel formats and which you should use when, check out Riq’s understanding pixel format guide.

By the way – if you look in the lower right corner of the window, Texture Packer will show you the image size your sprite sheet will take in memory based on your currently selected pixel format, so you don’t have to do the calculation by hand :]

Pixel Formats and Dithering

A lot of times, you can load an image with a smaller pixel format, and notice much of a reduction in quality. But in images that have a lot of gradients, you will notice some bad effects. Here’s an example of what it would look like if you load the art from this app with a 16-bit pixel format (RGBA4444) as-is:

Notice how there is “striping” in the areas with a lot of gradients, especially in the background, bear, and ooze image.

At this point, you might be tempted to rework your images to make less use of gradients or use a larger pixel format, but this is where Texture Packer comes in handy with another killer feature – image dithering!

When you save sprite sheets using Texture Packer, you can specify the target pixel format to save your images to (such as RGBA4444), and then choose a “dithering method.” This basically modifies your image’s colors a bit so that they look good at lower quality, even when they have gradients or other colors that usually would cause problems.

Go ahead and select RGBA4444 for this sprite sheet, and change the dithering option to “FloydSteinberg+Alpha”. Texture Packer will modify your images on the fly and show you an approximation of what they’ll look like – not bad when compared to the screenshot above, eh?

Now let’s save out the sprite sheet. Click on the “…” next to Texture file, browse to your TextureFun/Resources directory, and name the file “sprites-hd.pvr.ccz”. Then click the “…” next to Data file, browse to your TextureFun/Resources directory, and name the file “sprites-hd.plist”.

“But wait one gaddong minute”, you might say, “WTF is a pvr.ccz?!” Well I’m glad you asked…

PVRs and Compression, Oh My!

PVR images are an image container specific to the PowerVR graphics chip on iOS devices. They are good to use when possible on iOS because images can be loaded directly onto the graphics card, rather than needing to go through a conversion first.

PVR images can contain image data using a variety of pixel formats. For a while, Cocos2Donly supported the pixel formats you can create with the texturetool app that comes with the iOS SDK, but Cocos2D was recently extended to support many more formats as well.

And even more recently, Cocos2D was updated to support a compressed version of PVR images called pvr.ccz. The advantage of using these is a) your binary size is smaller since the images are compressed, and b) your game can start up much faster.

So in summary, for this sprite sheet you are choosing to save the image in a 16-bit pixel format (to reduce memory cost) and save as a pvr.ccz (to load the app faster).

Click “Publish, and your sprite sheet and property list will be generated! Texture Packer will tell you that some of the sprites will be created in red (since this is the free version).

Optimizing the Background

Let’s load up and optimize the background image as well. Click new to create a new Texture Packer window, click “Add Folder”, and choose the “TextureFun/Art/flower” folder.

Change the image format to RGB565 (for large images you want the worst quality you can get away with), change the Dithering to “FloydSteinberg”, save the texture file as “TextureFun/Resources/flower-hd.pvr.ccz”, and save the data file as “TextureFun/Resources/flower-hd.plist”.

Click “Publish”, close the warning, and when you’re done your screen should look like the following:

Using the Sprite Sheets in Cocos2D

Now go back to your project, right click on Resources, and choose “Add/Existing Files…”. Choose flower-hd.plist, flower-hd.pvr.ccz, sprites-hd.plist, and sprites-hd.pvr.ccz, cick Add, make sure that “Copy items into destination group’s folder (if needed)” is not selected, and click Add to finish.

Next, open HelloWorldScene.m and replace the contents of your init method with the following:

-(id) init
{
    if( (self=[super init] )) {
        CGSize winSize = [CCDirector sharedDirector].winSize;
 
        [CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGB565];        
        CCSprite * background = [CCSprite spriteWithFile:@"flower-hd.pvr.ccz"];
        background.anchorPoint = ccp(0,0);
        [self addChild:background];
 
        // More coming here soon...
    }
    return self;
}

The first thing you do here is load the background image. You first tell Cocos2D to use the RBG565 pixel format (the 8-bit pixel format you’re using for the background), and then call spriteWithFile to load the image off the disk in pvr.ccz format. Note that you don’t need to really treat it as a sprite sheet (i.e. load the plist), since the “sprite sheet” is just one image.

Note that you don’t really need the call to set the pixel format when loading from a pvr.ccz, because the file format itself contains information on what pixel format is contained within, but I wanted to add this here anyway because you do need this line if you are loading from a PNG instead (which is always saved as 32-bits per pixel, even though you may load it into memory in a different pixel format).

Next add the following where the “more coming here soon” comment is:

[CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGBA4444];
CCSpriteBatchNode *spritesBgNode;
spritesBgNode = [CCSpriteBatchNode batchNodeWithFile:@"sprites-hd.pvr.ccz"];
[self addChild:spritesBgNode];    
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"sprites-hd.plist"];

This changes the pixel format to RGBA4444 (the 16-bit pixel format you’re using for the main sprites) and then create a sprite batch node with the sprite sheet file. You also load the plist to get the definitions for each frame into the sprite frame cache.

Finally, add the following right below the above:

NSArray *images = [NSArray arrayWithObjects:@"bear_2x2.png", @"bird.png", @"cat.png", @"dog.png", @"turtle.png", @"ooze_2x2.png", nil];       
for(int i = 0; i < images.count; ++i) {
    NSString *image = [images objectAtIndex:i];
    float offsetFraction = ((float)(i+1))/(images.count+1);        
    CGPoint spriteOffset = ccp(winSize.width*offsetFraction, winSize.height/2);
    CCSprite *sprite = [CCSprite spriteWithSpriteFrameName:image];
    sprite.position = spriteOffset;
    [spritesBgNode addChild:sprite];
}
 
[CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_Default];

This loops through each of the images and scatters them across the screen.

If you compile and run your code (use iPad, it’s targeted for that), you’ll see the following:

This pretty much what you expect – and remember the red is just because you’re using the free version.

But what’s impressive about the above are the things you don’t see.

Behind the scenes, your app is loading a lot faster than it would have otherwise. It’s using a lot less memory, and best of all – it looks good. And the best part is it was totally easy to do just by using a few built-in settings with Texture Packer!

Don’t Believe Me?

While putting together this article, I did a couple simple tests to see how an app would perform in common scenarios, from worst to best. Here’s an overview of my findings:

  • Do the dumbest possible thing: By loading each and every sprite individually (i.e. no sprite sheets at all) in the default pixel format. Took about .73 secs to load, ~26MB RAM, and the game wouldn’t perform well as you start to add more sprites to the scene.
  • Use sprite sheets, with default pixel format: A good step forward. This would make the game perform better, and would also decrease memory usage somewhat (because you only have to upgrade one sprite sheet to the nearest power of two size, rather than every sprite in the game).
  • Use non-dithered sprite sheets made with Zwoptex, saved as PNGs, with reduced pixel formats: This significantly decreases memory cost, (to around ~15MB), but actually increases startup time a bit (up to 1 sec, I think due to having to change the pixel buffers around), and plus the graphics do not look good as you can see in the screenshot in the Pixel Foramts and Dithering section above.
  • Use dithered sprite sheets made with Texture Packer, saved as pvr.ccz: Huge improvement in looks and startup time (down to 0.31 sec startup!) Slight extra cost in memory usage (~17MB), I think may be due to a memory leak in the current version of the code that has since been fixed.

So overall, if you follow the best practice guidance above, I think you’ll be doing well for most cases :] If you want to see the test app I used and play around with it a bit, here ya go!

Texture Packer and XCode Integration

When using Texture Packer, you can use the GUI tool as you did here, or you can integrate it into your XCode build so it automatically updates your sprite sheets for you (if necessary) each time you compile.

If you’ve worked on a Cocos2D game in the past, you know how annoying it can be to have to constantly regenerate your sprite sheets. Even if it only takes a couple seconds each time, it adds up and gets old/annoying.

So let’s set this up real quick in the project – it only takes a few seconds and saves a lot of time later. Right click on Resources, choose “Add/New File…”, choose Mac OS X/Other/Shell Script, and click Next. Name the file PackTextures.sh, and click Finish.

Then replace the contents of PackTextures.sh with the following:

#!/bin/sh

TP="/usr/local/bin/TexturePacker"

if [ "${ACTION}" = "clean" ]
then
    echo "cleaning..."

    rm resources/sprites-hd.pvr.ccz
    rm resources/sprites-hd.plist

    rm resources/flower-hd.pvr.ccz
    rm resources/flower-hd.plist

    # ....
    # add all files to be removed in clean phase
    # ....
else
    echo "building..."

    # create hd assets
    ${TP} --smart-update /
          --format cocos2d /
          --data resources/sprites-hd.plist /
          --sheet resources/sprites-hd.pvr.ccz /
          --dither-fs-alpha /
          --opt RGBA4444 /
          Art/sprites/*.png

    ${TP} --smart-update /
          --format cocos2d /
          --data resources/flower-hd.plist /
          --sheet resources/flower-hd.pvr.ccz /
          --dither-fs-alpha /
          --opt RGB565 /
          Art/flower/*.png

    # ....
    # add other sheets to create here
    # ....
fi
exit 0

Everything you can do with Texture Packer, you can do with a command line tool (named TexturePacker, oddly enough!) If you type “TexturePacker” at the command line, you’ll see a printout of all the options it takes.

This script simply runs TexturePacker to create sprite sheets from the files in your Art directory – just like you did a little bit ago with the GUI tool. Check the TexturePacker command line help for more information about what each of these commands does.

Next, you need to set up your project to run this shell script when you compile. Right click on Targets, choose “Add/New Target…”, and choose “External Target” (not Shell Script Target!), and click Next. Name the Target TexturePacker, and click Finish.

Then double click on your TexturePacker target and set up the settings as follow:

The final step is to set this target as a dependency of your app. Double click on your TextureFun target, go to the General tab,click the + button in Direct Dependencies, choose Texture Packer from the list, and click Add Target.

Compile and run your app, and you should see the output from Texture Packer from your build results if everything is working OK.

If you see those printouts, that means that if you want to add a new file to your sprite sheet, literally all you need to do is drop it into your sprites directory and recompile! Pretty convenient, eh?

Texture Packer vs. Zwoptex

First of all let me say again, I am a big fan of Zwoptex, I think Robert has done an amazing job putting it together, and I honestly don’t think Cocos2D would be as far as it is today without it!

However when just comparing the features between Texture Packer and Zwoptex, Texture Packer seems to have 90% of the features Zwoptex has (it’s currently missing a few minor things like drag and drop, manual placement of images, etc.), but in addition has three killer features not currently in Zwoptex:

  • Dithering, dithering, dithering. Oh my, how I love me some dithering. I have had cases in the past where I wanted to use a lower quality pixel format but couldn’t, because the art looked like crap that way. The built-in dithering in Texture Packer makes the art look good, even at lower quality.
  • pvr.ccz support. Wow I love this new feature. It will really help games start up faster, and have smaller binary sizes too. Now I wish I hadn’t hacked Cocos2D so much in some of my older apps so it would be easier to upgrade O:-]
  • Command line support. Once you take the time to integrate Texture Packer into your XCode project, you will love life. Just makes things so convenient, especially when working back and forth with your artist in the development cycle.

Although Zwoptex is great, it doesn’t yet have these extremely useful features so for now I have to give the win to Texture Packer.

Texture Packer is a little more expensive ($17.95 vs. Zwoptex’s $14.95), but I think definitely worth it. And as Steffen Itterheim points out, both tools have a value worth even more than that!

Where To Go From Here?

Here is a sample project with all of the code from the above tutorial.

Are you a Texture Packer fan or a Zwoptex fan? Chime in your 2c below! Also if you have any good strategies for efficiently loading textures in Cocos2D, or any extra cool information, please let me know! :]

Category: iPad

Tags: cocos2D, game, iOS, iPad, sample code, tutorial

//

http://www.raywenderlich.com/2361/how-to-create-and-optimize-sprite-sheets-in-cocos2d-with-texture-packer-and-pixel-formats

你可能感兴趣的:(iOS,Game,Cocos2D)