04.08 - Animation Anticipation

Started by
51 comments, last by Teej 21 years, 5 months ago
Your Dividend Payment Congratulations! You made it though the first seven articles in this series, and I think it''s time you were rewarded with something fun and game-related, so this article is going to introduce you to animation. I should mention that there are still a few more areas we need to discuss before we can try our hands at an actual game, but since you might be getting tired of pixels and flying ''A''s, I thought I''d take this opportunity to remind everyone that we''re striving to make games in this forum by giving you something that actually looks like part of a game. Rather than having me attempt to explain what we''re going to do in this article, I''d like to draw your attention to RES_04_08.BMP (located on my webpage). You''ll need it for the example code, and I''m sure that once you take a look at it, you''ll instantly know what our mission will be. A Sprite Library The images in this particular bitmap are taken from a graphics library called SPRITELIB (also available on my webpage), and we''ve been given special permission by the author to use his artwork for educational purposes. In return for his generousity, we should all take the time to visit his website at www.arifeldman.com, give him a big ''thank-you!'', and check out his book, "Designing Arcade Computer Game Graphics". Thanks buddy! Bitmaps, Sprites and Cells A sprite is a game element that exhibits certain characteristics, such as movement and interactivity with its environment. If you think of a stereotypical game, the player, enemies, bullets, etc. are all game sprites. I can remember programming on the Commodore 64, which had eight sprite registers -- special support for moving interactive images. If you needed more than eight sprites on the screen at once, you had to employ a little trick where you''d alternate the contents of the sprite registers every other refresh (frame). This way, you''d draw eight sprites in their proper positions one frame, and swap in the other eight sprites to the registers at new screen locations for the other frame. The end result was the illusion of having up to 16 sprites on the screen at once, but the sprites were ''choppy'' on the display. Surely you''ve seen this effect on Atari, Nintendo or many older arcade games before... These days, we still use the term ''sprite'' for game entities. It''s our job to provide the support for them so that they can move, react to other sprites, etc. Because all game sprites have things in common (e.g. they all have a current position), people usually create a SPRITE data structure to manage them, and keep all valid sprites in a linked-list or some other quick-access data structure. If you take a look at the resources in SPRITELIB, you can see how multiple sprites are organized in a single bitmap. Furthermore, many sprites have more than one ''pose'', either denoting a different direction or an animation sequence. The basic entity on such a bitmap is called a cell, each cell for a sprite usually has the same width and height, and cells may or may not have borders. I''m sure that by taking a look at the bitmaps in SPRITELIB you can get an idea of how images are related and organized. I''ve taken one of the game sprites from SPRITELIB and created RES_04_08.BMP by copying and pasting them, and you can clearly see that we have 16 cells, with two animation frames for each direction that the sprite is facing. It''s the job of our game engine to know where each cell is positioned and what their content is so that when the time comes, we can quickly and accurately transfer these cells to our backbuffer surface. Image Editing Although this doesn''t have to do with our task in this article directly, I think that it''s important we take a look at image editing, focusing on color depth and palettes. A bitmap is a file -- one that contains information on the width, height, color depth, palette (if any), and of course the pixel information itself. Hopefully you have a decent image editing tool (MS PAINT just won''t cut it) so that you can perform operations such as copying/pasting to different images, palette loading/saving/editing, image resizing, rotating and other basic effects. I don''t think I''m wrong if I say that JASC''s PaintShop Pro is the pick of the crop in this department, and if you''re going to be making games you simply can''t live without a decent image editor...seriousely, unless you''re an aspring text-adventure game developer I think that we''ve already established that 32-bit color uses 24 bits for the RGB components (8 is either unused or for alpha information), 24-bit color is just that, 24 bits, 16-bit color uses either a 5-6-5 or x-5-5-5 RGB scheme, and 8-bit color uses the eight bits as an index value to a 256-entry palette (with each palette entry consisting of a 24-bit color). Since 8-bit images are special, I''ll discuss them in a moment...first, let''s get the 16,24,32-bit (high color) image discussion out of the way. When you''re dealing with transferring images from one bitmap to another, you have to take into account the color depth of the image on the source, and the resulting image on the destination. It would seem logical that moving a 16-bit image to a 24-bit bitmap doesn''t pose a problem -- the image will look exactly the same, except that now the image has more color variations available to it. Going the other way involves a bit of a translation because there''s less color resolution on the destination, so the image editing software does its best to find the closest color that matches. This is going to cause a slight degradation for images that made full use of 24-bit color, so keep that in mind. An 8-bit bitmap uses a palette, and therefore all artwork on that bitmap can only use the colors available in that palette. What goes into the palette is up to you, and for new bitmaps a default palette is usually assigned by the image editing software. What this means to you is that if you paste an image into an 8-bit bitmap, the image is going to be altered to use the colors in the palette. Sure, the image editing software attempts to find colors in the palette that are close to the original colors, but you''ll almost always notice that the image''s color have been changed to something that looks undesirable. Here are a couple of considerations:
  • If you are moving an image to a new 8-bit bitmap, convert the source image to 8-bit first, then save the source image''s palette information to disk. Load this palette information onto the destination bitmap, and now the copy will work properly (i.e. all image colors will match).
  • If you are moving an 8-bit image from one bitmap to another, you might want to ''merge'' the two bitmap''s palettes. Depending on how many colors each bitmap image requires, you might have enough palette slots free for a perfect color translation...otherwise, you''re going to have to settle for some color changes. If there''s more than one image on the source bitmap, you might want to first create a new bitmap for the image you want to copy, load in the source''s palette information, and have your image editing software streamline the palette for this new bitmap. This way, only the colors that are absolutely necessary for the image you''re trying to copy are counted, giving you a better chance of success when pasting this image to the final destination and merging palettes. I''ve also heard that a tool called Debabelizer is good at merging color information, but check out that $595 (Debabelizer Pro) price tag! Ouch.
The morale of the story here is to keep in mind that 8-bit images rely on their bitmap''s palette in order to be displayed correctly, so if you want to move the image, you''ll need to move some of the palette information as well unless the destination bitmap can already accomodate the image properly with its palette. Even in our game template code, when RESOURCE.BMP is an 8-bit bitmap, the palette information is taken from the bitmap and attached to the primary surface as a DirectDrawPalette object so that images are displayed in their proper colors. Okay, so in the case of SPRITELIB, where all bitmaps are 8-bit, I first saved the palette information from the bitmap I wanted images from, created a new 8-bit bitmap, loaded the palette information into it, and finally copied and pasted the cells I wanted. Speaking of which, let''s get back to animation, shall we? Accessing Images The basic premise behind the font blitter exercise holds true for this article as well -- with a few definitions that describe RESOURCE.BMP, we can grab whichever cell we want whenever we need it. Here''s my list:

// Some definitions for describing the images in RESOURCE.BMP
#define SPRITE_WIDTH        31
#define SPRITE_HEIGHT       32
// The number of animation frames per direction
#define SPRITE_NUMFRAMES    2
// The number of sprite images in a physical row
#define SPRITES_PER_ROW     8
 
These are self-explanatory, but I should point out that we''re ignoring the fact that there''s a border around each cell here...when it''s time to render, we just offset the source RECT by one pixel to the right and down and we''re OK. For such a small number of sprites, you could basically define the positions of each in an array of some sort, but just for the fun of it I thought we''d try deriving the formula you''d use to pinpoint the cell you want given the following:
  • Current direction
  • Current animation frame (first or second)
Our goal is to find a mathematical relationship between these criteria and the cells in the bitmap. A good place to start is to realize that there are eight possible directions, and two cells for each sprite direction:

Cell:   1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16
Sprite: 1a 1b 2a 2b 3a 3b 4a 4b 5a 5b 6a 6b 7a 7b 8a 8b
 
There''s obviously a relationship here, but it only seems convenient for finding the second (b) sprite for each direction (i.e. the sprite number * 2). For (a) sprites, the formula is actually ((spritenum * 2) - 1). From experience, I know that since numbering on the computer starts at zero, magical things happen when I follow suit:

Cell:   0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15
Sprite: 0a 0b 1a 1b 2a 2b 3a 3b 4a 4b 5a 5b 6a 6b 7a 7b
 
Much better. Now I have a solid relationship between the first sprite for each direction and the cell number (i.e. spritenum * 2):

Input                  Output
-----                  ------
Sprite Direction  -->  Cell Number (for first animation frame)
(d: 0-7)               (2d)
 
What this says is that given the direction of the sprite from zero to seven, we can get the sprite''s cell (i.e. relative location in the image) by multiplying the direction by two. We can tell what the direction numbers are by staring at the bitmap, but we''d better define these directions for the computer:

// Possible sprite directions
enum Direction {W, E, N, S, NW, NE, SW, SE, NONE};
 
The enum keyword assigns numbers to each of the identifiers in the braces starting at zero. Perfect.

// The sprite''s current facing direction
static Direction sprite_currDir = E;

// The current animation frame number (0 or 1)
static int       sprite_currFrame = 0;
 
With these two variables and our earlier definitions, we know have everything we need to compute the correct cell to render. Are you ready? Let''s find the starting x coordinate for the image: (sprite_currDir) : a number from 0-7 specifying direction (sprite_currDir * 2) : the cell number (0-14) for the image (first animation frame) ((sprire_currDir * 2) + sprite_currFrame) : The correct cell number for the image (0-15) ((sprire_currDir * 2) + sprite_currFrame) % SPRITES_PER_ROW : Every 8 cells, start over at the far left. Let''s stop and examine that last line. Take a look at this table:

Sprite Image File:
Row 1: 0(0,0) 1(1,0)  2(2,0)  3(3,0)  4(4,0)  5(5,0)  6(6,0)  7(7,0)
Row 2: 8(0,1) 9(1,1) 10(2,1) 11(3,1) 12(4,1) 13(5,1) 14(6,1) 15(7,1)
 
There are 16 cells, numbered 0-15, but in the bitmap there are 8 per row. Since we''re trying to find the x-coordinate, look at the relationship between the x-coordinate in the table and the cell number:

0  -> 0
1  -> 1
2  -> 2
3  -> 3
4  -> 4
5  -> 5
6  -> 6
7  -> 7
8  -> 0
9  -> 1
10 -> 2
11 -> 3
12 -> 4
13 -> 5
14 -> 6
15 -> 7
 
This is just the place where the modulus operator ''%'' comes in handy. Here''s how you''d describe the above table: n -> n % 8 Okay? So, back to our derivation: ((sprire_currDir * 2) + sprite_currFrame) % SPRITES_PER_ROW : Every 8 cells, start over at the far left. (((sprire_currDir * 2) + sprite_currFrame) % SPRITES_PER_ROW) * SPRITE_WIDTH : How many pixels over from the left of the bitmap for the cell (((sprire_currDir * 2) + sprite_currFrame) % SPRITES_PER_ROW) * SPRITE_WIDTH + 1 : Skip over the white border of the cell (1 pixel more to the right) That''s it! It''s long and looks complicated, but I hope that it eventually makes perfect sense to you. When I prepared this line mentally, I started by thinking in the opposite direction: "A cell is 31 pixels wide, which means that for every cell you want to move over, you need to use a multiple of 31. It''s this multiple of 31 that is directly related to the cell number, and all I need to do is correlate the direction the sprite is facing to the proper multiple of 31. Finally, I need to wrap-around every 8 cells since that''s how they''re physically organized on the bitmap, and move one pixel in before rendering or else the border will appear on the display." For the y-coordinate, I''m not going to go through the procedure again, because it''s basically that same formula -- look back up at the sprite table and mentally figure out the relationship between the cell number and the column number, and you''ll have the only change needed: (((sprite_currDir * 2) + sprite_currFrame) / SPRITES_PER_ROW) * SPRITE_HEIGHT + 1 Whew. Here''s the complete rendering stage for this sprite:

// Clear the display
EraseBackground();

// Prepare the source RECT
rectSrc.left = (((sprite_currDir * SPRITE_NUMFRAMES) + sprite_currFrame)
                    % SPRITES_PER_ROW) * SPRITE_WIDTH + 1;
rectSrc.top = (((sprite_currDir * SPRITE_NUMFRAMES) + sprite_currFrame)
                    / SPRITES_PER_ROW) * SPRITE_HEIGHT + 1;rectSrc.right = rectSrc.left + SPRITE_WIDTH - 1;
rectSrc.bottom = rectSrc.top + SPRITE_HEIGHT - 1;
 
// Prepare the destination RECT
rectDest.left = sprite_xPos;
rectDest.top  = sprite_yPos;
rectDest.right = rectDest.left + SPRITE_WIDTH - 1;
rectDest.bottom = rectDest.top + SPRITE_HEIGHT - 1;
 
// Render the sprite
G.lpDDSBack->Blt(&rectDest, G.lpDDSRes, &rectSrc,
                 DDBLT_KEYSRC | DDBLT_WAIT, NULL);
 
// Flip the backbuffer onto the active display surface
G.lpDDSPrimary->Flip(NULL, 0);
 
This is by no means the optimal method of locating the sprite and rendering it, but it is generalized to work with any sprite that''s cell-aligned, and it''s for that reason that I removed the ''2'' from the equation and used SPRITE_NUMFRAMES instead; hard-coding numbers is bad. Animation Variables and Timing I can see this is going to be a long article -- we haven''t even looked at the actual animation yet! Grab a coffee, beer, take a smoke break, stretch, etc. and let''s get to it. First, we need to react to the user pressing the directional keys so that our sprite actually moves. For starters, we should declare some variables that will be necessary:

// Current Position: where the sprite is on the display (start at center)
static int       sprite_xPos = (SCREEN_WIDTH / 2) - (SPRITE_WIDTH / 2),
                 sprite_yPos = (SCREEN_HEIGHT / 2) - (SPRITE_HEIGHT / 2);
// Counters for time elapsed since last movement turn
static DWORD     sprite_MoveTickCount = 0,
                 sprite_AnimTickCount = 0;
// The sprite''s current facing direction
static Direction sprite_currDir = E;
// The direction the user is currently requesting
Direction        sprite_newDir;
// The current animation frame number
static int       sprite_currFrame = 0;
 
As I''m sure you''ve guessed, sprite_xPos and sprite_yPos mark the sprite''s current position. the two counters, sprite_MoveTickCount and sprite_AnimTickCount are responsible for keeping track of how much time has passed (in milliseconds). When one of these counters goes over a predetermined amount, movement or animation is triggered, which reminds me; here''s the trigger values:

// The sprite''s velocity in pixels per second
const int sprite_MoveRate = 50;
// The number of times to switch animation frames per second when the
// sprite is moving (i.e. running effect)
const int sprite_AnimRate = 4;
 
Here''s the trigger for movement:

sprite_MoveTickCount += G.diffTickCount;
while (sprite_MoveTickCount >= (1000 / sprite_MoveRate)) {...}
 
And here''s the trigger for animation:

sprite_AnimTickCount += G.diffTickCount;
if (sprite_AnimTickCount / (1000 / sprite_AnimRate) > 0) {...}
 
Notice that they''re slightly different -- for movement, we may have to move more than one pixel in a frame, so we enter a while loop that removes ''turns'' from the counter variable until we''ve moved enough times. For animation, we''re only interested in whether or not it''s time to animate. You don''t animate more than once per frame, and you don''t carry over ticks towards the next animation trigger. You just want smoothe, constant animation, regardless of the dynamic changes in frame rate. User Input When we get the device state for the keyboard, we can tell which direction keys are being held down by using a line like so: if (KEYDOWN(DIK_UP)) {...} Since we''re interested in 8 possible directions, this code will accurately calculate the requested direction:

// The sprite''s current facing direction
static Direction sprite_currDir = E;
// The direction the user is currently requesting
Direction        sprite_newDir;
 
// Get the user''s input (desired direction)
if (KEYDOWN(DIK_UP))
{
    if (KEYDOWN(DIK_LEFT))       sprite_newDir = NW;
    else if (KEYDOWN(DIK_RIGHT)) sprite_newDir = NE;
    else                         sprite_newDir = N;
}
else if (KEYDOWN(DIK_DOWN))
{
    if (KEYDOWN(DIK_LEFT))       sprite_newDir = SW;
    else if (KEYDOWN(DIK_RIGHT)) sprite_newDir = SE;
    else                         sprite_newDir = S;
}
else if (KEYDOWN(DIK_LEFT))      sprite_newDir = W;
else if (KEYDOWN(DIK_RIGHT))     sprite_newDir = E;
else                             sprite_newDir = NONE;
 
The multiple if''s and else if''s are necessary because there are combinations of keys that go into some directions. Note that there are two Direction variables: one for the current state of the keyboard (sprite_newDir) and one for the sprite''s actual direction (sprite_currDir). This is necessary because even if the user isn''t pressing any keys, the sprite always has a current direction which we need for rendering. Provided that the user is pressing something valid, we can then go ahead and update the sprite''s direction:

// Update the sprite''s direction if requested
if (sprite_newDir != NONE) sprite_currDir = sprite_newDir;
 
Great, so now we know where the user would like the sprite to move (if at all). It''s time to turn this direction into actual movement, which is accomplished by altering the sprite''s position:

// Move the sprite one pixel in the requested direction
switch (sprite_currDir)
{
case N:
    sprite_yPos--; break;
case NW:
    sprite_yPos--; sprite_xPos--; break;
case NE:
    sprite_yPos--; sprite_xPos++; break;
case S:
    sprite_yPos++; break;
case SW:
    sprite_yPos++; sprite_xPos--; break;
case SE:
    sprite_yPos++; sprite_xPos++; break;
case W:
    sprite_xPos--; break;
case E:
    sprite_xPos++; break;
}
 
Watch out -- we only move the sprite when the movement counter has triggered; i.e. when it''s really time to move. Until that exact moment, all the user can do is change the direction the sprite is facing -- no more. Therefore, this code sits inside of the movement routine:

// Update movement ticker
sprite_MoveTickCount += G.diffTickCount;
 
// Move the sprite
dwTicksInATurn = 1000 / sprite_MoveRate;
while (sprite_MoveTickCount >= dwTicksInATurn)
{
    // Note the fact that we''re moving one turn
    sprite_MoveTickCount -= dwTicksInATurn;
 
    // If the user isn''t currently holding down any direction keys,
    // then there''s nothing to do (except eat up unused ticks)
    if (sprite_newDir == NONE) continue;
 
    // MOVEMENT CODE FROM ABOVE GOES HERE
 
    // Ensure sprite lies within display boundaries
    if (sprite_xPos < -SPRITE_WIDTH)  sprite_xPos = -SPRITE_WIDTH;
    if (sprite_xPos > SCREEN_WIDTH)   sprite_xPos =  SCREEN_WIDTH;
    if (sprite_yPos < -SPRITE_HEIGHT) sprite_yPos = -SPRITE_HEIGHT;
    if (sprite_yPos > SCREEN_HEIGHT)  sprite_yPos =  SCREEN_HEIGHT;
}
 
The last section of code makes sure that the user isn''t running miles off the display...we let the sprite run just far enough that it''s off the display. Remember, the sprite''s position variables are for the top-left of the sprite image, so being at x-position zero doesn''t mean he''s off the display; just sitting against the left edge. We do a little adjusting for the edges to take into account the x-position versus the actual width and height of the sprite on-screen. Animation All we need to do here is sprite_AnimRate (4) times a second, swap one sprite cell with the other. Thanks to the fact that the cell is figured out for us in the rendering code, we need only to update the sprite_currFrame variable (0 or 1):

sprite_AnimTickCount += G.diffTickCount;
if (sprite_newDir != NONE && sprite_AnimTickCount / (1000 / sprite_AnimRate) > 0)
{
    // Extra ticks aren''t carried over -- this would make the sprite
    // seem to run unevenly
    sprite_AnimTickCount = 0;
 
    // Advance to the next animation frame, and wrap around if necessary
    sprite_currFrame++;
    if (sprite_currFrame == SPRITE_NUMFRAMES) sprite_currFrame = 0;
}
 
The expression sprite_newDir != NONE just means that if the user isn''t currently holding down any keys, the sprite isn''t actually moving, so there''s no need to animate (the animation frames are for running). Everything else here is within your comprehension. A Demo and a Challenge Someone recently asked me why I haven''t written a book on game development. Trust me, with the size of some of these articles, I feel like I already have The source code for these demos are getting large, so I''ll simply direct you towards my webpage where you can grab the BaseCode2_Animate ZIPfile. As expected, our little buddy runs around when we press the arrow keys. I think I''ve done enough work for today, so now it''s your turn... Take another look at RES_04_08.BMP, at the far right of the bitmap. Do you see it? Guess what that is... it''s a bullet! Yup, a little projectile that you''re going to make our little sprite fire! Here''s the specification:
  • Choose a key for firing and look it up in DINPUT.H for the DIK_XXX definition
  • The bullet will need position and movement rate variables, and you''ll have to figure out the direction of the bullet by using the sprite''s current direction at the time of firing
  • There will only be one bullet at a time on the display to keep things reasonable
  • Try and make the bullet leave the sprite at the approximate location of the sprite''s weapon
That should tide you over until the next article appears. Now get to work! Questions? Comments? Please reply to this topic!
Advertisement
yo, great job but should i memorize the stuff under init_terms and utils or are we gonna go mnore into that, great job on explaining pixels, im working on Bitmaps as we speak
-kaptan"If you cant handle the heat, Get off the car hood"
Haven''t read the article yet, but I have downloaded the files
and I noticed the character in the resource.bmp file is the same character sprite I used in a game I programmmed 2 yrs ago...:D

The movement/animation code I did in that game was really poor (it was my first game), hopefully now I will learn to do it proper ... :D

btw, great work Teej, I''m learning lots
Homba do deligadoo
Here''s a little deathmatch game based on the latest code:
http://bamze.nu/lazzie/Download/DEATHMATCH.zip
(Sorry for not posting any code, but it''s a 10-minute work, so it doesn''t look very good, I''ll try to clean it up and post it later if people are interested ).

As always, keep up the great work, Teej!

OOPS, almost forgot, the controls are:

Player1:
up, down, left, right, return

Player2:
w, s, a, d, space

When a player gets 20 kills, he''s put in the center of the screen, facing down. Nothing much, but I hope you check it out and comment.

------------------
It''s me again!
Taharez,
Nice work with the game
I have a suggestion though... if you hold the space bar or press the space bar again while the bullet is moving, it resets the bullets position and fires it again while erasing the old bullet, not giving it a chance to finish the shot.
I recommend using a variable to keep track if a bullet has been fired or not, and then only reset it when the bullet has left the screen (or after a certain amount of time), and when it hits the other character.
Dionysis:
Thanks for the input. I was actually planning on adding a list of bullets, so this solution was only temporary, I''ll upload a new version in a couple of days ( when I regain consciousness after my graduation =) ).

Hope to see some other peoples programs and code here, keep up the coding ( unless you''re going to graduate this week too, then i just wish good luck to you, and have fun! =) ).

------------------
It''s me again!
I''m trying to compile basecode2_animate, and I keep receiving an initialization error. It says, "DD_init: -10." I know the problem is loading the bitmap, but what is the solution?
Why do I always figure out my problem after I post the question?
I just misplaced the file.
in the animation code i am trying to use a switch statement on the bullets dir to change the place where the bullet starts. But it doesn''t want to work i always get the last statements results.
Yay like the last person to post i worked it out just changed where the statement was and added a boolean variable for if it is the first frame of the bullet.

This topic is closed to new replies.

Advertisement