Game Character Animations Management

Last Update: 08/31/2009

Introduction
The aim of this tutorial is to provide a smart solution to animate 2D game's characters.
The idea behind this consists in using a Finite State Machine which has states to represent animations and transitions to represent actions - like the pression of a key - which might cause an animation to change from an idle sequence to a walking one. While the FSM handles all the logic behind our animations an animation manager class displays the right animation sequence according to the FSM state. This way we keep all the logic and displaying operations apart from other game routines.

Source Code
You can download the source code of the entire project here. This program is written in C++ with SDL and you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

How does it work
Here's an UML class diagram which shows how the classes are related togheter:

We got the Entity class representing a character - in terms of position, logic and graphic - inside our game. The Entity has a State Machine to handle all the logic behind our animations and a Sprite reference. The Sprite class provides graphics data to our Entity with an AnimationManager and SpriteSheet references. Let's focus on the purpose of each of these classes.

The SpriteSheet class keeps inside the pixel data of our sprites. Sprite sheets are composed of a series of animations frames which need to be looped in order to give life to our game characters. Each frame can be identified with x, y coordinates respect to the origin of the image and width and height. Here's the sprite sheet we are using for this tutorial:



So the SpriteSheet class just carries the pixel data of our Entity. Since in this tutorial we are using SDL all the graphic data is stored inside a SDL_Surface. No one stops you from using other libraries for blitting operations so I decided to keep all the graphic data in a class which will be the only portion of code you need to change to fit other libraries needs.

The AnimationManager class keeps information about animation sequences giving a logical meaning to the data inside the SpriteSheet. An animation sequence is a set of frames displayed in a certain order with an interval of time for each frame. Here's the data structures we use to keep informations about sequences:

typedef vector< SDL_Rect > Rects;

typedef struct
{
	int id;
	Rects frames;
} Sequence;

typedef vector< Sequence > Sequences;


Basically the AnimationManager has a Sequences container. Each Sequence has a container of SDL_Rect to keep informations about coordinates of each frame. I also assign an id for every sequence.
Let's assume we want to create and push in our AnimationManager's sequences attribute a standing position animation. Giving a look at the sprite sheet we decide that the frames which will compose the standing sequence are just two as you can see from the following image:



Here's the code we are gonna write inside the AnimationManager's createSequences function in order to push the standing sequence inside our frames vector.

Sequence standSequence;
standSequence.id = STAND;

SDL_Rect anim1 = {  0, 1, 24, 23 };
SDL_Rect anim2 = { 26, 1, 24, 23 };

standSequence.frames.push_back( anim1 );
standSequence.frames.push_back( anim1 );
standSequence.frames.push_back( anim1 );
standSequence.frames.push_back( anim1 );
standSequence.frames.push_back( anim1 );
standSequence.frames.push_back( anim1 );
standSequence.frames.push_back( anim2 );

sequences.push_back( standSequence );


First of all we declare a sequence object assigning the STAND id to it. Then we declare two SDL_Rect containing the coordinates and size of the two frames we want to use for the standing sequence. After this we push the rect1 and rect2 inside frames of our standSequence variable; rect1 is pushed six times since we want the first frame ( the character with the open eyes ) to be displayed for a longer time interval than the second frame. When we've finished with this we push the standSequence inside the AnimationManager's sequences container.
Once all the animation sequences are set the AnimationManager update function is called in each application cycle to play the current character's animation.

void AnimationManager::update( Entity *entity, int x, int y )
{

	Utils::Blit( entity->getSprite()->getSpriteSheet()->getSheet(),
		     x, y,
	             &(sequences[ currentSequence ].frames[ currentFrame ]) );

	if( SDL_GetTicks() - lastTick > millisPerFrame )
	{
		lastTick = SDL_GetTicks();

		currentFrame ++;

		if( currentFrame >= sequences[ currentSequence ].frames.size() )
		{

			currentFrame = 0;
		}
	}
}


Update function first performs blitting operations calling a Blit function inside an Utility class. The Blit function takes as parameters the sprite sheet of the entity we want to display, x and y coordinates and a SDL_Rect reference to get the right pixel data inside the SpriteSheet class. Then, if the time for each frame ( millisecPerFrame ) has elapsed we increase the currentFrame value in order to display the next frame. A good idea would be to assign a millisecPerFrame value to each frame of our sequence to gain an higher degree of freedom for our animations thus avoiding multiple push of the same frame inside the frames container to make an image stay still for a longer period. In this case also a graphic tool would be very helpful to create and export animation sequences.

So far we've seen the SpriteSheet and AnimationManager classes which are attributes of the Sprite class and have the role to display on screen the graphic data of our entity. Let's move now to the StateMachine class to see how do we decide which graphic data, in particular, which sprite's frame to display.

The StateMachine class is essentialy a digraph whose nodes are states and edges are actions, like the pression of a key, which make our Entity to change state. The potential of the state machine consists in the flexibility it gives us to set new nodes and actions and the strictness the state machine adopts when has to follow the rules we have set. Here's a scheme of a fsm used to handle Entity's animations:





Let's assume our Entity is in the STAND state, the only way to get to the WALK state is to send to the fsm a WALK_KEY_PRESSED action by pressing a directional key on the keyboard. It is then up to the fsm to process the received action, change the entity's state and set the right sequence in the AnimationManager. Here's how do we set the rules for the fsm:

pStateMachine = new StateMachine();
pStateMachine->setRule( STAND, WALK_KEY_PRESSED, WALK  );
pStateMachine->setRule( WALK,  ZERO_X_VELOCITY,  STAND );
pStateMachine->setRule( STAND, JUMP_KEY_PRESSED, JUMP  );
pStateMachine->setRule( JUMP,  GROUND_COLLISION, STAND );
pStateMachine->setRule( WALK,  JUMP_KEY_PRESSED, JUMP  );


These rules are set after fsm creation inside the createEntity function of our concrete game Entity. The first parameter is an id of the initial state, the second parameter is an action which makes the Entity move from the initial state to the resulting state, the third parameter. These rules are saved inside a bidimensional array of STATES_LENGTH rows and ACTIONS_LENGTH columns. Here's how the fsm handles an incoming action:

void StateMachine::performAction( Entity* entity, int action )
{

	switch( smTable[ entity->getState() ][ action ] )
	{

		case STAND:
			entity->setState( smTable[ entity->getState() ][ action ] );
			entity->getSprite()->getAnimationManager()->setSequence( STAND );
		break;
		case WALK:
			entity->setState( smTable[ entity->getState() ][ action ] );			
			entity->getSprite()->getAnimationManager()->setSequence( WALK );
		break;
		case JUMP:
			entity->setState( smTable[ entity->getState() ][ action ] );
			entity->getSprite()->getAnimationManager()->setSequence( JUMP );
			entity->setYVel( entity->getXVel() - 10 );
		break;
	}
}


The fsm checks the resulting state deferring the smTable bidimensional array. If the resulting value is a consistant state the fsm performs certains operations like setting a yVel velocity of the Entity in case of JUMP state. It is then our role to decide when to send actions to the state machine. Here is a series of actions which can be sended to the fsm from the Entity::Update function:

void ConcreteEntity::update()
{

	if( yPos + currentRect.h > GROUND_LEVEL )
	{

		yPos = GROUND_LEVEL - currentRect.h;
		yVel = 0;

		pStateMachine->performAction( this, GROUND_COLLISION );
	}

	if( xVel.isZero() )
	{

		pStateMachine->performAction( this, ZERO_X_VELOCITY );
	}
	
}



Summing Up
This solution demonstrates how game character's animations can be managed applying strict rules handled by a finite state machine and an animation manager class to play animations following frames sequences. For any questions, feedbacks or errors reporting please contact me at this email address.