Author Topic: Texture Atlas Tutorial  (Read 4453 times)

Dids

  • Member
  • **
  • Posts: 25
  • Karma: +0/-0
    • View Profile
Texture Atlas Tutorial
« on: Fri, Apr 27, 2012 »
Recently I noticed that Canabalt uses texture atlases instead of individual sprites and upon researching these further, I noticed that they (supposedly) have an impact on performance, especially in flixel-ios's case, because you can preload (cache) them on startup, so they only have to be loaded in to memory once.

After much trial and error, I found the right "path" to succesfully using texture atlases on all iOS devices, using the latest iOS SDK and latest XCode. The following tutorial is based on what worked for me.

1. Get an app that produces atlases. I experimented with many (including TexturePacker and ShoeBox) but ultimately went with the one that worked the best: Zwoptex. The free version should do just fine.

2a. Get all your sprite graphics ready and import them in to Zwoptex.

2b. It's important to note that you need to (I had to) fit everything inside a 512x512 texture atlas, so in case they just won't fit, you'll have to create multiple atlases and link them together (which is what I did), more on this later.

3. In Zwoptex, select all your sprites and uncheck the Trim(med) option, we don't want that. Also set all padding/margins/etc to zero (0). Remember to set the width and height to 512, as previously discussed.

4. Open up Zwoptex preferences, open up the Coordinates Formats tab and click the + sign to add a new format. I named mine Flixel iOS. Set the extension to .plist and paste the following in to the source box.
Code: [Select]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>images</key>
<dict>{% for sprite in sprites %}
<key>{{ sprite.name }}</key>
<dict>
<key>atlas</key>
<string>{{ metadata.target.textureFileName }}{{ metadata.target.textureFileExtension }}</string>
<key>x</key>
<real>{{ sprite.textureRectX }}</real>
<key>y</key>
<real>{{ sprite.textureRectY }}</real>
<key>width</key>
<real>{{ sprite.sizeWidth }}</real>
<key>height</key>
<real>{{ sprite.sizeHeight }}</real>
</dict>{% /for %}
</dict>
</dict>
</plist>

5. Close the Preferences window and click on the big Layout-button in the upper left corner. This will automagically move your sprites around to fit the space of the atlas. Remember, if it seems like it's unable to fit them (ie. most of them are top of eachother in the upper left corner), remove some of them and add the removed sprites to a new atlas (using the same settings as before).

6. Once the layout process is done and it looks like it worked, click on Publish Settings in the upper right corner. For simplicitys sake, I changed the Save To File fields of both the atlas and the plist to say "MyAtlas.png" and "MyAtlas.plist", just seemed easier that way. From the Format-dropdown menu, select the previously created Flixel iOS format template and click Done.

7. You're ready to Publish, so go on ahead and click that big button (you've already set up the Publish Settings, so if it warns you about them, just click Done).

8. Repeat this process for any extra atlases that you might need, I separated my big and small sprites, so I had two atlases (SmallAtlas.png/plist & BigAtlas.png/plist).

9a. As Zwoptex doesn't seem to support multiple texture atlases using only one plist, we'll need to do a bit of work here.

9b. Open up your favorite text editor and open all of the plist files of the previously created atlas(es).

9c. Create a new file (I called mine CombinedAtlas.plist), which will hold all the atlas data combined. You'll need the basic plist structure, but you'll also need to copy and paste the relevant data from all of the plist files you've previously created. To recap: you'll create a plist template, then fill in the items from all the atlas plists you have, it should be pretty simple. Here's something to visualize that a bit better.
Code: [Select]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>images</key>
<dict>
<key>BigSprite.png</key>
<dict>
<key>atlas</key>
<string>BigAtlas.png</string>
<key>x</key>
<real>160</real>
<key>y</key>
<real>0</real>
<key>width</key>
<real>32</real>
<key>height</key>
<real>224</real>
</dict>
<key>SmallSprite.png</key>
<dict>
<key>smoke.png</key>
<dict>
<key>atlas</key>
<string>SmallAtlas.png</string>
<key>x</key>
<real>464</real>
<key>y</key>
<real>74</real>
<key>width</key>
<real>16</real>
<key>height</key>
<real>16</real>
</dict>
</dict>
</dict>
</plist>

10. You're almost there! Add (copy) the CombinedAtlas.plist and both the SmallAtlas.png and BigAtlas.png to your XCode project (leaving the Small/Big plists out, because we don't need those anymore).

11. Open up your AppDelegate and mofify the preloadTextureAtlases() function like seen below.
Code: [Select]
void preloadTextureAtlases()
{
    NSDictionary * infoDictionary = nil;
    infoDictionary = [NSDictionary dictionaryWithContentsOfFile:[NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] resourcePath], @"CombinedAtlas.plist"]];
    NSDictionary *images = [infoDictionary objectForKey:@"images"];
    for (NSString *image in images)
    {
        NSDictionary * imageInfo = [images objectForKey:image];
        CGRect placement;
        placement.origin.x = [[imageInfo objectForKey:@"x"] floatValue];
        placement.origin.y = [[imageInfo objectForKey:@"y"] floatValue];
        placement.size.width = [[imageInfo objectForKey:@"width"] floatValue];
        placement.size.height = [[imageInfo objectForKey:@"height"] floatValue];
NSString * atlas = [imageInfo objectForKey:@"atlas"];
        SemiSecretTexture * textureAtlas = [FlxG addTextureWithParam1:atlas param2:NO];
        SemiSecretTexture * texture = [SemiSecretTexture textureWithAtlasTexture:textureAtlas offset:placement.origin size:placement.size];
        [FlxG setTexture:texture forKey:image];
    }
}

12. Make sure you're calling it in the AppDelegate's didFinishLaunchingWithOptions function after you've initialized your game and before returning YES.

13. You're welcome. (yes, that's it, no more steps!)

Hopefully I didn't forget anything, as I just figured all this out myself, so writing it mostly out of memory. :-)

initials

  • Contributor
  • ****
  • Posts: 378
  • Karma: +0/-0
  • Initials
    • View Profile
    • Initials Blog. Code and other things.
Re: Texture Atlas Tutorial
« Reply #1 on: Fri, Apr 27, 2012 »
Great tutorial.

Canabalt also comes with a script that will put textures into an atlas.

I believe it's called .textureAtlas and there is a bash script that you use to call it.

like this:

Code: [Select]
#textureAtlas [prefix] [size] [files ...]

./textureAtlas menu_ 512 title.png bar.png title2.png button.png back.png
Initials: Super Lemonade Factory, Super Lemonade Factory Part Two, Above The Clouds, Revvolvver, Four Chambers of the Human Heart

Dids

  • Member
  • **
  • Posts: 25
  • Karma: +0/-0
    • View Profile
Re: Texture Atlas Tutorial
« Reply #2 on: Fri, Apr 27, 2012 »
Oh, I totally missed that, that's good to know, thanks. :)