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.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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:

Background sprite saved with Texture Helper

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:

Resources folder after adding background sprite sheet files from TexturePacker

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:

Spritesheets loaded on iPad

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 :]

Contributors

Over 300 content creators. Join our team.