In this tutorial I will walk you through animating a sprite with a texture packer.
Example source code
Though this is a reasonably simple procedure, there are some prerequisites:
- SDL (if you don’t know SDL, but want to, there are plenty of helpful tutorials here)
- The SDL Image library (Direct download)
- Jsoncpp (to generate the source files, just run
amalgamate.py
from the repo, the files can then be found in the dist folder) - A json file that points to the packed texture and defines the subtextures (I’m using Shoebox to pack the textures, which depends on Adobe Air. It spits out xml files but there are plenty of online converters).
First off, you’ll want to write your main.cpp and set up SDL in your application. If you’re using Visual Studio like me, you’ll need to open up your project properties, go to VC++ Directories and add the path to your sdl includes and libraries in the appropriate files (detailed info on that here).
Next step is to include SDL in your source file, like so:
And then write your main()
and initialize SDL:
Build and run your project. Make sure the compiler finds the SDL headers and that the linker finds the libraries. (If you’re having problems, LazyFoo’ has in-depth tutorials on getting SDL to behave nicely on your machine.)
Moving forward, you’ll want to declare an array of (pointers to) SDL_Rect
s. These are your source rectangles for animation. We’re going to load one texture andnuse multiple source rectangles to draw the desired part of it. You could write a sprite class to hold these but for the sake of this tutorial we’re just going to do it in main()
. If you don’t know how many frames you’ll have at compile time, consider using a vector instead of an array.
As well as the array (or vector) you will also need a pointer to an SDL_Texture
. This will point to the packed texture, which we’ll get in a minute.
Alrighty then, you’ve got an array of SDL_Rect
pointers and a pointer to an SDL_Texture
; Now you need to load up your json file: have no fear, it’s simpler than it sounds! (but we will be writing a class for this part)
The class shall be called TextureAtlas
, and will serve as our texture atlas! It too needs a pointer to a texture. It also needs a map of strings to SDL_Rect
pointers (this is so that we can request subtextures by their original names). You will need to include SDL and SDL_image in the class, just as we did in main, but we will also need <fstream>
(for reading the file) and json/json.h
(for parsing the json, duh!).
With a little foresight, we’re also going to declare three methods in addition to the constructor and destructor, they are:
operator[]
, which takes a string and returns anSDL_Rect
pointer. (It’s also a public and const function)getTexture()
, which returns a pointer to the texture. (Is also both public and const)loadTexture()
, which takes a string and a pointer to anSDL_Renderer
. We’ll use this to load our texture and keep that code away from our json parsing code.
Note that the constructor takes a string (path to the json file) and a pointer to an SDL_Renderer
(for texture loading). The class does NOT have a default constructor.
With any look your header file for TextureAtlas
will look something like this:
Now it’s time to dive into implementing this class. Go ahead and create your cpp file, we’ll start with the constructor, which is going to be doing most of the work in the class.
The first thing we need to do it open our json file (this is where the file stream comes into play):
Next, we’re going to need to get the root of our json file. Therfore, declare a Json::Value
object and name it root. This object will correspond to the first curly brace of the file, and it’s children will be everything before the closing brace.
Notice how we’re using the bitshift operator to get the root from the filestream. We can now output this to the console with:
At this point, if you were to build and run your program, you would see your json file printed neatly inside the console (if it is indeed a valid json file and you created an instance of TextureAtlas inside main()
).
Now it’s time to load the texture, so call loadTexture
and pass root["TextureAtlas"]["imagePath"].asCString()
and the pointer to the renderer. TextureAtlas
is a child of root (in the example json file) and imagePath
is a child of TextureAtlas
. Jsoncpp uses square brackets to access children, it’s that easy.
However, our loadTexture
method doesn’t take a Json::Value
as an arguement, it takes a string, so we get the value of imagePath
as a C string (if you’re not using c-style strings in your code, you can use .asString()
instead). (Don’t worry about the implementation of loadTexture, we’ll cover that soon.)
Time to parse our subtextures! Once again, declare a Json::Value
object; call it subtextures or something else appropriate. Assign it to be like so:
Instead of imagePath
this time, we’re looking for SubTexture
(which is an array). We’re going to need to iterate through the array of subtextures and, for each one, push an SDL_Rect
(pointer) into our map, using the entries name as the key. The for-loop is similar to any other and can be declared like this: (note the use of Json::Value::size()
)
Because we’re iterating though an array, we can now use the index of the for-loop to access each entry in the json file. Go ahead and declare a string for the name of the entry and and SDL_Rect
pointer for the bounds.
Woah, let’s just review those assignments for a sec. We get the current entry with subtextures[index]
and append ["name"].asString()
to get the name of the current entry from the file and parse it as a string. We then assign the x
, y
, width
and height
values in the same way, but with .asInt()
instead of .asString()
.
But Alex, the example project’s code is different! It has atoi() and .asCString()! What’s going on???
Don’t panic! The program I’m using to pack my textures creates the json file with all values as strings. If you’re json file has quotes around the numerical values, you’ll have to do the same. What is the same, you ask? atoi() parses a c-style string to an integer, so I’m getting each value as a c-style string and parsing it that way.
Why didn’t you take the quotes out for the tutorial then??
So that, if you’re using Shoebox or a similar program, you can fix the problem now and not be trawling the internet for solutions. *drops mic*
Back to business: at the end of the loop, insert the rectangle into the map with the name as the key. Congratulations, you’ve parsed a json file.
Moving on: the loadTexture method is straightforward and pretty much the same wherever to find it / write it.
You could return the pointer to the texture, instead of setting the member variable. It’s just an alternative.
The operator[]
and getTexture
methods are even more straightforward, being just getters:
Congratz again, you’ve just completed the TextureAtlas
class. With little work, this can be adapted/mutilated to work with SFML(the successor to SDL) or any other media library for that matter. (Heck, with a wrapper class for texture, you can make it agnostic).
It’s time to return to main.cpp
and tie up the loose ends.
Write a for-loop to populate the array of rectangles here with the corresponding ones from the atlas. In my example, my subtextures use the naming scheme (in the json file, and therefore in the atlas): frameNUMBER.png (“frame1.png”, “frame2.png”, …). My loop looks like this:
If you’ve rendered textures to the screen with SDL before (which I hope you have, because this tutorial assumes that knowledge), then you know you’ll need to define a destination rectangle:
All that’s left is to write a render loop and draw the current frame to the screen (incrementing the index each time).
Voila. Animation with a texture packer.
If you want to continue from this point, you can take the variables from main and create a Sprite class and/or Animation class.