An animated sprite, then, is an array of sprites drawn using new properties, such as timing, direction, and velocity.. The AnimSprite program loads these six image files, each containing
Trang 1explode_bmp = load_bitmap(“explode.bmp”, NULL);
Trang 2}
//look for a direct hit using basic collision
//tank is either 0 or 1, so negative num = other tank
}
}
///////////////////////////////////////////////////////////////////// // fireweapon function
// set bullet direction and speed and activate it
///////////////////////////////////////////////////////////////////// void fireweapon(int num)
Trang 3bullet_bmp = load_bitmap(“bullet.bmp”, NULL);
Trang 4bullets[num].xspd = 0;
bullets[num].yspd = BULLETSPEED;
break;
//SW case 5:
}
The next section of code covers the keyboard input code, including forward,backward,turnleft,
turnright, and getinput These functions are largely the same as before, but they now mustsupport eight directions (evident in the ifstatement within turnleftandturnright)
Trang 6//WASD - SPACE keys control tank 1
textprintf(screen, font, SCREEN_W-70*(player+1), 1,
BURST, “P%d: %d”, player+1, points);
}
Thesetuptanks function has changed dramatically from the last version because that iswhere the new tank bitmaps are loaded Since this game uses the rotate_spritefunction togenerate the sprite images for all eight directions, this function takes care of that by firstcreating each image and then blitting the source tank image into each new image with aspecified rotation angle The end result is two tanks fully rotated in eight directions
/////////////////////////////////////////////////////////////////////
// setuptanks function
// load tank bitmaps and position the tank
/////////////////////////////////////////////////////////////////////
Trang 7//load first tank bitmap
tank_bmp[0][0] = load_bitmap(“tank1.bmp”, NULL);
//rotate image to generate all 8 directions
//load second tank bitmap
tank_bmp[1][0] = load_bitmap(“tank2.bmp”, NULL);
//rotate image to generate all 8 directions
Trang 8The next section of the code includes the setupscreen function The most importantchange to this function is the inclusion of a single line calling set_color_depth(32), whichcauses the game to run in 32-bit color mode Note that if you don’t have a 32-bit videocard, you might want to change this to 16 (which will still work).
textprintf(screen, font, 1, 1, BURST,
“Tank War - %dx%d”, SCREEN_W, SCREEN_H);
//draw screen border
rect(screen, 0, 12, SCREEN_W-1, SCREEN_H-1, TAN);
rect(screen, 1, 13, SCREEN_W-2, SCREEN_H-2, TAN);
}
Finally, the last section of code in the third enhancement to Tank War includes the
all-important mainfunction Several changes have been made in main, notably the removal ofthe calls to clearpath(which checked for bullet hits by looking directly at pixel color) Thecall to rest now has a value of 10 to speed up the game a bit in order to have smootherbullet trajectories There is also a line of code that displays the direction of each tank, as Iexplained previously
Trang 9textprintf(screen, font, 0, SCREEN_H-10, WHITE,
“DIRS %d , %d”, tanks[0].dir, tanks[1].dir);
//erase the tanks
Trang 10This marks the end of perhaps the most interesting chapter so far, at least in my opinion.The introduction to sprites that you have received in this chapter provided the basicswithout delving too deeply into sprite programming theory The next chapter covers someadvanced sprite programming topics, including the sorely needed collision detection
I will also get into sprite animation in the next chapter There are many more changes on
the way for Tank War as well The next several chapters will provide a huge amount of new functionality that you can use to greatly enhance Tank War, making it into a truly top-
notch game with a scrolling background, animated tanks, a radar screen, and many morenew features!
Chapter Quiz
You can find the answers to this chapter quiz in Appendix A, “Chapter Quiz Answers.”
1 What is the term given to a small image that is moved around on the screen?
Trang 115 Which function draws a vertically-flipped sprite?
Trang 13If Chapter 7 provided the foundation for developing bitmap-based games, then
Chapter 8 provided the frame, walls, plumbing, and wiring (House analogies are frequently used to describe software development, so they may be used to describe
game programming as well.) Therefore, what you need from this chapter are the sheetrock,
finishing, painting, stucco, roof tiles, appliances, and all the cosmetic accessories that
com-plete a new house—yes, including the kitchen sink
The other sections of this chapter (on RLE sprites, compiled sprites, and collision
detec-tion) are helpful, but might be considered the Italian tile of floors, whereas linoleum will
work fine for most people But the segment on animated sprites is absolutely crucial in
your quest to master the subject of 2D game programming So what is an animated sprite?
You already learned a great deal about sprites in the last chapter, and you have at your
dis-posal a good tool set for loading and blitting sprites (which are just based on common
bitmaps) An animated sprite, then, is an array of sprites drawn using new properties, such
as timing, direction, and velocity
Here is a breakdown of the major topics in this chapter:
I Working with animated sprites
I Using run-length encoded sprites
I Working with compiled sprites
I Understanding collision detection
Trang 14Animated Sprites
The sprites you have seen thus far were handled somewhat haphazardly, in that no realstructure was available for keeping track of these sprites They have simply been loadedusingload_bitmapand then drawn using draw_sprite, with little else in the way of control
or handling To really be able to work with animated sprites in a highly complex game
(such as a high-speed scrolling shooter like R-Type or Mars Matrix), you need a
frame-work for drawing, erasing, and moving these sprites, and for detecting collisions For all
of its abstraction, Allegro leaves this entirely up to you—and for good reason No singleperson can foresee the needs of another game programmer because every game has aunique set of requirements (more or less) Limiting another programmer (who may be farmore talented than you) to using your concept of a sprite handler only encourages thatperson to ignore your handler and write his own That is exactly why Allegro has no spritehandler; rather, it simply has a great set of low-level sprite routines, the likes of which youhave already seen
What should you do next, then? The real challenge is not designing a handler for workingwith animated sprites; rather, it is designing a game that will need these animated sprites,and then writing the code to fulfill the needs of the game In this case, the game I am tar-
geting for the sprite handler is Tank War, which you have improved in each new chapter— and this one will be no exception In Chapter 8, you modified Tank War extensively to
convert it from a vector- and bitmap-based game into a sprite-based game, losing somegameplay along the way (The battlefield obstacles were removed.) At the end of this chapter,you’ll add the sprite handler and collision detection—finally!
Drawing an Animated Sprite
To get started, you need a simple example followed by an explanation of how it works Ihave written a quick little program that loads six images (of an animated cat) and drawsthem on the screen The cat runs across the screen from left to right, using the spriteframes shown in Figure 9.1
The AnimSprite program loads these six image files, each containing a single frame of the
animated cat, and draws them in sequence, one frame after another, as the sprite is movedacross the screen (see Figure 9.2)
Figure 9.1 The animated cat sprite, courtesy of Ari Feldman
Trang 15#include <conio.h>
#include <stdlib.h>
#include <stdio.h>
#include “allegro.h”
#define WHITE makecol(255,255,255)
#define BLACK makecol(0,0,0)
Trang 16textout(screen, font, “AnimSprite Program (ESC to quit)”,
framecount = 0;
curframe++;
if (curframe > 5) curframe = 0;
} acquire_screen();
//draw the sprite draw_sprite(screen, kitty[curframe], x, y);
//display logistics textprintf(screen, font, 0, 20, WHITE,
“Sprite X,Y: %3d,%3d”, x, y);
textprintf(screen, font, 0, 40, WHITE,
“Frame,Count,Delay: %2d,%2d,%2d”, curframe, framecount, framedelay);
release_screen();
Trang 17Now for that explanation, as promised The difference between AnimSprite and DrawSprite
(from the previous chapter) is multifaceted The key variables,curframe,framecount, and
framedelay, make realistic animation possible You don’t want to simply change the frame
every time through the loop, or the animation will be too fast The frame delay is a static
value that really needs to be adjusted depending on the speed of your computer (at least
until I cover timers in Chapter 11, “Timers, Interrupt Handlers, and Multi-Threading”)
The frame counter, then, works with the frame delay to increment the current frame of
the sprite The actual movement of the sprite is a simple horizontal motion using the x
A really well thought-out sprite handler will have variables for both the velocity (x, y) and
velocity (x speed, y speed), along with a velocity delay to allow some sprites to move quite
slowly compared to others If there is no velocity delay, each sprite will move at least one
pixel during each iteration of the game loop (unless velocity is zero, which means that
This concept is something I’ll explain shortly
Creating a Sprite Handler
Now that you have a basic—if a bit rushed—concept of sprite animation, I’d like to walk
you through the creation of a sprite handler and a sample program with which to test it
Now you’ll take the animation code from the last few pages and encapsulate it into a
struct If you were using the object-oriented C++ language instead of C, you’d no doubt
Trang 18“class it.” That’s all well and good, but I don’t care what C++ programmers claim—it’smore difficult to understand, which is the key reason why this book focuses on C ThatAllegro itself is written in C only supports this decision The actual bitmap images for thesprite are stored separately from the sprite struct because it is more flexible that way.
In addition to those few animation variables seen in AnimSprite, a full-blown animated
sprite handler needs to track several more variables Here is the struct:
typedef struct SPRITE
to the size of the sprite image(stored outside the struct) Thevelocity elements (xspeed,yspeed)determine how many pixels thesprite will move in conjunctionwith the velocity delay (xdelay,
xcount and ydelay, ycount) Thevelocity delay allows somesprites to move much slowerthan other sprites on thescreen—even more slowly thanone pixel per frame This givesyou a far greater degree of control over how a sprite behaves The animation elements(curframe,maxframe,animdir) help the sprite animation, and the animation delay elements(framecount,framedelay) help slow down the animation rate The animdirelement is of par-ticular interest because it allows you to reverse the direction that the sprite frames aredrawn (from 0 to maxframeor from maxframeto 0, with looping in either direction) The mainreason why the BITMAParray containing the sprite images is not stored inside the struct isbecause that is wasteful—there might be many sprites sharing the same animation images
Figure 9.3 The SPRITEstruct and its elements help abstract
sprite movement into reusable code
Trang 19Now that we have a sprite struct, the actual handler is contained in a function that I will
As you can see,updatespriteaccepts a pointer to a SPRITEvariable A pointer is necessary
because elements of the struct are updated inside this function This function would be called
at every iteration through the game loop because the sprite elements should be closely tied
to the game loop and timing of the game The delay elements in particular should rely
upon regular updates using a timed game loop The animation section checks animdirto
increment or decrement the framecountelement
Trang 20However,updatespritewas not designed to affect sprite behavior, only to manage the tics of sprite movement After updatesprite has been called, you want to deal with thatsprite’s behavior within the game For instance, if you are writing a space-based shooterfeaturing a spaceship and objects (such as asteroids) that the ship must shoot, then youmight assign a simple warping behavior to the asteroids so that when they exit one side ofthe screen, they will appear at the opposite side Or, in a more realistic game featuring alarger scrolling background, the asteroids might warp or bounce at the edges of the uni-verse rather than just the screen In that case, you would call updatesprite followed byanother function that affects the behavior of all asteroids in the game and rely on custom
logis-or random values flogis-or each asteroid’s struct elements to cause it to behave slightly differentlythan the other asteroids, but basically follow the same behavioral rules Too many pro-grammers ignore the concept of behavior and simply hard-code behaviors into a game
I love the idea of constructing many behavior functions, and then using them in a game
at random times to keep the player guessing what will happen next For instance, a simplebehavior that I often use in example programs is to have a sprite bounce off the edges ofthe screen This could be abstracted into a bounce behavior if you go that one extra step
in thinking and design it as a reusable function
One thing that must be obvious when you are working with a real sprite handler is that itseems to have a lot of overhead, in that the struct elements must all be set at startup.There’s no getting around that unless you want total chaos instead of a working game! Youhave to give all your sprites their starting values to make the game function as planned.Stuffing those variables into a struct helps to keep the game manageable when the sourcecode starts to grow out of control (which frequently happens when you have a truly greatgame idea and you follow through with building it)
The SpriteHandler Program
I have written a program called SpriteHandler that demonstrates how to put all this
together into a workable program that you can study This program uses a ball sprite with
16 frames of animation, each stored in a file (ball1.bmp, ball2.bmp, and so on to ball16.bmp).One thing that you must do is learn how to store an animation sequence inside a singlebitmap image and grab the frames out of it at run time I’ll show you how to do that shortly
Figure 9.4 shows the SpriteHandler program running Each time the ball hits the edge, it
changes direction and speed
#include <conio.h>
#include <stdlib.h>
#include <stdio.h>
#include “allegro.h”
#define BLACK makecol(0,0,0)
#define WHITE makecol(255,255,255)
Trang 21//define the sprite structure
typedef struct SPRITE
Figure 9.4 The SpriteHandler program demonstrates a full-featured
animated sprite handler
Trang 22void erasesprite(BITMAP *dest, SPRITE *spr)
{
//erase the sprite using BLACK color fill
rectfill(dest, spr->x, spr->y, spr->x + spr->width, spr->y + spr->height, BLACK);
if (—spr->curframe < 0) spr->curframe = spr->maxframe;
} else if (spr->animdir == 1) {
if (++spr->curframe > spr->maxframe) spr->curframe = 0;
} }
}
void bouncesprite(SPRITE *spr)
{
Trang 23//simple screen bouncing behavior
Trang 24//load sprite images
//initialize the sprite with lots of randomness
ball->x = rand() % (SCREEN_W - ballimg[0]->w);
ball->y = rand() % (SCREEN_H - ballimg[0]->h);
textprintf(screen, font, 0, 20, WHITE,
“x,y,xspeed,yspeed: %2d,%2d,%2d,%2d”,
Trang 25ball->x, ball->y, ball->xspeed, ball->yspeed);
textprintf(screen, font, 0, 30, WHITE,
Grabbing Sprite Frames from an Image
In case you haven’t yet noticed, the idea behind the sprite handler that you’re building in
this chapter is not to encapsulate Allegro’s already excellent sprite functions (which were
covered in the previous chapter) The temptation of nearly every C++ programmer would
be to drool in anticipation over encapsulating Allegro into a series of classes What a
shame and what a waste of time! I can understand classing up an operating system
ser-vice, which is vague and obscure, to make it easier to use In my opinion, a class should be
used to simplify very complex code, not to make simple code more complex just to satisfy
an obsessive-compulsive need to do so
On the contrary, you want to use the existing functionality of Allegro, not replace it with
something else By “something else” I mean not necessarily better, just different The
wrapping of one thing and turning it into another thing should arise out of use, not
com-pulsion Add new functions (or in the case of C++, new classes, properties, and methods)
as you need them, not from some grandiose scheme of designing a library before using it
Thus, you have a basic sprite handler and now you need a function to grab an animation
sequence out of a tiled image So you can get an idea of what I’m talking about, Figure 9.5
shows a 32-frame tiled animation sequence in a file called sphere.bmp
Figure 9.5 This bitmap image contains
32 frames of an animated sphere used as
a sprite Courtesy of Edgar Ibarra
Trang 26The frames would be easy to capture if they were lined up in a single row, so how wouldyou grab them out of this file with eight columns and four rows? It’s easy if you have thesprite tile algorithm I’m sure someone described this in some mathematics or computergraphics book at one time or another in the past; I derived it on my own years ago I sug-gest you print this simple algorithm in a giant font and paste it on the wall above yourcomputer—or better yet, have a T-shirt made with it pasted across the front.
int x = startx + (frame % columns) * width;
int y = starty + (frame / columns) * height;
Using this algorithm, you can grab an animation sequence that is stored in a bitmap file,even if it contains more than one animation (For instance, some simpler games mightstore all the images in a single bitmap file and grab each sprite at run time.) Now that youhave the basic algorithm, here’s a full function for grabbing a single frame out of an image
by passing the width, height, column, and frame number:
BITMAP *grabframe(BITMAP *source,
int width, int height, int startx, int starty, int columns, int frame) {
BITMAP *temp = create_bitmap(width,height);
int x = startx + (frame % columns) * width;
int y = starty + (frame / columns) * height;
n o t e
The grabframefunction really should have some error detection code built in, such as a check forwhether the bitmap is NULL after blitting it As a matter of fact, all the code in this book is inten-tionally simplistic—with no error detection code—to make it easier to study In an actual game,you would absolutely want to add checks in your code
Trang 27The SpriteGrabber Program
The SpriteGrabber program demonstrates how to use grabframe by modifying the
SpriteHandler program and using a more impressive animated sprite that was rendered
(courtesy of Edgar Ibarra) See Figure 9.6 for a glimpse of the program
I’m going to list the entire source code for SpriteGrabber and set in boldface the lines that
have changed (or been added) so you can note the differences I believe it would be too
confusing to list only the changes to the program There is a significant learning experience
to be had by observing the changes or improvements to a program from one revision to
#define BLACK makecol(0,0,0)
#define WHITE makecol(255,255,255)
Figure 9.6 The SpriteGrabber program demonstrates how to grab sprite
images (or animation frames) from a tiled source image
Trang 28//define the sprite structure
typedef struct SPRITE
//erase the sprite using BLACK color fill
rectfill(dest, spr->x, spr->y, spr->x + spr->width, spr->y + spr->height, BLACK);
Trang 29//update frame based on animdir
Trang 30spr->y = SCREEN_H - spr->height;
spr->yspeed = rand() % 2 - 6;
spr->animdir *= -1;
}
}
BITMAP *grabframe(BITMAP *source,
int width, int height, int startx, int starty, int columns, int frame) {
BITMAP *temp = create_bitmap(width,height);
int x = startx + (frame % columns) * width;
int y = starty + (frame / columns) * height;
install_timer();
srand(time(NULL));
textout(screen, font, “SpriteGrabber Program (ESC to quit)”,
0, 0, WHITE);
//load 32-frame tiled sprite image
temp = load_bitmap(“sphere.bmp”, NULL);
Trang 31//initialize the sprite with lots of randomness
ball->x = rand() % (SCREEN_W - ballimg[0]->w);
ball->y = rand() % (SCREEN_H - ballimg[0]->h);
//draw the ball sprite
draw_sprite(screen, ballimg[ball->curframe], ball->x, ball->y);
//display some logistics
textprintf(screen, font, 0, 20, WHITE,
“x,y,xspeed,yspeed: %2d,%2d,%2d,%2d”,
ball->x, ball->y, ball->xspeed, ball->yspeed);
textprintf(screen, font, 0, 30, WHITE,
“xcount,ycount,framecount,animdir: %2d,%2d,%2d,%2d”,
ball->xcount, ball->ycount, ball->framecount, ball->animdir);
//unlock the screen
Trang 32The Next Step: Multiple Animated Sprites
You might think of a single sprite as a single-dimensional point in space (thinking interms of geometry) An animated sprite containing multiple images for a single sprite is atwo-dimensional entity The next step, creating multiple copies of the sprite, might becompared to the third dimension So far you have only dealt with and explored the con-cepts around a single sprite being drawn on the screen either with a static image or with
an animation sequence But how many games feature only a single sprite? It is really a test
of the sprite handler to see how well it performs when it must contend with many sprites
at the same time
Because performance will be a huge issue with multiple sprites, I will use a double-buffer
in the upcoming program for a nice, clean screen without flicker I will add another level
of complexity to make this even more interesting—dealing with a bitmapped backgroundimage instead of a blank background.rectfillwill no longer suffice to erase the spritesduring each refresh; instead, the background will have to be restored under the sprites asthey move around
Instead of a single sprite struct there is an array of sprite structs, and the code throughoutthe program has been modified to use the array To initialize all of these sprites, you need
to use a loop and make sure each pointer is pointing to each of the sprite structs
//initialize the sprite
for (n=0; n<MAX; n++)
{
sprites[n] = &thesprites[n];
sprites[n]->x = rand() % (SCREEN_W - spriteimg[0]->w);
sprites[n]->y = rand() % (SCREEN_H - spriteimg[0]->h);
sprites[n]->width = spriteimg[0]->w;
sprites[n]->height = spriteimg[0]->h;
sprites[n]->xdelay = rand() % 3 + 1;
Trang 33This time I’m using a much larger animation sequence containing 64 frames, as shown in
Figure 9.7 The source frames are laid out in an 8×8 grid of tiles
To load these frames into the sprite handler, a loop is used to grab each frame individually
//load 64-frame tiled sprite image
temp = load_bitmap(“asteroid.bmp”, NULL);
Figure 9.7 The source image for the animated
asteroid contains 64 frames
Trang 34The MultipleSprites Program
The MultipleSprites program animates 100 sprites on the screen, each of which has 64
frames of animation! Had this program tried to store the actual images with every singlesprite instead of sharing the sprite images, it would have taken a huge amount of systemmemory to run—so now you see the wisdom in storing the images separately from the
structs Figure 9.8 shows the MultipleSprites program running at 1024×768 This program differs from SpriteGrabber because it uses a screen warp rather than a screen bounce
behavior
This program uses a secondbuffer to improve perfor-mance Could you imaginethe speed hit after erasingand drawing 100 spritesdirectly on the screen? Evenlocking and unlocking thescreen wouldn’t help muchwith so many writes takingplace on the screen That iswhy this program uses dou-ble-buffering—so all blit-ting is done on the secondbuffer, which is then quicklyblitted to the screen with asingle function call
//update the screen
acquire_screen();
blit(buffer,screen,0,0,0,0,buffer->w,buffer->h);
release_screen();
The game loop in MultipleSprites might look inefficient at first glance because there are
four identical for loops for each operation—erasing, updating, warping, and drawingeach of the sprites
//erase the sprites
Trang 35It might seem more logical to use a single forloop with these functions inside that loop
instead, right? Unfortunately, that is not the best way to handle sprites First, all of the
sprites must be erased before anything else happens Second, all of the sprites must be
moved before any are drawn or erased Finally, all of the sprites must be drawn at the same
time, or else artifacts will be left on the screen Had I simply blasted the entire background
onto the buffer to erase the sprites, this would have been a moot point The program
might even run faster than erasing 100 sprites individually However, this is a learning
experience It’s not always practical to clear the entire background, and this is just a
demonstration—you won’t likely have 100 sprites on the screen at once unless you are
building a very complex scrolling arcade shooter or strategy game
Following is the complete listing for the MultipleSprites program If you are typing in the
code directly from the book, you will want to grab the asteroids.bmp and ngc604.bmp
files from the CD-ROM (They are located in \chapter09\multiplesprites.)
#include <conio.h>
#include <stdlib.h>
#include <stdio.h>
#include “allegro.h”
#define BLACK makecol(0,0,0)
#define WHITE makecol(255,255,255)
#define MAX 100
#define WIDTH 640
#define HEIGHT 480
#define MODE GFX_AUTODETECT_WINDOWED
//define the sprite structure
typedef struct SPRITE
{
int x,y;
int width,height;
Trang 36//erase the sprite
blit(back, dest, spr->x, spr->y, spr->x, spr->y, spr->width, spr->height);
Trang 37BITMAP *grabframe(BITMAP *source,
int width, int height, int startx, int starty, int columns, int frame) {
BITMAP *temp = create_bitmap(width,height);