Frame by frame animations in LibGDX

Intoduction

When we need to animate our game objects, one of the methods we have is the frame by frame animations. It’s the classic animation method, and consists on drawing a serie of images that, when they are showed in sequence, it seems the element is doing some action.

runnerFor example, if we want to show a running character, we should have a serie of images, each with a little advance over the previous image, so as when they are displayed them sequentially, we can see the character moving:

charset

 
 

Frame by frame animations in LibGDX

La Animation class

The class responsible of the management of frame by frame animations is Animation. Its work involves in calculate the frame that must rendered at each time, being us the responsibles of draw the result.

This class needs these four elements to work:

  • Images that compose the animation. Each of this images is a frame of the animation.
  • Time to wait between a frame and the next one.
  • Play mode. The possible modes are defined in the enum Animation.PlayMode. It tells to our Animation object the order in which the frames must be rendered. The possible modes are:
    • NORMAL: The frames are displayed from first to last. Without looping. So when the animation gets to the last frame, then always is returned the last frame.
    • REVERSED: The frames are displayed from last to first. Without looping. So when the animation gets to the first frame, then always is returned the first frame.
    • LOOP: The frames are displayed from first to last. With looping. So when the animation gets to the last frame, then starts again with the first one.
    • LOOP_REVERSED: The frames are displayed from last to first. With looping. So when the animation gets to the first frame, then starts again with the last one.
    • LOOP_PINGPONG: The frames are displayed from first to last. When it gets to the last one, it starts to draw from last to first, and then again from first to last, last to first, …, ping pong 馃檪
    • LOOP_RANDOM: The frames are displayed randomly, without a defined order.
  • Elapsed time from the beginning of the animation. Each time we draw on screen, we must pass the elapsed time to the Animation, and it return us the frame to draw depending on the elapsed time and the play mode.

 
 

Example

Normally, in videogames, each character will have several animations which will be drawn depending of character’s state (walking, running, jumping, etc.). In order to make this example the most clear possible, I will not use states, and I am going to show only one element with one animation.

 

Images

For this example i’m going to use the file (charset.png) which stores all the images that compose the animation:

charsetAlso I will make the file charset.atlas, knowing that each frame takes 80 pixels for width and 120 for height. In the atlas, I use the same name for all the frames, and I assign an index to each one of them. The file looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
charset.png
format: RGBA8888
filter: Linear,Linear
repeat: none
running
  rotate: false
  xy: 0, 0
  size: 80, 120
  orig: 80, 120
  offset: 0, 0
  index: 0
running
  rotate: false
  xy: 80, 0
  size: 80, 120
  orig: 80, 120
  offset: 0, 0
  index: 1
running
  rotate: false
  xy: 160, 0
  size: 80, 120
  orig: 80, 120
  offset: 0, 0
  index: 2
(continue ...)

This way, we can easily get all frames that compose the animation:

public class GdxAnimationExample extends ApplicationAdapter {
    ...
    // Atlas with all the frames defined in "charset.atlas"
    private TextureAtlas charset;
    ...
 
    @Override
    public void create () {
        ...
        // Frames loading from "charset.atlas"
        charset = new TextureAtlas( Gdx.files.internal("charset.atlas") );
 
        // Getting the images for "running" animation
        Array<AtlasRegion> runningFrames = charset.findRegions("running");
        ...
    }
    ...
}

 

Building the animation

Once we have loaded the images that compose the animation, we can build it. For this example we want to play it for ever, so we use the play mode PlayMode.LOOP:

public class GdxAnimationExample extends ApplicationAdapter {
    ...
    // Time which each frame keeps on screen
    private static float FRAME_DURATION = .05f;
 
    // Running animation
    private Animation runningAnimation;
    ...
 
    @Override
    public void create () {
        ...
        // Building of the animation
        runningAnimation = new Animation(FRAME_DURATION, runningFrames, PlayMode.LOOP);
        ...
    }
    ...
}

 

Drawing each frame

At the time to draw, the object runningAnimation will return us the frame that we need to draw depending of the elapsed time. For this reason we have a local variable, elapsed_time, where we store the elapsed time from de beginning of the animation:

public class GdxAnimationExample extends ApplicationAdapter {
    ...
    // Frame that must be rendered at each time
    private TextureRegion currentFrame;
 
    // Elapsed time
    private float elapsed_time = 0f;
 
    // Auxiliar variables to know where to draw the picture to center it on the screen
    private float origin_x, origin_y;
    ...
 
    @Override
    public void render () {
        ...
        // Elapsed time
        elapsed_time += Gdx.graphics.getDeltaTime();
 
        // Getting the frame we must draw at this moment
        currentFrame = runningAnimation.getKeyFrame(elapsed_time);
 
        // Drawing on the screen
        batch.begin();
        batch.draw(currentFrame, origin_x, origin_y);
        batch.end();
        ...
    }
    ...
}

 

Complete example

Combining all of the above, the class looks like this:

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
 * Frame-by-frame animation example with LibGDX:
 * 
 * http://www.pixnbgames.com/blog/libgdx/frame-by-frame-animations-in-libgdx/
 * 
 * @author angel
 */
public class GdxAnimationExample extends ApplicationAdapter {
 
    // Time while each frame keeps on screen
    private static float FRAME_DURATION = .05f;
 
    // To draw on screen
    private SpriteBatch batch;
 
    // Atlas with the definition of the frames "charset.atlas"
    private TextureAtlas charset;
 
    // Frame that must be rendered at each time
    private TextureRegion currentFrame;
 
    // Running animation
    private Animation runningAnimation;
 
    // Elapsed time
    private float elapsed_time = 0f;
 
    // Auxiliar variables to know where to draw the picture to center it on the screen
    private float origin_x, origin_y;
 
 
    @Override
        public void create () {
        batch = new SpriteBatch();
 
        // Frames loading from "charset.atlas"
        charset = new TextureAtlas( Gdx.files.internal("charset.atlas") );
 
        // Frames that compose the animation "running"
        Array<AtlasRegion> runningFrames = charset.findRegions("running");
 
        // Building the animatino
        runningAnimation = new Animation(FRAME_DURATION, runningFrames, PlayMode.LOOP);
 
        // Calculates the x and y position to center the image
        TextureRegion firstTexture = runningFrames.first();
        origin_x = (Gdx.graphics.getWidth()  - firstTexture.getRegionWidth())  / 2;
        origin_y = (Gdx.graphics.getHeight() - firstTexture.getRegionHeight()) / 2;
    }
 
 
    @Override
    public void render () {
        // Clear the screen
        Gdx.gl.glClearColor(1.0f, .8f, .667f, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
 
        // Elapsed time
        elapsed_time += Gdx.graphics.getDeltaTime();
 
        // Getting the frame which must be rendered
        currentFrame = runningAnimation.getKeyFrame(elapsed_time);
 
        // Drawing the frame
        batch.begin();
        batch.draw(currentFrame, origin_x, origin_y);
        batch.end();
    }
 
 
    @Override
    public void dispose() {
        // Resource releasing
        charset.dispose();
        super.dispose();
    }
}

 

 

Source code

You can get the sample project from my GitHub repository.

 

Regards,

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*