Home > Game Development Walkthroughs > 2D Platform Games Part 7: Player Character Animation

2D Platform Games Part 7: Player Character Animation

January 28, 2013 Leave a comment Go to comments

IMPORTANT! All of the articles in this series require you to have either Visual Studio 2010 or the Visual C++ 2010 Redistributable Package (4.8MB) installed in order to be able to try the pre-compiled EXEs provided with the examples. The Redistributable Package can be freely bundled with your own applications.

IMPORTANT! From Part 6 onwards, compatibility with Windows Vista in the pre-compiled EXEs has been dropped. To run the pre-compiled EXEs, you must have Windows 7 Service Pack 1 with Platform Update for Windows 7 installed, or Windows 8.

This article builds upon the demo project created in 2D Platform Games Part 6: Creating Platforms and Geometry the Player can be Inside (Ladders, Ropes and Water). Start with 2D Platform Games Part 1: Collision Detection for Dummies if you just stumbled upon this page at random!

Download source code and compiled EXE for the code in this article.

Figure 1. A sprite sheet of our player. This is a 160x160 sprite containing 4 rows of 20x40 player sprites at 8 columns each. The first row of sprites represents the player walking left, the second row represents the player walking right, sprites 1-3 on the third row represent the player jumping left, sprites 4-6 on the third row represent the player jumping right, and the fourth row represents the player climbing up or down a ladder.

Figure 1. A sprite sheet of our player. This is a 160×160 sprite containing 4 rows of 20×40 player sprites at 8 columns each. The first row of sprites represents the player walking left, the second row represents the player walking right, sprites 1-3 on the third row represent the player jumping left, sprites 4-6 on the third row represent the player jumping right, and the fourth row represents the player climbing up or down a ladder.

After the trauma of player physics, collision detection and surface dynamics it’s probably a good time to take a little breather and tackle a less vexing subject, that of animating your player. We shall look at how to animate the player as they walk left and right along a normal platform or slope, when they jump, and when they climb or descend a ladder. This covers all the types of animation we need so far in our project, and gives a flavour of the maths involved which should make it easy to adapt to new animations as and when you need them.

We are going to store the frames of animation in a sprite sheet. This is essentially a single bitmap which contains a number of smaller bitmaps in a grid where each bitmap is of fixed width and height and represents a single frame of animation (see Figure 1). We shall use the Simple2D Animation class to create time-stepped counters which automatically provide the current frame number to use in the animation based on the time elapsed, but if you don’t use Simple2D you can easily re-create this, or pilfer it from the Simple2D source code.

Setting up the animations

Start by creating three animation frame counters in the main class definition. We’ll also need to know which way the player is facing so we can decide whether to play a ‘walking left’ or ‘walking right’ animation, as well as to keep the player facing in the same direction when they come to a standstill (this will contain either 1 or -1 to indicate the X direction of movement):

// Player movement animation functions and flags
Animation playerXanim;
Animation playerJumpAnim;
Animation playerLadderAnim;
int playerXfacing;

In the class constructor, replace the line which loads the static player sprite with the new file:

/* REPLACE */
player = MakeImage(L"stickman.png");

/* WITH */
player = MakeImage(L"stickman-anim.png"); // 8x4 tilesheet

Next we’ll configure the initial animations (also in the class constructor):

// Setup player movement animations
playerXanim = Animation::FromTo(0, 7, 500);
playerJumpAnim = Animation(Animations::Linear, 200, 2.f, 0.f, Animation::Clamp);
playerLadderAnim = Animation::FromTo(0, 7, 500);

playerXanim is a timer which returns a value from 0-7 (representing one of 8 frames of animation – in fact, the index in the sprite sheet to the correct sprite on the first or second row depending on the player’s direction of movement), repeating every 500ms (0.5 seconds). playerLadderAnim does exactly the same thing.

The jump animation in playerJumpAnim is different. While we want the walking and ladder animations to repeat endlessly until the player stops moving, our jump animation has the player bringing his legs into the air for 3 frames when the jump starts. After this, we want to only show the final frame of the animation until the player lands. The timer created for playerJumpAnim essentially works the same as the other two, except that it returns a value (frame index) from 0-2 over the course of the first 200ms from when the animation begins, and after that always returns 2, because it is clamped at its highest value.

How the frame counter works

Some explanatory notes about the Animation class before we continue:

  • Reset() on an animation resets it to the first frame, marks the current time and starts/unpauses itself
  • Reset(true) resets it to the first frame but clamps (freezes) the animation at the first frame by pausing it
  • Pause() pauses the animation on the current frame
  • Pause(false) resumes (unpauses) the animation and it continues from where it left off

Remember that although I’ve written things like ‘starts from the first frame’, the Animation class itself does not handle animation; it simply returns a frame index into the sprite sheet based on the amount of elapsed time and the settings specified when the instance of Animation was created. Later we’ll use the function Animation::GetAnimOffset() to retrieve the frame index from each instance of the object. So for example with playerXanim, if you call Reset(), then GetAnimOffset() will return 0 for 500/8 ms, then 1 for the next 500/8 ms and so on. It is our job to take that number, find the correct portion of the sprite sheet and do the rendering.

Updating the animation positions

We’re going to need to know the movement vector of the player this frame, before it is updated by the player potentially pressing movement keys. This is so we can compare it with the movement vector after player keyboard input has been processed to determine if we have just started moving in a particular direction, and therefore, whether to trigger a new animation and which one.

Place this code right before the player keyboard input is checked:

// Store current movement vector
// We will use this to determine how to animate the player
float prevSpeedX = speedX, prevSpeedY = speedY;

The bulk of the update work is done right at the end of UpdateObjects(), before the player death flag is checked. Let’s step through it one item at a time.

Walking animation

First, if we weren’t moving left or right on the last frame but we are now, we’ll reset the X animation to the first frame:

// If we just started moving in X, start the X animation at the first frame
if (speedX != 0 && prevSpeedX == 0)
	playerXanim.Reset();

If on the other hand, we are not moving left or right, we want to stop animating the player and just show one frame (which is selected later depending on which direction the player is facing):

// If we just stopped moving in X, clamp the X animation to the first frame
if (speedX == 0)
	playerXanim.Reset(true);

We take a note of which way the player is facing so that we can select from the correct row of the sprite sheet when rendering (the top row if walking left, the 2nd row if walking right):

// Which way the player is facing (used when still)
if (speedX != 0)
	playerXfacing = (speedX > 0? 1 : -1);

Crucially, the direction the player is facing is only updated when walking left or right. When still, the variable is not changed, leaving the player facing the same way as they were walking before they stopped.

Ladder animation

The following code follows on directly from the code above:

// If we just arrived on a ladder, reset the ladder animation to the first frame
if (standingPlatform)
	if (standingPlatform->surface == Ladder)
		if (!previousPlatform)
			playerLadderAnim.Reset(true);
		else if (previousPlatform->surface != Ladder)
			playerLadderAnim.Reset(true);

First we check if we are standing on a platform. If so, we check if it is a ladder. There are then three possibilities:

  1. we weren’t previously standing on a platform, in which case we reset and start the ladder animation
  2. we were previously standing on a platform which wasn’t a ladder, in which case we reset and start the ladder animation
  3. we were previously standing on the same or another ladder, in which case we don’t do anything (the ladder animation will continue without being reset)

We only want the climbing animation to play when we are actually moving up or down, so we add some more code for this:

// If we're on a ladder but just stopped moving in Y, pause the animation
if (standingPlatform)
	if (standingPlatform->surface == Ladder)
		if (speedY == 0 && prevSpeedY != 0)
			playerLadderAnim.Pause();
	
// If we're on a ladder and just start moving in Y, unpause the animation
if (standingPlatform)
	if (standingPlatform->surface == Ladder)
		if (speedY != 0 && prevSpeedY == 0)
			playerLadderAnim.Pause(false);

As shown in the comments, the code merely pauses the animation when the player is not moving up or down, and if they have just started moving up or down, unpauses it. Note how unpause instead of reset is used here: when the player stops moving vertically, the most recently used frame of climbing animation is shown, so for example if the player’s right arm was grabbing the rung above him, he will still be doing this when stopped. When he continues climbing, the animation runs from where it left off, for the most natural-looking effect.

Jump animation

The jump animation is very simple. In the code where the user presses the jump movement button, reset and begin the jump animation in the innermost if loop (which is to say, if the jump action was allowed to begin, otherwise do nothing):

...
if ((standingB && !standingT)
 	|| (standingB && standingT && speedX != 0))
{
	jumping = true;
	jumpKeyDown = true;
	jumpPlatform = standingPlatform;
	speedY = -jumpStartSpeedY;

    /* THIS LINE OF CODE IS NEW */
	// Start jumping animation from the first frame
	playerJumpAnim.Reset();
}
...

At this point, our frame counters all have the correct values to point at the right frames of animation each frame depending what the player is doing. Now we have to select the appropriate sprites and render them.

Rendering the animations

First remove the line of code in DrawScene() which renders the static player sprite from earlier parts of the series:

/* REMOVE THIS LINE */
player->Draw(static_cast<int>(playerX), static_cast<int>(playerY));
Jump animation

We start with jumping because this overrides all other possible animations. Remember that with ladders, we can be jumping while still inside the platform, so we need to test for jumping first and show this animation in preference to the climbing animation. We begin in place of the above line of code hence:

// If in mid-air, use the jumping animation
if (jumping)
	player->DrawPartWH(static_cast<int>(playerX), static_cast<int>(playerY), 20, 40,
		static_cast<int>(floor(playerJumpAnim.GetAnimOffset()) * 20) + (playerXfacing == 1? 60 : 0), 80, 20, 40);

A lot of numbers there. The function prototype for Simple2D’s DrawPartWH() should help clear things up:

// Draw portion of bitmap (srcX, srcY) -> (srcX + srcW, srcY + srcH) scaled to fit (x, y) -> (x + w, y + h) on screen
void DrawPartWH(int x, int y, int w, int h, int srcX, int srcY, int srcW, int srcH, float opacity = 1.0f, float rotation = 0.f);

In short, the first 4 arguments specify where to draw the player on the screen and its width and height. These arguments will always be the same: the location of the player, and the width and height of a single player sprite, which in our game is 20×40.

The next 4 arguments determine which portion of the sprite sheet stored in player to use. Once again the first two of these arguments specify the top-left co-ordinate to use in the sprite sheet, and the last two specify the width and height, which as you see is the same as the player width and height (20×40) as you would expect.

Let’s look more closely at the top-left co-ordinate selected in the sprite sheet. Here, this is:

static_cast<int>(floor(playerJumpAnim.GetAnimOffset()) * 20) + (playerXfacing == 1? 60 : 0), 80

Recall that floor(playerJumpAnim.GetAnimOffset()) will return a frame number from 0-2 depending how many milliseconds we are into the jump (0 from 0-99ms, 1 from 100-199ms, and 2 from 200ms onwards), and that each column on the sprite sheet is 20 pixels (one player width) wide. Multiplying the frame counter by 20 gives us a starting left-hand X pixel on the sprite sheet of either 0, 20 or 40, which is what we want for a left-hand jump. Recall also that frames 0-2 on the 3rd row are for jumps to the left, and frames 3-5 are for jumps to the right. 3 frames of animation are 3 player widths or 60 pixels wide, therefore if we are facing to the right, we add 60 to the left-hand X pixel to use, giving values of either 60, 80 or 100 for the right-hand jump animation.

Next, recall that all the jumping animations appear on the 3rd row of the sprite sheet and that each row is 40 pixels (one player height) tall. Therefore, the Y co-ordinate of the top-left corner to use in the sprite sheet will always be 80, indicating the top pixel row of the 3rd row of sprites.

Armed with this mathematical information, we can see that the call to DrawPartWH() always selects the correct jump animation frame depending on the time elapsed and direction the player is facing.

Ladder animation

This code follows on immediately from the above if statement and executes whenever we are not jumping:

// If on a platform:
else
{
	// Test if we are on a ladder. Note that when standing at the top of a ladder, we want to
	// use the player's normal animation, not the climbing one, so we also check the standing*
	// flags to see whether we are in the ladder or at the top
	bool onLadder = false;
	if (standingPlatform)
		if (standingPlatform->surface == Ladder && (standingT || standingL || standingR))
			onLadder = true;

First we check to see if we are on a ladder. To do this, we must be standing on a platform, it must have a surface type of Ladder and either the top, left or right of the player must be touching it as well as the bottom of the player. This last check is crucial, otherwise when standing on top of a ladder, since we are still considered to be inside it, the climbing animation would be displayed when we really want the normal ‘standing still’ image.

If we are on a ladder (and not just standing on top of one), we render the climbing animation:

	// If on a ladder, use the climbing animation
	if (onLadder)
		player->DrawPartWH(static_cast<int>(playerX), static_cast<int>(playerY), 20, 40,
		static_cast<int>(floor(playerLadderAnim.GetAnimOffset()) * 20), 120, 20, 40);

As you can see the first 4 arguments are – as expected – the same as for the jump animation. This time we select a frame from the 4th row (120 pixels down from the top of the sprite sheet).

Walking animation

The walking animation is basically the same as the ladder animation except that we choose a row from the sprite sheet depending on which direction the player is facing:

	// Otherwise use the normal movement animation
	else
		player->DrawPartWH(static_cast<int>(playerX), static_cast<int>(playerY), 20, 40,
			static_cast<int>(floor(playerXanim.GetAnimOffset()) * 20), (playerXfacing == 1? 40 : 0), 20, 40);
}

Once again the first 4 arguments are the same as before. The 6th argument is of interest here, selecting the top Y pixel from the sprite sheet as either 40 if moving right (the 2nd row), or 0 if moving left (the 1st row).

Making the player’s walking animation vary with the speed of the player

At the moment our player’s legs move at the same speed in the fixed-time walking animation regardless of how fast the player is actually walking. This may look pretty weird (some might say oldskool!) on our slippery ice and sticky treacle platforms. While this type of animation is perfectly fine, with a smidgen of maths we can easily make the player animate relative to his or her actual movement speed.

In DrawScene(), right before the code which selects the animation sprites and renders the player:

// Optional code
// This sets the speed of the player's walking animation according to how fast he/she is moving.
// The faster the player's movement, the smaller the repeat interval of the animation.
// To avoid weird animation effects when decelerating, we first get the current animation position
// and then set it to the same after changing the interval, since deceleration increases the
// interval and therefore puts us earlier in the animation.
double animPos = playerXanim.GetAnimPos();
playerXanim.SetInterval(static_cast<int>(150000 / abs(speedX)));
playerXanim.SetPos(animPos);

The crux of the problem is solved by the middle line of code. All we do is change the interval (how many milliseconds the animation takes to complete and start repeating) so that the faster we are moving, the shorter the interval is, and therefore the quicker we will get through each frame of animation as each will be on-screen for less time. I chose the value of 150000 somewhat arbitrarily; use trial and error until you like the way it looks: higher values slow down the animation, smaller ones speed it up.

The first and third lines are to fix an issue with what happens when the player starts to decelerate. As the player’s speed decreases, the animation’s total time becomes longer, however our absolute time position into the animation remains the same. Imagine that an 8-frame animation is 800ms long and we are 650ms into it – this would put us on frame 6 (counting from 0-7). Now imagine that the player slows down and the animation interval doubles to 1600ms – but we are still only 650ms into it. We are now on frame 3. Since the player tends to decelerate quickly, this leads to a bizarre effect on the screen of a series of incorrect frames being displayed in rapid succession in a backwards animation as the total animation time grows exponentially. To rectify this issue, the first line of code above first retrieves from the Animation class exactly how far (in percentage and fractional percentage) into the animation we are. The animation time is then changed, and we then set our position in the animation to the same (relatively speaking) as it was before, ie. the same percentage. This ensures we stay on the same frame we were on before and at the same relative position in the animation.

Boogie Town

And that’s all there is to it. Animations for climbing ropes, swimming in water (hint: you will need to widen the player sprite and change the collision points) and such will work largely on the same principles as the animation types demonstrated here. A link to the full source code and pre-compiled EXE can be found at the top of the article.

In Part 8, we will look at implementing pass-through platforms, where the player can jump up through them from underneath, but they are solid (and the player does not fall through them) on top. Until next time!

Advertisements
  1. Axel
    September 5, 2016 at 23:17

    Am I aloud to use this in my own video game I am designing for free (no copyright and royalty free)???

  1. January 28, 2013 at 23:46
  2. January 29, 2013 at 02:02
  3. October 13, 2016 at 06:48

Share your thoughts! Note: to post source code, enclose it in [code lang=...] [/code] tags. Valid values for 'lang' are cpp, csharp, xml, javascript, php etc. To post compiler errors or other text that is best read monospaced, use 'text' as the value for lang.

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: