PC Games

Orb
Lasagne Monsters
Three Guys Apocalypse
Water Closet
Blob Wars : Attrition
The Legend of Edgar
TBFTSS: The Pandoran War
Three Guys
Blob Wars : Blob and Conquer
Blob Wars : Metal Blob Solid
Project: Starfighter
TANX Squadron

Android Games

DDDDD
Number Blocks
Match 3 Warriors

Tutorials

2D shoot 'em up
2D top-down shooter
2D platform game
Sprite atlas tutorial
Working with TTF fonts
2D adventure game
Widget tutorial
2D shoot 'em up sequel
2D run and gun
Roguelike
SDL 1 tutorials (outdated)

Latest Updates

SDL2 Rogue tutorial
Wed, 29th September 2021

SDL2 Gunner tutorial
Thu, 26th August 2021

SDL2 Shooter 2 tutorial
Tue, 13th July 2021

SDL2 Widget tutorial
Fri, 18th June 2021

SDL2 Adventure tutorial
Tue, 8th June 2021

All Updates »

Tags

android (3)
battle-for-the-solar-system (9)
blob-wars (9)
brexit (1)
code (6)
edgar (6)
games (37)
lasagne-monsters (1)
making-of (5)
match3 (1)
numberblocksonline (1)
orb (2)
site (1)
tanx (4)
three-guys (3)
three-guys-apocalypse (3)
tutorials (8)
water-closet (3)

Books

Intermediate Tutorials

Intermediate Game Tutorial #5 - Advanced animation

Introduction

In this tutorial we will look at more advanced methods of animation.

Note: This tutorial only deals with animating a sprite, it will not go through the steps of how to draw a sprite. If you want to attempt to draw your own sprites, you could try doing a Google search for "sprite tutorials" or "pixel art tutorials", but be warned that drawing sprites takes a lot of time and patience.

Compile and run tutorial15. The program will read the animation data from a file and display the character on screen. Use the arrow keys (not the ones on the numeric pad) to move the character around. Pressing space will make the character jump and pushing down will make it crouch. Pressing Escape or closing the window will exit the program.

An in-depth look

We will store all our animation data in the following Animation structure:

typedef struct Animation
{
    char name[50];
    int frameCount, active;
    int *frameTimer;
    SDL_Surface **frame;
} Animation;
Each animation that we load will have a name to help us idenitfy it. We also store the number of frames that the animation contains, whether or not this animation is active and pointers to the individual frames and how long each frame will last. Our Entity structure also contains a few extra variables:
typedef struct Entity
{
    int active, w, h, onGround;
    int thinkTime, face, state;
    int currentFrame, frameTimer;
    float x, y, dirX, dirY;
    Animation *currentAnim;
    void (*action)(void);
    void (*draw)(void);
} Entity;
face is used to determine which way the Entity is facing, which is either left or right. state is the Entity's current animation state, which will be standing, walking or crouching. The currentAnim points to the animation that the Entity is currently using and currentFrame and frameTimer is the frame index of the current animation and how long to display this frame for.

Before we look at loading the animation data, we will look at the file itself:

PLAYER_CROUCH_RIGHT
1
gfx/player/player_crouch_right.gif 6
PLAYER_CROUCH_LEFT
1
gfx/player/player_crouch_left.gif 6
PLAYER_STAND_RIGHT
1
gfx/player/player_stand_right.gif 6
PLAYER_STAND_LEFT
1
gfx/player/player_stand_left.gif 6
PLAYER_WALK_RIGHT
8
gfx/player/player_walk_right00.gif 6
gfx/player/player_walk_right01.gif 6
gfx/player/player_walk_right02.gif 6
gfx/player/player_walk_right03.gif 6
gfx/player/player_walk_right04.gif 6
gfx/player/player_walk_right05.gif 6
gfx/player/player_walk_right06.gif 6
gfx/player/player_walk_right07.gif 6
PLAYER_WALK_LEFT
8
gfx/player/player_walk_left00.gif 6
gfx/player/player_walk_left01.gif 6
gfx/player/player_walk_left02.gif 6
gfx/player/player_walk_left03.gif 6
gfx/player/player_walk_left04.gif 6
gfx/player/player_walk_left05.gif 6
gfx/player/player_walk_left06.gif 6
gfx/player/player_walk_left07.gif 6
The file is of the following form: name of the Animation, the number of frames in the Animation and then the path to each of the frames and how long to display it for. Putting this information in a text file makes it readable and also requires no recompilation to add extra frames or change their duration. Now we will look at loading this Animation, which is handled in animation.c:
void loadAnimationData(char *filename)
{
    char frameName[255];
    int id, i;
    FILE *fp;

    fp = fopen(filename, "rb");

    if (fp == NULL)
    {
        printf("Failed to open animation file: %s\n", filename);

        exit(1);
    }

    while (!feof(fp))
    {
        for (id=0;id<MAX_ANIMATIONS;id++)
        {
            if (animation[id].active == 0)
            {
                animation[id].active = 1;

                break;
            }
        }

        if (id == MAX_ANIMATIONS)
        {
            printf("More free slots for animation: %s\n", filename);

            exit(1);
        }

        fscanf(fp, "%s", animation[id].name);
        fscanf(fp, "%d", &animation[id].frameCount);

        /* Allocate space for the animation */

        animation[id].frame = (SDL_Surface **)
            malloc(animation[id].frameCount * sizeof(SDL_Surface *));

        if (animation[id].frame == NULL)
        {
            printf("Ran out of memory when creating the animation for %s\n",
                animation[id].name);

            exit(1);
        }

        /* Allocate space for the frame timer */

        animation[id].frameTimer = (int *)
            malloc(animation[id].frameCount * sizeof(int));

        if (animation[id].frameTimer == NULL)
        {
            printf("Ran out of memory when creating the animation for %s\n",
                animation[id].name);

            exit(1);
        }

        /* Now load up each frame */

        for (i=0;i<animation[id].frameCount;i++)
        {
            fscanf(fp, "%s", frameName);

            animation[id].frame[i] = loadImage(frameName);

            if (animation[id].frame[i] == NULL)
            {
                printf("Failed to load animation frame %s\n", frameName);

                exit(1);
            }

            fscanf(fp, "%d", &animation[id].frameTimer[i]);
        }
    }
}
First, we open the animation file and then start reading all of the data from the file. Before we add an Animation we need to check to see if there is a free slot available for this new Animation. When we find a free slot we set the name of the animation and the number of frames the Animation contains. Next we allocate space for all the frame images and the timings for each one. We then loop through all the frames and load the image up for each one along with the timer for each frame. As we can see, there is nothing particularly special about this function. The only other function that we will look at in this file is the setAnimation function:
void setAnimation(char *name, Entity *entity)
{
    int i;

    for (i=0;i<MAX_ANIMATIONS;i++)
    {
        /* Loop through all the animations and find the one that matches the name */

        if (strcmp(name, animation[i].name) == 0)
        {
            /* Set the animation and the frameTimer to 0 */

            entity->currentAnim = &animation[i];
            entity->currentFrame = 0;
            entity->frameTimer = entity->currentAnim->frameTimer[entity->currentFrame];

            return;
        }
    }

    /* If the animation couldn't be found, then exit */

    printf("Could not find animation %s\n", name);

    exit(1);
}
setAnimation takes two arguments, the name of the Animation we are looking for and the Entity to apply the Animation to. We loop through all of the animations and if we find an Animation whose name matches the one we are looking for then we set the Entity's currentAnim variable to point to this animation, set the Entity's currentFrame to 0, which is the first frame of any Animation and set the frameTimer to the frameTimer of this Animation frame. We will now look at how to apply these functions in player.c.

player.c contains 4 functions, which we will look at in turn, starting with loadPlayer:

void loadPlayer()
{
    loadAnimationData("data/anim/player.dat");
}
loadPlayer calls the loadAnimationData function and passes in the data file containing the player's animations. This function is called in the main function. Next we will look at initPlayer:
void initPlayer()
{
    player.x = player.y = 0;
    player.dirX = player.dirY = 0;

    player.thinkTime = 0;

    player.face = RIGHT;

    setAnimation("PLAYER_STAND_RIGHT", &player);
}
This function simply sets up the the player's coordinates, velocity and facing direction and sets the initial Animation to be standing right. It is important that this function is called after the player's Animation is loaded otherwise the call to setAnimation will fail to find the animation. The next function processes the player and updates their Animation:
void doPlayer()
{
    player.dirX = 0;

    /* Gravity always pulls the player down */

    player.dirY += GRAVITY_SPEED;

    if (player.dirY >= MAX_FALL_SPEED)
    {
        player.dirY = MAX_FALL_SPEED;
    }

    if (input.left == 1)
    {
        player.face = LEFT;
    }

    else if (input.right == 1)
    {
        player.face = RIGHT;
    }

    if (input.down == 1 && player.onGround == 1)
    {
        if (player.face == RIGHT && player.state != CROUCH_RIGHT)
        {
            player.state = CROUCH_RIGHT;

            setAnimation("PLAYER_CROUCH_RIGHT", &player);
        }

        else if (player.face == LEFT && player.state != CROUCH_LEFT)
        {
            player.state = CROUCH_LEFT;

            setAnimation("PLAYER_CROUCH_LEFT", &player);
        }
    }

    if (input.left == 1 && input.down == 0)
    {
        player.dirX -= PLAYER_SPEED;

        if (player.state != WALK_LEFT)
        {
            player.state = WALK_LEFT;

            setAnimation("PLAYER_WALK_LEFT", &player);
        }
    }

    else if (input.right == 1 && input.down == 0)
    {
        player.dirX += PLAYER_SPEED;

        if (player.state != WALK_RIGHT)
        {
            player.state = WALK_RIGHT;

            setAnimation("PLAYER_WALK_RIGHT", &player);
        }
    }

    else if (input.left == 0 && input.right == 0 && input.down == 0)
    {
        if (player.face == RIGHT && player.state != STAND_RIGHT)
        {
            player.state = STAND_RIGHT;

            setAnimation("PLAYER_STAND_RIGHT", &player);
        }

        else if (player.face == LEFT && player.state != STAND_LEFT)
        {
            player.state = STAND_LEFT;

            setAnimation("PLAYER_STAND_LEFT", &player);
        }
    }

    if (input.jump == 1 && player.onGround == 1)
    {
        if (player.face == RIGHT && player.state != STAND_RIGHT)
        {
            player.state = STAND_RIGHT;

            setAnimation("PLAYER_STAND_RIGHT", &player);
        }

        else if (player.face == LEFT && player.state != STAND_LEFT)
        {
            player.state = STAND_LEFT;

            setAnimation("PLAYER_STAND_LEFT", &player);
        }

        player.dirY = -JUMP_HEIGHT;

        input.jump = 0;
    }

    player.onGround = 0;

    player.x += player.dirX;
    player.y += player.dirY;

    if (player.x < 0)
    {
        player.x = 0;
    }

    else if (player.x + player.w >= SCREEN_WIDTH)
    {
        player.x = SCREEN_WIDTH - player.w - 1;
    }

    if (player.y + player.h >= (SCREEN_HEIGHT / 2))
    {
        player.onGround = 1;

        player.y = (SCREEN_HEIGHT / 2) - player.h;
    }
}
Firstly, we apply gravity to the player as in the previous tutorial. We then check the left and right movements to determine which way the player is facing. We do this first to help determine which Animation to choose. The next part checks which Animation we need to apply:
if (input.down == 1 && player.onGround == 1)
{
	if (player.face == RIGHT && player.state != CROUCH_RIGHT)
	{
		player.state = CROUCH_RIGHT;

		setAnimation("PLAYER_CROUCH_RIGHT", &player);
	}

	else if (player.face == LEFT && player.state != CROUCH_LEFT)
	{
		player.state = CROUCH_LEFT;

		setAnimation("PLAYER_CROUCH_LEFT", &player);
	}
}
If down is being pressed and the player is on the ground, then we need to set the crouch Animation. Before we do this though we need to check if the player's Animation is already crouching. We do this by checking the state variable. If the state is already set to CROUCH_RIGHT and the player is facing right then we will not reset the Animation. Otherwise we will call setAnimation to search fo the PLAYER_CROUCH_RIGHT Animation and set the state of the player to CROUCH_RIGHT. We apply the same logic to facing left. If down is not being pressed but left or right is, then we need to apply the walking Animation:
if (input.left == 1 && input.down == 0)
{
	player.dirX -= PLAYER_SPEED;

	if (player.state != WALK_LEFT)
	{
		player.state = WALK_LEFT;

		setAnimation("PLAYER_WALK_LEFT", &player);
	}
}
As we can see, the code is very similar to the code for crouching, except that we also move the player and the code for standing is exactly the same. Finally, we will look at the drawPlayer:
void drawPlayer()
{
	player.frameTimer--;

	if (player.frameTimer <= 0)
	{
		player.currentFrame++;

		if (player.currentFrame >= player.currentAnim->frameCount)
		{
			player.currentFrame = 0;
		}

		player.frameTimer = player.currentAnim->frameTimer[player.currentFrame];

		player.w = player.currentAnim->frame[player.currentFrame]->w;
		player.h = player.currentAnim->frame[player.currentFrame]->h;
	}

	drawAnimation(player.currentAnim, player.currentFrame, player.x, player.y);
}
Each frame we decrement the player's frameTimer and if it is less than or equal to 0, we move to the next frame, wrapping around to frame index 0 if we reach the current Animation's frameCount. Finally, we call drawAnimation to draw the image to the screen.

We will not look at the other files since there is nothing in them that has not been covered before.

Conclusion

This is one way to do Animation. Some games may hardcode animations into particular array indexes and search for them that way rather than doing a string comparison. In the next tutorial we will put together everything from previous tutorials to make a very basic platform game.

Downloads

Source Code

Mobile site