explode_bmp = load_bitmap(“explode.bmp”, NULL);
}
//draw the explosion bitmap several times
for (n = 0; n < 5; n++)
{
rotate_sprite(screen, explode_bmp,
x + rand()%10 - 20, y + rand()%10 - 20,
itofix(rand()%255));
rest(30);
}
//clear the explosion
circlefill(screen, x, y, 50, BLACK);
}
/////////////////////////////////////////////////////////////////////
// updatebullet function
// update the position of a bullet
/////////////////////////////////////////////////////////////////////
void updatebullet(int num)
{
int x = bullets[num].x;
int y = bullets[num].y;
//is the bullet active?
if (!bullets[num].alive) return;
//erase bullet
rectfill(screen, x, y, x+10, y+10, BLACK);
//move bullet
bullets[num].x += bullets[num].xspd;
bullets[num].y += bullets[num].yspd;
x = bullets[num].x;
y = bullets[num].y;
//stay within the screen
if (x < 6 || x > SCREEN_W-6 || y < 20 || y > SCREEN_H-6)
{
bullets[num].alive = 0;
Enhancing Tank War 267
return;
}
//look for a direct hit using basic collision
//tank is either 0 or 1, so negative num = other tank
int tx = tanks[!num].x;
int ty = tanks[!num].y;
if (x > tx-16 && x < tx+16 && y > ty-16 && y < ty+16)
{
//kill the bullet
bullets[num].alive = 0;
//blow up the tank
explode(num, x, y);
score(num);
}
else
//if no hit then draw the bullet
{
//draw bullet sprite
draw_sprite(screen, bullet_bmp, x, y);
//update the bullet positions (for debugging)
textprintf(screen, font, SCREEN_W/2-50, 1, TAN,
“B1 %-3dx%-3d B2 %-3dx%-3d”,
bullets[0].x, bullets[0].y,
bullets[1].x, bullets[1].y);
}
}
/////////////////////////////////////////////////////////////////////
// fireweapon function
// set bullet direction and speed and activate it
/////////////////////////////////////////////////////////////////////
void fireweapon(int num)
{
int x = tanks[num].x;
int y = tanks[num].y;
//load bullet image if necessary
if (bullet_bmp == NULL)
{
Chapter 8
■
Basic Sprite Programming268
bullet_bmp = load_bitmap(“bullet.bmp”, NULL);
}
//ready to fire again?
if (!bullets[num].alive)
{
bullets[num].alive = 1;
//fire bullet in direction tank is facing
switch (tanks[num].dir)
{
//north
case 0:
bullets[num].x = x-2;
bullets[num].y = y-22;
bullets[num].xspd = 0;
bullets[num].yspd = -BULLETSPEED;
break;
//NE
case 1:
bullets[num].x = x+18;
bullets[num].y = y-18;
bullets[num].xspd = BULLETSPEED;
bullets[num].yspd = -BULLETSPEED;
break;
//east
case 2:
bullets[num].x = x+22;
bullets[num].y = y-2;
bullets[num].xspd = BULLETSPEED;
bullets[num].yspd = 0;
break;
//SE
case 3:
bullets[num].x = x+18;
bullets[num].y = y+18;
bullets[num].xspd = BULLETSPEED;
bullets[num].yspd = BULLETSPEED;
break;
//south
case 4:
bullets[num].x = x-2;
bullets[num].y = y+22;
Enhancing Tank War 269
bullets[num].xspd = 0;
bullets[num].yspd = BULLETSPEED;
break;
//SW
case 5:
bullets[num].x = x-18;
bullets[num].y = y+18;
bullets[num].xspd = -BULLETSPEED;
bullets[num].yspd = BULLETSPEED;
break;
//west
case 6:
bullets[num].x = x-22;
bullets[num].y = y-2;
bullets[num].xspd = -BULLETSPEED;
bullets[num].yspd = 0;
break;
//NW
case 7:
bullets[num].x = x-18;
bullets[num].y = y-18;
bullets[num].xspd = -BULLETSPEED;
bullets[num].yspd = -BULLETSPEED;
break;
}
}
}
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 must
support eight directions (evident in the
if
statement within
turnleft
and
turnright
).
/////////////////////////////////////////////////////////////////////
// forward function
// increase the tank’s speed
/////////////////////////////////////////////////////////////////////
void forward(int num)
{
tanks[num].speed++;
if (tanks[num].speed > MAXSPEED)
tanks[num].speed = MAXSPEED;
}
Chapter 8
■
Basic Sprite Programming270
/////////////////////////////////////////////////////////////////////
// backward function
// decrease the tank’s speed
/////////////////////////////////////////////////////////////////////
void backward(int num)
{
tanks[num].speed—;
if (tanks[num].speed < -MAXSPEED)
tanks[num].speed = -MAXSPEED;
}
/////////////////////////////////////////////////////////////////////
// turnleft function
// rotate the tank counter-clockwise
/////////////////////////////////////////////////////////////////////
void turnleft(int num)
{
//***
tanks[num].dir—;
if (tanks[num].dir < 0)
tanks[num].dir = 7;
}
/////////////////////////////////////////////////////////////////////
// turnright function
// rotate the tank clockwise
/////////////////////////////////////////////////////////////////////
void turnright(int num)
{
tanks[num].dir++;
if (tanks[num].dir > 7)
tanks[num].dir = 0;
}
/////////////////////////////////////////////////////////////////////
// getinput function
// check for player input keys (2 player support)
/////////////////////////////////////////////////////////////////////
void getinput()
{
//hit ESC to quit
if (key[KEY_ESC]) gameover = 1;
Enhancing Tank War 271
//WASD - SPACE keys control tank 1
if (key[KEY_W]) forward(0);
if (key[KEY_D]) turnright(0);
if (key[KEY_A]) turnleft(0);
if (key[KEY_S]) backward(0);
if (key[KEY_SPACE]) fireweapon(0);
//arrow - ENTER keys control tank 2
if (key[KEY_UP]) forward(1);
if (key[KEY_RIGHT]) turnright(1);
if (key[KEY_DOWN]) backward(1);
if (key[KEY_LEFT]) turnleft(1);
if (key[KEY_ENTER]) fireweapon(1);
//short delay after keypress
rest(20);
}
The next short code section includes the
score
function that is used to update the score for
each player.
/////////////////////////////////////////////////////////////////////
// score function
// add a point to a player’s score
/////////////////////////////////////////////////////////////////////
void score(int player)
{
//update score
int points = ++tanks[player].score;
//display score
textprintf(screen, font, SCREEN_W-70*(player+1), 1,
BURST, “P%d: %d”, player+1, points);
}
The
setuptanks
function has changed dramatically from the last version because that is
where the new tank bitmaps are loaded. Since this game uses the
rotate_sprite
function to
generate the sprite images for all eight directions, this function takes care of that by first
creating each image and then blitting the source tank image into each new image with a
specified rotation angle. The end result is two tanks fully rotated in eight directions.
/////////////////////////////////////////////////////////////////////
// setuptanks function
// load tank bitmaps and position the tank
/////////////////////////////////////////////////////////////////////
Chapter 8
■
Basic Sprite Programming272
void setuptanks()
{
int n;
//configure player 1’s tank
tanks[0].x = 30;
tanks[0].y = 40;
tanks[0].speed = 0;
tanks[0].score = 0;
tanks[0].dir = 3;
//load first tank bitmap
tank_bmp[0][0] = load_bitmap(“tank1.bmp”, NULL);
//rotate image to generate all 8 directions
for (n=1; n<8; n++)
{
tank_bmp[0][n] = create_bitmap(32, 32);
clear_bitmap(tank_bmp[0][n]);
rotate_sprite(tank_bmp[0][n], tank_bmp[0][0],
0, 0, itofix(n*32));
}
//configure player 2’s tank
tanks[1].x = SCREEN_W-30;
tanks[1].y = SCREEN_H-30;
tanks[1].speed = 0;
tanks[1].score = 0;
tanks[1].dir = 7;
//load second tank bitmap
tank_bmp[1][0] = load_bitmap(“tank2.bmp”, NULL);
//rotate image to generate all 8 directions
for (n=1; n<8; n++)
{
tank_bmp[1][n] = create_bitmap(32, 32);
clear_bitmap(tank_bmp[1][n]);
rotate_sprite(tank_bmp[1][n], tank_bmp[1][0],
0, 0, itofix(n*32));
}
}
Enhancing Tank War 273
The next section of the code includes the
setupscreen
function. The most important
change to this function is the inclusion of a single line calling
set_color_depth(32)
, which
causes the game to run in 32-bit color mode. Note that if you don’t have a 32-bit video
card, you might want to change this to 16 (which will still work).
/////////////////////////////////////////////////////////////////////
// setupscreen function
// set up the graphics mode and draw the game screen
/////////////////////////////////////////////////////////////////////
void setupscreen()
{
//set video mode
set_color_depth(32);
int ret = set_gfx_mode(MODE, WIDTH, HEIGHT, 0, 0);
if (ret != 0) {
allegro_message(allegro_error);
return;
}
//print title
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
main
function. Several changes have been made in
main
, notably the removal of
the calls to
clearpath
(which checked for bullet hits by looking directly at pixel color). The
call to
rest
now has a value of 10 to speed up the game a bit in order to have smoother
bullet trajectories. There is also a line of code that displays the direction of each tank, as I
explained previously.
/////////////////////////////////////////////////////////////////////
// main function
// start point of the program
/////////////////////////////////////////////////////////////////////
void main(void)
{
//initialize the game
Chapter 8
■
Basic Sprite Programming274
allegro_init();
install_keyboard();
install_timer();
srand(time(NULL));
setupscreen();
setuptanks();
//game loop
while(!gameover)
{
textprintf(screen, font, 0, SCREEN_H-10, WHITE,
“DIRS %d , %d”, tanks[0].dir, tanks[1].dir);
//erase the tanks
erasetank(0);
erasetank(1);
//move the tanks
movetank(0);
movetank(1);
//draw the tanks
drawtank(0);
drawtank(1);
//update the bullets
updatebullet(0);
updatebullet(1);
//check for keypresses
if (keypressed())
getinput();
//slow the game down
rest(10);
}
//end program
allegro_exit();
}
END_OF_MAIN();
Enhancing Tank War 275
Summary
This 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 basics
without delving too deeply into sprite programming theory. The next chapter covers some
advanced 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 more
new 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?
A. Bitmap
B. Sprite
C. Fairy
D. Mouse cursor
2. Which function draws a sprite?
A.
draw_sprite
B.
show_sprite
C.
display_sprite
D.
blit_sprite
3. What is the term for drawing all but a certain color of pixel from one bitmap to
another?
A. Alpha blending
B. Translucency
C. Transparency
D. Telekinesis
4. Which function draws a scaled sprite?
A.
stretch_sprite
B.
draw_scaled_sprite
C.
draw_stretched_sprite
D.
scale_sprite
Chapter 8
■
Basic Sprite Programming276
5. Which function draws a vertically-flipped sprite?
A.
draw_vertical_flip
B.
draw_sprite_v_flip
C.
flip_v_sprite
D.
draw_flipped_sprite
6. Which function draws a rotated sprite?
A.
rotate_angle
B.
draw_rotated_sprite
C.
draw_rotation
D.
rotate_sprite
7. Which function draws a sprite with both rotation and scaling?
A.
draw_sprite_rotation_scaled
B.
rotate_scaled_sprite
C.
draw_rotated_scaled_sprite
D.
scale_rotate_sprite
8. What function draws a pivoted sprite?
A.
draw_pivoted_sprite
B.
draw_pivot_sprite
C.
pivot_sprite
D.
draw_sprite_pivot
9. Which function draws a pivoted sprite with scaling and vertical flip?
A.
pivot_scaled_sprite_v_flip
B.
pivot_stretch_v_flip_sprite
C.
draw_scaled_pivoted_flipped_sprite
D.
scale_pivot_v_flip_sprite
10. Which function draws a sprite with translucency (alpha blending)?
A.
alpha_blend_sprite
B.
draw_trans_sprite
C.
draw_alpha
D.
trans_sprite
Chapter Quiz 277
This page intentionally left blank
279
Advanced Sprite
Programming: Animation,
Compiled Sprites, and
Collision Detection
chapter 9
I
f 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:
■
Working with animated sprites
■
Using run-length encoded sprites
■
Working with compiled sprites
■
Understanding collision detection
Chapter 9
■
Advanced Sprite Programming280
Animated Sprites
The sprites you have seen thus far were handled somewhat haphazardly, in that no real
structure was available for keeping track of these sprites. They have simply been loaded
using
load_bitmap
and 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 single
person can foresee the needs of another game programmer because every game has a
unique set of requirements (more or less). Limiting another programmer (who may be far
more talented than you) to using your concept of a sprite handler only encourages that
person to ignore your handler and write his own. That is exactly why Allegro has no sprite
handler; rather, it simply has a great set of low-level sprite routines, the likes of which you
have already seen.
What should you do next, then? The real challenge is not designing a handler for working
with 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 some
gameplay 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. I
have written a quick little program that loads six images (of an animated cat) and draws
them on the screen. The cat runs across the screen from left to right, using the sprite
frames 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 moved
across the screen (see Figure 9.2).
Figure 9.1 The animated cat sprite, courtesy of Ari Feldman
#include <conio.h>
#include <stdlib.h>
#include <stdio.h>
#include “allegro.h”
#define WHITE makecol(255,255,255)
#define BLACK makecol(0,0,0)
BITMAP *kitty[7];
char s[20];
int curframe=0, framedelay=5, framecount=0;
int x=100, y=200, n;
int main(void)
{
//initialize the program
allegro_init();
install_keyboard();
install_timer();
set_color_depth(16);
set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
Animated Sprites 281
Figure 9.2 The
AnimSprite
program shows how you can do basic sprite
animation.
textout(screen, font, “AnimSprite Program (ESC to quit)”,
0, 0, WHITE);
//load the animated sprite
for (n=0; n<6; n++)
{
sprintf(s,”cat%d.bmp”,n+1);
kitty[n] = load_bitmap(s, NULL);
}
//main loop
while(!key[KEY_ESC])
{
//erase the sprite
rectfill(screen, x, y, x+kitty[0]->w, y+kitty[0]->h, BLACK);
//update the position
x += 5;
if (x > SCREEN_W - kitty[0]->w)
x = 0;
//update the frame
if (framecount++ > framedelay)
{
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();
Chapter 9
■
Advanced Sprite Programming282
rest(10);
}
return 0;
}
END_OF_MAIN();
Now 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
variable.
//update the frame
if (framecount++ > framedelay)
{
framecount = 0;
curframe++;
if (curframe > 5)
curframe = 0;
}
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
sprite is motionless).
//update the position
x += 5;
if (x > SCREEN_W - kitty[0]->w)
x = 0;
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
Animated Sprites 283
“class it.” That’s all well and good, but I don’t care what C++ programmers claim—it’s
more difficult to understand, which is the key reason why this book focuses on C. That
Allegro itself is written in C only supports this decision. The actual bitmap images for the
sprite 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
{
int x,y;
int width,height;
int xspeed,yspeed;
int xdelay,ydelay;
int xcount,ycount;
int curframe,maxframe,animdir;
int framecount,framedelay;
}SPRITE;
The variables inside a struct are called struct elements, so I will refer to them as such (see
Figure 9.3).
The first two elements (
x
,
y
)
track the sprite’s position. The
next two (
width
,
height
) are set
to the size of the sprite image
(stored outside the struct). The
velocity elements (
xspeed
,
yspeed
)
determine how many pixels the
sprite will move in conjunction
with the velocity delay (
xdelay
,
xcount
and
ydelay
,
ycount
). The
velocity delay allows some
sprites to move much slower
than other sprites on the
screen—even more slowly than
one pixel per frame. This gives
you 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
animdir
element is of par-
ticular interest because it allows you to reverse the direction that the sprite frames are
drawn (from 0 to
maxframe
or from
maxframe
to 0, with looping in either direction). The main
reason why the
BITMAP
array containing the sprite images is not stored inside the struct is
because that is wasteful—there might be many sprites sharing the same animation images.
Chapter 9
■
Advanced Sprite Programming284
Figure 9.3 The
SPRITE
struct and its elements help abstract
sprite movement into reusable code.
Now that we have a sprite struct, the actual handler is contained in a function that I will
call
updatesprite
:
void updatesprite(SPRITE *spr)
{
//update x position
if (++spr->xcount > spr->xdelay)
{
spr->xcount = 0;
spr->x += spr->xspeed;
}
//update y position
if (++spr->ycount > spr->ydelay)
{
spr->ycount = 0;
spr->y += spr->yspeed;
}
//update frame based on animdir
if (++spr->framecount > spr->framedelay)
{
spr->framecount = 0;
if (spr->animdir == -1)
{
if (—spr->curframe < 0)
spr->curframe = spr->maxframe;
}
else if (spr->animdir == 1)
{
if (++spr->curframe > spr->maxframe)
spr->curframe = 0;
}
}
}
As you can see,
updatesprite
accepts a pointer to a
SPRITE
variable. 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
animdir
to
increment or decrement the
framecount
element.
Animated Sprites 285
However,
updatesprite
was not designed to affect sprite behavior, only to manage the logis-
tics of sprite movement. After
updatesprite
has been called, you want to deal with that
sprite’s behavior within the game. For instance, if you are writing a space-based shooter
featuring a spaceship and objects (such as asteroids) that the ship must shoot, then you
might assign a simple warping behavior to the asteroids so that when they exit one side of
the screen, they will appear at the opposite side. Or, in a more realistic game featuring a
larger 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 by
another function that affects the behavior of all asteroids in the game and rely on custom
or random values for each asteroid’s struct elements to cause it to behave slightly differently
than 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 simple
behavior that I often use in example programs is to have a sprite bounce off the edges of
the 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 it
seems 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! You
have 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 source
code starts to grow out of control (which frequently happens when you have a truly great
game 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 single
bitmap 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)
Chapter 9
■
Advanced Sprite Programming286
//define the sprite structure
typedef struct SPRITE
{
int x,y;
int width,height;
int xspeed,yspeed;
int xdelay,ydelay;
int xcount,ycount;
int curframe,maxframe,animdir;
int framecount,framedelay;
}SPRITE;
//sprite variables
BITMAP *ballimg[16];
SPRITE theball;
SPRITE *ball = &theball;
//support variables
char s[20];
int n;
Animated Sprites 287
Figure 9.4 The
SpriteHandler
program demonstrates a full-featured
animated sprite handler.
void 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);
}
void updatesprite(SPRITE *spr)
{
//update x position
if (++spr->xcount > spr->xdelay)
{
spr->xcount = 0;
spr->x += spr->xspeed;
}
//update y position
if (++spr->ycount > spr->ydelay)
{
spr->ycount = 0;
spr->y += spr->yspeed;
}
//update frame based on animdir
if (++spr->framecount > spr->framedelay)
{
spr->framecount = 0;
if (spr->animdir == -1)
{
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)
{
Chapter 9
■
Advanced Sprite Programming288
//simple screen bouncing behavior
if (spr->x < 0)
{
spr->x = 0;
spr->xspeed = rand() % 2 + 4;
spr->animdir *= -1;
}
else if (spr->x > SCREEN_W - spr->width)
{
spr->x = SCREEN_W - spr->width;
spr->xspeed = rand() % 2 - 6;
spr->animdir *= -1;
}
if (spr->y < 40)
{
spr->y = 40;
spr->yspeed = rand() % 2 + 4;
spr->animdir *= -1;
}
else if (spr->y > SCREEN_H - spr->height)
{
spr->y = SCREEN_H - spr->height;
spr->yspeed = rand() % 2 - 6;
spr->animdir *= -1;
}
}
void main(void)
{
//initialize
allegro_init();
set_color_depth(16);
set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
install_keyboard();
install_timer();
srand(time(NULL));
textout(screen, font, “SpriteHandler Program (ESC to quit)”,
0, 0, WHITE);
Animated Sprites 289
//load sprite images
for (n=0; n<16; n++)
{
sprintf(s,”ball%d.bmp”,n+1);
ballimg[n] = load_bitmap(s, NULL);
}
//initialize the sprite with lots of randomness
ball->x = rand() % (SCREEN_W - ballimg[0]->w);
ball->y = rand() % (SCREEN_H - ballimg[0]->h);
ball->width = ballimg[0]->w;
ball->height = ballimg[0]->h;
ball->xdelay = rand() % 2 + 1;
ball->ydelay = rand() % 2 + 1;
ball->xcount = 0;
ball->ycount = 0;
ball->xspeed = rand() % 2 + 4;
ball->yspeed = rand() % 2 + 4;
ball->curframe = 0;
ball->maxframe = 15;
ball->framecount = 0;
ball->framedelay = rand() % 3 + 1;
ball->animdir = 1;
//game loop
while (!key[KEY_ESC])
{
erasesprite(screen, ball);
//perform standard position/frame update
updatesprite(ball);
//now do something with the sprite—a basic screen bouncer
bouncesprite(ball);
//lock the screen
acquire_screen();
//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”,
Chapter 9
■
Advanced Sprite Programming290
Animated Sprites 291
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
release_screen();
rest(10);
}
for (n=0; n<15; n++)
destroy_bitmap(ballimg[n]);
return;
}
END_OF_MAIN();
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.