Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
70 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Contents
 Introduction
 Character Image
 Files

 Checking for
 Movement

 Collision Detection
 Animation
 NPCs and Random
 Movement

 Ordering Characters

 Printable version
 Discuss this article
 in the forums



The Series
 Beginning Windows
 Programming

 Using Resources
 in Win32 Programs

 Tracking Your
 Window/Using GDI

 Introduction
 to DirectX

 Palettes and Pixels
 in DirectDraw

 Bitmapped Graphics
 in DirectDraw

 Developing the
 Game Structure

 Basic Tile Engines
 Adding Characters
 Tips and Tricks

Animation

The first thing we should do is to think about what we want to happen when the player presses an arrow key. You may be thinking, "Oh, that's simple: the character should walk from one tile to the next," but that is not always the case. In pixel by tile (PxT) scrolling, this is what happens. As any movement key is pressed, the character walks from one tile to the next. An alternate method is called pixel by pixel (PxP) scrolling, and allows you a bit more freedom, because as soon as the player releases the arrow key, the character stops moving, even if he's between two tiles. The good part about PxP scrolling is that it can take away from the feeling that your character is locked into a grid, especially if you also employ eight-directional movement. The bad part is that it makes collision detection a bit tricky. I'm not going to cover PxP scrolling here, since it's a good idea to get PxT up and running first so you can get a feel for creating a simple game engine, but think about it... it does require a bit more thought, but once you've come up with a good way to handle it, it's easily worth a little extra effort.

All right, so we're going to move one tile at a time. Our animation code checks if animation is necessary by simply looking at the xMove or yMove member of a character's CHARMOVE structure. If it is nonzero, the character is moving. So what do we do from there? Well, remember that at the beginning of an animation, the character is standing on one tile, but his location variables say he's on the next. This is possible because one of the character's offsets is set to 32 or -32. So for each frame of animation, we gradually move the offset back towards zero, and as a result, the character will move towards his correct location. It's really that simple.

The only thing that even deserves any thought is to consider which frames are displayed when. We have three frames of animation for each direction: standing still, right foot forward, and left foot forward. This animation should play in the sequence: 0, 1, 0, 2, 0, 1, 0, 2, etc. So it doesn't look like he's taking ridiculously small steps, we'll say that a movement from one tile to the next is done in four steps: 0 to 1, 1 to 0, 0 to 2, 2 to 0. This will actually appear as two full steps on the screen. To set a reasonable character speed, let's shift the character by four pixels every frame. So a movement from one tile to the next will look like this:

Game FrameOffset (absolute value)Animation Frame
0320
1280
2241
3201
4160
5120
682
742
800

Hopefully this will clear things up a bit. The leftmost column is simply a numbering system for game frames -- these are happening at about 30 per second. The middle column represents player.move.xOffset for horizontal movement, or player.move.yOffset for vertical. This is an absolute value because it may in fact range from -32 to 0, for instance if the character is walking up instead of down. The important thing to note is that this gradually approaches 0. This represents the character's actual movement onscreen, since xOffset and yOffset, as you should remember, are used in the calculation for where the player appears on the map. When the offset reaches 0, the appropriate movement variable -- that is, xMove or yMove, is reset to 0, and the character stops. The final column represents the animation frame to display. Note that it goes in the sequence we decided on earlier. We will add this number to player.move.nFace to get the actual frame number within the image file. Remember, that's why we used 0, 3, 6, and 9 for the movement directions.

To further demystify things, let's get some code down that implements the table above. What needs to happen? First, the code should check if movement in a given direction is occurring. Second, the offset should be updated. Third, the code should check to see if the character has finished moving from one tile to the next. If so, reset the movement variable. We could write this in two cases, one for horizontal movement and one for vertical, but just in the interest of keeping things easy to understand, I'm going to split it into four instead, one for each direction. I'm also going to replace player.move with simply move, because eventually this will be a function that gets used for players and NPCs. Let's see how it goes.

// first check for movement south
if (move.yMove > 0)
{
  move.yOffset += 4;
  if (move.yOffset == 0)
    move.yMove = 0;
}

// now check north
if (move.yMove < 0)
{
  move.yOffset -= 4;
  if (move.yOffset == 0)
    move.yMove = 0;
}

// check movement east
if (move.xMove > 0)
{
  move.xOffset += 4;
  if (move.xOffset == 0)
    move.xMove = 0;
}

// check movement west
if (move.xMove < 0)
{
  move.xOffset -= 4;
  if (move.xOffset == 0)
    move.xMove = 0;
}

This takes care of moving the player around from step to step, and so there's only one more step remaining: we still have to draw the player on the screen. The only thing that's not obvious about this is how to find the coordinates at which to draw the player. After all, he won't always be at the center of the screen. The answer is simply to calculate his coordinates in terms of world coordinates, and then subtract the camera coordinates from them to get the final rendering position. Then we just get the correct frame like we discussed earlier, and voila, we've finally got what we want! Here's the code:

// set up a default source RECT
RECT rcSrc = {0, 0, 32, 64}, rcDest;
int nFrame;

// first figure out which frame to use
nFrame = move.nFace;
if (((move.xMove) && ((abs(move.xOffset) == 4) || (abs(move.xOffset) == 8))) ||
    ((move.yMove) && ((abs(move.yOffset) == 4) || (abs(move.yOffset) == 8))))
    nFrame++;
if (((move.xMove) && ((abs(move.xOffset) == 20) || (abs(move.xOffset) == 24))) ||
    ((move.yMove) && ((abs(move.yOffset) == 20) || (abs(move.yOffset) == 24))))
    nFrame += 2;

// update rcSrc to correct frame
rcSrc.left += (nFrame<<5);
rcSrc.right += (nFrame<<);

// set up rcDest
rcDest.left = (move.xTile<<5) + move.xOffset - mapdata.xCamera;
rcDest.top =  (move.yTile<<5) + move.yOffset - mapdata.yCamera - 32;
rcDest.right = rcDest.left + 32;
rcDest.bottom = rcDest.top + 64;

// blit sprite
lpddsBack->Blt(&rcDest, lpddsCharacters, &rcSrc, DDBLT_WAIT | DDBLT_KEYSRC, NULL);

The only part of this code that might strike you as odd is that I'm subtracting 32 from the y-value of the character. Remember, that's because we calculated the screen location of the tile on which the character is standing... but the character is two tiles tall, so we need to plot him one tile above his actual location to make sure he's standing in the right place.

Now the character responds to arrow keypresses by walking in the appropriate direction. The animation follows exactly what we set up, and the map scrolls to center the character when he's not near the map's edge. About bloody time, hey? :) He's probably going to get tired of wandering around all by himself though, so let's throw some NPCs in there. We've actually done most of the work already...





Next : NPCs and Random Movement