Using a Texture Atlas

Posted May 20, 2022

Hold up!

If you like 90s platformers, check out my new game Kid Bubblegum!

Using a texture atlas (or a “sprite sheet”, if you prefer) is one of the easiest ways to increase the performance of your game. In case you don’t know what an atlas is: It’s essentially one big image that contains all your game’s sprites, along with a text file that lists all these sprites with their names and positions within the image, so that your game can easily retrieve them.

The reason a texture atlas increases the performance of your game is that instead of loading and storing hundreds of separate image files in memory, your game only needs to keep one image handy at all times. Even with a 90s platformer like Go! Go! PogoGirl, this can make a huge difference. I’d therefore highly recommend using a texture atlas once your game gets out of the prototyping stage.

Luckily, HaxeFlixel has a lot of built-in functions to help you out. However, it can be a bit confusing figuring out how it all works, so let me explain how I do it!

Note that there are probably better and smarter ways to do it, but this approach works for me.

Packing Textures

Broadly speaking, there are two big steps when using texture atlases: Preparing the texture atlas, and loading it in the game.

There are a bunch of different programs for creating a texture atlas, but I use Free Texture Packer. It’s free, flexible and gives me everything I need. Plus, it works well with HaxeFlixel.

I’m not gonna give you a full tutorial on how to use it, as it’s not that complex. But broadly speaking, this is what you want to do:

  1. Export all your sprites as single images. This means that every frame of every animation of every sprite should be its own image file (preferably .png). Make sure that files belonging to the same animation are numbered: For example, pogogirl1.png,pogogirl2.png,pogogirl3.png
  2. Point your texture packer to the folder containing all your images. It will then pack them together into one big texture.
  3. Finally, export that big texture along with a descriptor file.

If you’re using Free Texture Packer, make sure to export the descriptor in xml format. The xml file contains a list of all your sprites (using the file names) along with their x- and y-positions within the texture. This file will serve as a “directory” for HaxeFlixel to look up what sprite has been stored where in the atlas texture.

For reference, this is what my export settings look like in Free Texture Packer:

images/export.png

And this is a snippet from my exported .xml file. Note the numbered pogogirl entries, which are all animation frames of PogoGirl. Also note how they’re not in order, because Free Texture Packer sorts them as it sees fit (which is perfectly fine, as we won’t manually work with this list anyway):

images/sheetxml.png

Now plop the texture and xml file somewhere in the assets folder of your game.

Loading the Sheet

The next step is loading and using the atlas (or “sheet”) we’ve just created. This can be separated into two sub-steps: First, we load the atlas into memory. Then, each entity in the game grabs the frames it needs from the atlas.

To load the atlas, I use a singleton called SheetBox. You don’t have to do it that way, but I find it useful because a) it makes sure that the atlas is only loaded once and b) it actually makes it easier to load and deal with multiple atlases.

My singleton contains a private map _sheets of type Map<String, FlxAtlasFrames> which, as you can probably guess, stores all the loaded atlases, along with a name for easy retrieval. Actually loading an atlas is done like this in the constructor of the singleton:

_sheets["entities"] = FlxAtlasFrames.fromTexturePackerXml("assets/images/sheets/entities.png", "assets/images/sheets/entities.xml");

As you can see, we specify both the texture containing all our sprites (entities.png) and the corresponding descriptor file (entities.xml). HaxeFlixel then creates an object of type FlxAtlasFrames that combines the two to give us easy access.

Note: FlxAtlasFrames actually contains multiple functions to load atlases of different formats. For xml files exported in Free Texture Packer, fromTexturePackerXml is ideal. If you’re already used to another packer, feel free to check if FlxAtlasFrames has an import function for it!

Okay, now we have our atlas loaded and stored in the _sheets map. To retrieve the sheets from outside the singleton, I wrote a small helper function:

public function getSheet(Name:String):FlxAtlasFrames
{
	var s = _sheets[Name];
	if (s == null)
		throw "No Sheet by that name!";
	return s;
}

This one should be self-explanatory.

And again: You don’t need a singleton with a map of atlases. You could just as well just load your atlases at the beginning of the PlayState, or whatever. I just like the singleton approach because it keeps the codebase tidy, and I can easily reuse it in different projects.

Loading the Sprites

Okay, with the atlas now loaded and ready, it’s time to actually grab the sprites we need for our entities.

All my in-game entities (which extend from FlxSprite) have a helper function to easily load stuff from an atlas. It looks like this:

public function loadGraphicFromSheet(SheetName:String, ?Single:String)
{
	this.frames = SheetBox.instance.getSheet(SheetName);
	if (Single != null)
	{
		this.frame = this.frames.getByName(Single + ".png");
		resetSizeFromFrame();
	}
}

This function is used instead of the usual loadGraphic you use when loading a single image file.

The magic happens in the first line. As you can see, we’re setting the frames property to use the FlxAtlasFrames we just loaded in our singleton. This tells this sprite to use the atlas SheetName. And if our sprite doesn’t have animations, we’re almost done! That’s where the Single parameter comes in.

By setting this.frames, we tell the entity to use the atlas…but not which of the many sprites in the atlas to use. To do that, we need to set the frame property (don’t confuse it with frames) of the entity. This happens in this.frame = this.frames.getByName(Single + ".png"). The function getByName grabs the frame/sprite of a certain name from the atlas and sets it as the currently active frame of the entity. The name here is one of the names listed in the xml file of our atlas.

So, for example, if my atlas entities had a static image of a wooden crate in it that’s listed in the xml as crate.png, I’d call the function from inside my Crate entity like this:

loadGraphicFromSheet("entities","crate");

This will grab the crate sprite from the atlas and load it for the entity.

And you might have noticed the final line in that function: If we don’t call resetSizeFromFrame() after grabbing frames from an atlas, the hitbox of the sprite will actually be as big as the whole atlas texture itself! As the name implies, resetSizeFromFrame resizes the hitbox to fit the current frame, which is what we want.

Animations

That’s it for loading static sprites, but what if we want to load animations from the atlas? Luckily, this is only slightly different from how we usually load animations in HaxeFlixel.

First, when we call loadGraphicFromSheet from within our entity, we don’t pass in a Single argument, as we don’t just want to load a single sprite. Instead, we want to load animations. And we do that similarly to how we usually do it. Here’s an example:

animation.addByIndices("bounce", "pogogirl", [2, 2, 3, 3, 4, 4, 5], ".png", 15, false);

Instead of just calling animation.add, we call animation.addByIndices. And as you can see, the function signatures of both functions are similar. But let’s go through the parameters.

First, we specify the name our animation should have ("bounce"). Then we specify the base name of the sprite we want to load from the atlas; again, this is the name as listed in the xml file. Then, we specify the frames of the animation. Then comes a postfix (in most cases the file extension of the texture), along with the desired frame rate and whether the animation should loop.

So basically, it’s the same setup as with the usual animation.add, just that we also have to specify the name of the sprite and the file extension.

But there is a gotcha: When specifying animation frames with animation.add, the first frame of an animation is stored at position 0. However, this might not necessarily be the case with addByIndices! This function doesn’t directly load frames from anywhere. Instead, it combines the base name (pogogirl) with the frame numbers in the array and the postfix to load the frames from the atlas. This means that if the first animation frame is listed as pogogirl-1.png in the atlas, you have to pass 1 into the array to get it! And this also means that if your sprites in the xml aren’t listed with their file extensions, you can just leave the postfix argument empty in the function call.

And after loading the animation, don’t forget to call resetSizeFromFrame() to adjust the size of the hitbox. You might also have to call centerOrigin() if you want to do rotations, by the way!

Summary

This was a lot, so let’s go over it one more time. To load a sprite from a texture atlas in HaxeFlixel, you do the following:

  1. Export all your sprites and animation frames in single images. Make sure that files belonging to the same animation are numbered consecutively.
  2. Load the images into Free Texture Packer and export a texture with an xml file. This xml will list all your image files using their file names, along with their position within the texture.
  3. Load those two files into HaxeFlixel using FlxAtlasFrames.fromTexturePackerXml (or a similar function, if you’re using different formats). This gives you a FlxAtlasFrames object with all the data you need.
  4. When you want to load sprites from the sheet, set the frames property of the respective entity to the loaded FlxAtlasFrames. This basically tells the entity what atlas to use.
  5. Load the frame(s) from the atlas you actually want to use, either by manually setting the frame property or by loading animations.
  6. Don’t forget to adjust the size of the hitbox, otherwise your entity will be as big as the whole atlas texture.

This might seem like a lot of work, but it can drastically improve your game’s performance, and once you’ve set it all up, it’s rather easy to reuse across your different projects.

Got a better way of handling texture atlases? Got any recommendations for texture packers? Let me know in the comments below!

If you have any questions, comments or criticism, post them in the comments below or reach out to me on Twitter @ohsat_games!

Join my Discord Server!

Hang out, get news, be excellent!

Come hang out!

Want To Buy Me a Coffee?

Coffee rules, and it keeps me going! I'll take beer too, though.

Check out the rest of this tutorial series!

  • HaxeFlixel Crash Course: Make a Pong Game in Under 1 Hour
  • Pixel-Perfect 2D Water Shader
  • Collision and Overlap
  • Using Finite State Machines in HaxeFlixel
  • One-Way Collisions
  • Z-Sorting in HaxeFlixel
  • Making an Underwater Music Effect in HaxeFlixel
  • Quick Snow Effect
  • How to Implement Cheats
  • Using LDtk with FlxTilemap
  • Using a Texture Atlas
  • How To Make Your Own Haxe Library
  • By using the Disqus service you confirm that you have read and agreed to the privacy policy.

    comments powered by Disqus