Texture Packer Tutorial: How to Create and Optimize Sprite Sheets
This Texture Packer tutorial will show you how to use Texture Packer to create and optimize sprite sheets in your games, using a Cocos2D 2.X game as an example. By Ray Wenderlich.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Texture Packer Tutorial: How to Create and Optimize Sprite Sheets
35 mins
- Getting Started
- Creating a Sprite Sheet with Texture Packer
- Pixel Formats
- Pixel Formats and Dithering
- PVRs and Compression, Oh My!
- Optimizing the Background
- Using the Sprite Sheets in Cocos2D
- Don't Believe Me?
- Installing the Texture Packer Command Line Tool
- Texture Packer and XCode Integration
- Adding The Target As Part Of The Build
- Why get the Pro version?
- Where To Go From Here?
Update 5/10/2013: Fully updated for Cocos2D 2.1-rc0a, Texture Packer 3.07, and Modern Objective-C style (original post by Ray Wenderlich, update by Tony Dahbura).
When making 2D games, it’s important to combine your sprites into large images called sprite sheets, in order to get the best performance for your games.
And Texture Packer is a great tool that makes it extremely easy to generate these sprite sheets – with the click of a button.
This Texture Packer tutorial will show you how to use Texture Packer to create and optimize sprite sheets in your games, using a Cocos2D 2.X game as an example.
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!
If you are new to Cocos2D, you can still follow along with this Texture Packer tutorial, but there may be some missing gaps in your knowledge about Cocos2D itself as the focus will be on TexturePacker. If you’re curious to learn more about Cocos2D, check out some of our other Cocos2D tutorials.
Getting Started
Like I said, even though this is a Texture Packer tutorial, I wanted to show you an example of this working in an actual game, so we’ll be using Cocos2D for that.
So first make sure that you have the latest “unstable” version of Cocos2D v2.x installed.
Once you’ve got that installed, start up Xcode and make a new project with the iOS\cocos2d v 2.x\cocos2D iOS template, and name the project TextureFun. Set your device family to iPad.
You are going to use ARC in this project, but by default the template isn’t set up to use ARC. Luckily, fixing it is really easy. Just go to Edit\Refactor\Convert to Objective-C ARC. Expand the dropdown and select only the last four files (main.m, AppDelegate.m, HelloWorldLayer.m, and IntroLayer.m), then click Check and finish the steps of the wizard.
Next, you’ll need some images to make the sprite sheets. Download this sample artwork I gathered together, and after you unzip it, drag the entire directory as a subfolder of your TextureFun project source code 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 copy of Texture Packer. There is a lite version that will work for this tutorial but you will quickly see the advantages of this great tool and want to use the full featured version.
When you download the DMG, and open the package and agree to the license, drag the TexturePacker icon to the Applications folder and follow the prompts to install Texture Packer on your machine.
After you finish installing, click on Texture Packer in your Applications folder to run it. Agree to the license prompt and when you see the first popup that appears, choose “Test Pro for 1 week” to continue.
Now, click the Add Folder button in the top toolbar and choose the TextureFun/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!
A few notes about adding images using 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 automatically – 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 output, geometry (size), and layout options for the sprite sheet.
Try dragging the entire Texture Settings bar – you’ll see that it can detach. One of the many new features of Texture Packer is the ability to pull the settings off onto your desktop and have them float outside your work documents.
Let’s quickly go through some of the new size and layout options on the Texture Settings bar:
- Data Format: This provides you the ability to select the format for your data files to match your game framework. If your framework is not listed (unlikely as it may be) you can create a custom format to support your needs/framework.
- Texture Format: This specifies the graphics file format desired for the sprite sheet. For this tutorial you will be using the pvr.ccz format which is the Power VR with zlib compression on the data file. Other option choices will change based on the selection of this.
- Premultiply alpha: This is an option when using PVR format and is checked by default. This option multiplies the alpha value times each color so they are computed prior to the framework needing them and is required by some frameworks-it results in faster rendering.
- Image Format: This specifies the pixel format to use in the texture file. These settings affect things like how many bytes are used per pixel of the graphic. For this tutorial you will be using RGBA4444 which means two bytes per pixel will be used.
- Dithering: This lets you specify the algorithm to use when computing values to attempt to maintain image quality during compression. FloydSteinberg Alpha method uses a distribution to compute the next approximation of the color that should be used to minimize color loss with the reduced pixels. This allows an image that is of higher number of colors to a format with lower number of colors (bits to represent color).
- AutoSD: This is a great feature that lets TexturePacker size and scale your graphics automatically during the publish phase. This allows them to be suffixed with a naming format that Cocos2D will use to select the appropriate file based on the device type and display type. This feature saves you the effort of doing this yourself for all the different screen types and resolutions.
- Max size/Fixed size: This 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).
- Size constraintsL POT (Power of 2) 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.
- Scale: This 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 Texture Packer includes two algorithms MaxRects and Basic, MaxRects 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
It’s important to understand the pixel formats your game engine supports, 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 iPhone 5 phones have 1GB total, with the higher resolution screen and desire for more and more action in your games having large sprite sheets can add up quickly. Older phones like the 3G have 128MB, and 4S phone have 512MB. 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 bytes 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 not 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 Image format of RGBA4444 for this sprite sheet, and change the Dithering option to FloydSteinbergAlpha. 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?
In the panel on the left, under Output make sure Data Format says cocos2d. Then click the button with the … next to Data File and locate your Cocos2d 2.x project’s resources folder and save the file as sprites-ipadhd.plist. TexturePacker will automatically fill in the Texture File name for you.
Now click the Texture Format dropdown and select zlib compr. PVR (.pvr.ccz, Ver.2). A warning will popup about premultiplying alpha to avoid halos-just select yes for now.
Next, click the gear icon next to AutoSD. In the Presets dropdown, select cocos2d ipad/hd/sd and click Apply. This makes TexturePacker scale down the artwork for the iPhone retina (2x) and iPhone non-retina (1x) displays automatically.
Close the popup and click Publish. TexturePacker will automatically create the sprite sheets for you – in all resolutions.
As a last step save your settings as a TexturePacker TPS file by selecting Save and saving as sprites.tps in TextureFun/Resources. You can always use this file later to open your Texture file and look at the settings. You will look at a great use of this file at the end of the tutorial!
Now, go back to XCode and add these to your project. Right click on the Resources folder of your project, click Add Files to TextureFun…, select all files that begin with sprite in the Resources folder (except for the .tps file), make sure the Copy items into destination group’s folder is unchecked and click the Add button to add them to your project. At this point your Resources group should look like this:
“But wait one gaddong minute”, you might say, “what in the world 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, Cocos2D only supported the pixel formats you can create with the texturetool app that comes with the iOS SDK, but Cocos2D was later 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).
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/TextureFun_Art/background folder. The MuleDeer.png image is large (2048×2048) and you will see a warning at the bottom “1 not fitting sprites, …”.
This is Texture Packer informing you that your sprite sheet size is inadequate to fit this sprite. Rather than increase the size of our sprite sheet, since this is a single image on the sprite sheet, which is sized 2048×2048 you will adjust the border-padding. Scroll to the Layout section and set the Border padding value to 0. The image will now appear on the sheet and the warning will disappear.
Change the Image format to RGB565 (for large images you want the worst quality you can get away with) under the Output section, change the Dithering to FloydSteinberg.
In the panel on the left, under Output make sure Data Format says cocos2d. Then click the button with the … next to Data File and locate your Cocos2d 2.x project’s resources folder TextureFun/Resources and save the file as background-ipadhd.plist. TexturePacker will automatically fill in the Texture File name for you.
Now click the Texture format dropdown and select zlib compr. PVR (.pvr.ccz, Ver.2). Acknowledge the warning that pops up by selecting yes.
Next, click the gear icon next to AutoSD. In the Presets dropdown, select cocos2d ipad/hd/sd and click Apply. This makes TexturePacker scale down the artwork for the iPhone retina (2x) and iPhone non-retina (1x) displays automatically.
Click Publish, close the warning, and when you’re done your screen should look like the following screen:
As a last step save your settings as a TexturePacker TPS file by selecting Save and saving as background.tps in TextureFun/Resources. You can always use this file later to open your Texture file and look at the settings.
Using the Sprite Sheets in Cocos2D
Now go back to Xcode and in your project right click on the Resources folder of your project, click Add Files to TextureFun…, select all files that begin with background in the Resources folder, make sure the Copy items into destination group’s folder is unchecked and click the Add button to add them to your project. Your Resources folder should look like:
Next, open HelloWorldLayer.m and replace the contents of your init method with the following:
-(id) init
{
if( (self=[super init] )) {
CGSize winSize = [CCDirector sharedDirector].winSize;
CCSpriteBatchNode *backgroundBgNode;
backgroundBgNode = [CCSpriteBatchNode batchNodeWithFile:@"background.pvr.ccz"];
[self addChild:backgroundBgNode];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"background.plist"];
CCSprite *background = [CCSprite spriteWithSpriteFrameName:@"MuleDeer-ipadhd.png"];
background.anchorPoint = ccp(0,0);
[backgroundBgNode addChild:background];
// More coming here soon...
}
return self;
}
The first thing you do here is load the background image. In the olden days, you needed to tell Cocos2D to use the RBG565 pixel format (the 8-bit pixel format you’re using for the background using [CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGB565]
), but this is no longer necessary because the PVR files store a reference to the pixel format they are saved as.
Next, you create a sprite batch node from the sprite sheet file using batchNodeWithFile
to load the image off the disk in pvr.ccz format. You then load the plist to get the definition (even though there is only one) and finally get the sprite with spriteWithSpriteFrameName
by specifying the background.png name.
Next add the following where the “more coming here soon” comment is:
CCSpriteBatchNode *spritesBgNode;
spritesBgNode = [CCSpriteBatchNode batchNodeWithFile:@"sprites.pvr.ccz"];
[self addChild:spritesBgNode];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"sprites.plist"];
This creates 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:@"bird.png", @"cat.png", @"dog.png", @"turtle.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];
}
This loops through each of the images and scatters them across the screen. If you compile and run your code (use iPad Retina, it's targeted for that), you'll see the following:
This is pretty much what you would expect.
If you launched this using the iPad non-retina option you may have noticed the background was displayed in the lower left of the screen and did not cover the whole background. What gives?
Remember the AutoSD option you used to create different sizes of our sprites. TexturePacker by default uses a file suffix of hd for the non-retina version. Cocos 2D is looking for a suffix of ipad.
Luckily, this is easy to fix, open AppDelegate.m and look for the following line (it is midway in the didFinishLaunchingWithOptions
method):
[sharedFileUtils setiPadSuffix:@"-ipad"]; // Default on iPad is "ipad"
Change this line to the following:
[sharedFileUtils setiPadSuffix:@"-hd"]; // Default on iPad is "ipad", but match it to hd for TexturePacker
Save and run again, and if you used the non-retina iPad as your simulator your background should properly fill the screen.
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 great. And, 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 the 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 a different sprite sheet generator, 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!).
So overall, if you follow the best practice guidance above, I think you'll be doing well for most cases :]
Installing the Texture Packer Command Line Tool
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. To use the command line tool you will need to install it. Texture Packer makes this a snap! Launch Texture Packer and select the Install Command Line Tool option in the TexturePacker menu.
Upon selecting this menu option you will be greeted with a dialog box asking where to install the utility. The default location of /usr/local/bin is the best place, so go ahead and click Install. You may be prompted to enter admin privileges in order have the file written. When done the install, you will be greeted with an Installation seems to be fine dialog. Click OK, then click Done in the remaining dialog. You now have the command line tool installed :]
Open a terminal window and just type TexturePacker and hit return. You should see a help screen scroll by. Next, you will integrate this into your Xcode project's build cycle.
Everything you can do with Texture Packer, you can do with the command line tool (named TexturePacker, oddly enough!).
Texture Packer and XCode Integration
If you've worked on a Cocos2D 2.x 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 quickly.
So, let's automate this for our project - it only takes a few seconds and saves a lot of time later. Right click on Resources, choose New File..., choose OS X\Other\Shell Script, and click Next. Name the file PackTextures.sh, select the Resources directory of your TextureFun project, and click Create.
Then replace the contents of PackTextures.sh with the following:
#!/bin/sh TP="/usr/local/bin/TexturePacker" cd ${PROJECT_DIR}/${PROJECT} if [ "${ACTION}" = "clean" ]; then echo "cleaning..." rm -f Resources/sprites*.pvr.ccz rm -f Resources/sprites*.plist rm -f Resources/background*.pvr.ccz rm -f Resources/background*.plist # .... # add all files to be removed in clean phase # .... else #ensure the file exists if [ -f "${TP}" ]; then echo "building..." # create assets ${TP} --smart-update \ --format cocos2d \ --texture-format pvr2ccz \ --main-extension "-ipadhd" \ --autosd-variant 0.5:-hd \ --autosd-variant 0.25: \ --data Resources/sprites-ipadhd.plist \ --sheet Resources/sprites-ipadhd.pvr.ccz \ --dither-fs-alpha \ --opt RGBA4444 \ TextureFun_Art/sprites/*.png ${TP} --smart-update \ --format cocos2d \ --texture-format pvr2ccz \ --main-extension "-ipadhd" \ --autosd-variant 0.5:-hd \ --autosd-variant 0.25: \ --data background-ipadhd.plist \ --sheet background-ipadhd.pvr.ccz \ --dither-fs \ --opt RGB565 \ --border-padding 0 \ --width 2048 \ --height 2048 \ TextureFun_Art/background/*.png # .... # add other sheets to create here # .... exit 0 else #if here the TexturePacker command line file could not be found echo "TexturePacker tool not installed in ${TP}" echo "skipping requested operation." exit 1 fi fi exit 0
This script simply runs the TexturePacker command line tool 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. Select your project in Xcode, select the TextureFun target under TARGETS, and click the Add Target button on the bottom. Select OSX, Other, External Build System and click Next.
For the Product Name enter TexturePacker, and click Finish. Now select the new Tartget and enter /bin/sh in the Build Tool field. Finally, enter ${PROJECT_DIR}/${PROJECT}/Resources/PackTextures.sh for the Arguments field. This allows Xcode to find your PackTextures.sh script by searching off where your project is located.
Adding The Target As Part Of The Build
The final step is to set this target as a dependency of your app. Single click on your TextureFun target, go to the Build Phases tab, select the Target Dependencies row and expand it. Click the + button and select the TexturePacker Target in the dialog.
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?
Once you take the time to integrate Texture Packer into your XCode project, you will love life. It makes things so convenient, especially when working back and forth with your artist in the development cycle.
Why get the Pro version?
The pro license gets you is an ability to greatly simplify the script you used above within Xcode by referencing the .tps files you created within TexturePacker, removing the need to code out all those settings in the script.
The other advantage is you can change your settings graphically and update/save the new tps file and Xcode will pick it up the next time it does a build! The new (pro) version of the PackTextures.sh script becomes:
#!/bin/sh TP="/usr/local/bin/TexturePacker" cd ${PROJECT_DIR}/${PROJECT} if [ "${ACTION}" = "clean" ]; then echo "cleaning..." rm -f Resources/sprites*.pvr.ccz rm -f Resources/sprites*.plist rm -f Resources/background*.pvr.ccz rm -f Resources/background*.plist # .... # add all files to be removed in clean phase # .... else #ensure the file exists if [ -f "${TP}" ]; then echo "building..." # create assets ${TP} --smart-update Resources/sprites.tps ${TP} --smart-update Resources/background.tps # .... # add other sheets to create here # .... exit 0 else #if here the TexturePacker command line file could not be found echo "TexturePacker tool not installed in ${TP}" echo "skipping requested operation." exit 1 fi fi exit 0
In the above script you are referencing the sprite.tps and the background.tps files rather than writing out all those options as you did earlier! What a time-saver :]
Where To Go From Here?
Here is a sample project with all of the code from the above tutorial.
If you have any good strategies for efficiently using Texture Packer, or any extra cool information, please let me know! :]