2D Rendering - 12 - Sprite Animation
Download the source code for this post
In my previous tutorial, we introduced a couple of new classes - XNASpriteBatch
and XNASprite
- to help us draw sprites in a more familiar way. In this post, I just want to put those two classes to one side and look at what libGDX provides us with for sprite animation purposes.
By the end of this tutorial, we’ll have a simple application which animates a sprite, as shown in the video below.
TexturePacker
libGDX’s TexturePacker is nothing short of excellent. I’m not going to delve into the details of how to use the tool, as you can just click the link that I have provided.
All you need to know for this tutorial, is that I have used it to pack the frames needed for my demonstration of Sprite animation, and, that we can get hold of the frames for that animation as shown in the source code below.
Initialisation
Let’s have a look at our create()
method and it’s supporting class member variables.
SpriteBatch batch;
private TextureAtlas textureAtlas;
private Animation animation;
private Array<AtlasRegion> frames;
@Override
public void create () {
batch = new SpriteBatch();
textureAtlas = new TextureAtlas("spritesheet.atlas"); // 9.
frames = textureAtlas.findRegions("invader1"); // 10.
animation = new Animation(1/15f, frames); // 11.
}
When I packed my sprites into a texture atlas, TexturePacker
generated a file called spritesheet.atlas
. This file contains all of the information about each sprite frame I packed. The TextureAtlas class deals with de-serialsing that file and provides us with handy methods to get hold of the frames for a particular sprite, along with other useful information.
On line 9, we create an instance of TextureAtlas
, by passing it the path to our spritesheet.atlas
file.
On line 10, we ask the TextureAtlas
for an array of all the AtlasRegions
(frames) belonging to the invader1
sprite. As an added bonus, because of the way I configured TexturePacker
, we receive them in animation order. Note that AtlasRegion has the same parent class as Sprite
- TextureRegion
, which we have seen before. For our purposes, each AtlasRegion
holds a frame of animation for our sprite.
The Animation class makes it a breeze to animate our sprites. On line 11, we create a new Animation
instance and instruct it to animate our sprite frames 15 times per second. In other words, the duration between each frame is 1/15
seconds, or, 0.0666
seconds.
Update
To help cycle the animation frames, in our update()
method we utilise a variable which accumulates the delta time between frames - animationStateTime
.
private float animationStateTime;
private void update(float delta){
animationStateTime += delta;
}
Render
When it is time to render our sprite, we ask our Animation
instance for the frame that we need to draw - AKA the Key Frame
. We do this by passing it the current value of our animationStateTime
variable. You can see this happening on line 6 below.
public void render () {
update(Gdx.graphics.getDeltaTime());
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
batch.draw(animation.getKeyFrame(animationStateTime, true), 0, 0); // 6.
batch.end();
}
The Problem
TexturePacker
, coupled with the Animate
class, makes sprite animation trivial. So, what’s the problem?
Well, if I were modelling a Sprite class, I would say that a Sprite is made up of a collection of Frames. There is no provision for this in libGDX, and it makes things a bit tricky.
For example, when I want to rotate, flip, scale or set the position of a sprite, I would expect that those operations would affect all frames of a sprite. In libGDX’s model, that isn’t true. From what I can see, you either need to apply the operations to all frames, or work out the current key frame, and apply it to that.
JK