chopper class use these.
We will also make a background class. The code now looks
like this:
#include <allegro.h>
#include <stl.h>
#include "tutorial.h"
#define MIN_Y 8
#define MAX_Y 180
DATAFILE*data;
BITMAP*backdrop,*framebuf;
class sprite {
protected:
fix X,Y;
RLE_SPRITE*image;
public:
sprite(fix _X,fix _Y) { X=_X; Y=_Y; image=NULL; }
sprite(fix _X,fix _Y,RLE_SPRITE*img) { X=_X; Y=_Y; image=img; }
virtual ~sprite() {}
virtual void draw(BITMAP*dest) {
draw_rle_sprite(dest,image,X,Y);
}
virtual void clippos() { if (Y+image->h>MAX_Y) Y=MAX_Y-image->h; }
virtual void move(fix DX,fix DY) { X+=DX; Y+=DY; clippos(); }
virtual void place(fix NX,fix NY) { X=NX; Y=NY; clippos(); }
virtual int outside(fix _X,fix _Y) { return (_X<-image->w)||(_X>=SCREEN_W); }
virtual bool animate() { return FALSE; }
};
typedef list<sprite*> sprite_list;
sprite_list sprites;
class background : public sprite {
public:
background() : sprite(0,MIN_Y) {}
virtual void draw(BITMAP*dest) {
blit(backdrop,dest,0,0,X,Y,backdrop->w,backdrop->h);
}
};
class chopper : public sprite {
protected:
int frame;
public:
chopper(fix _X,fix _Y)
: sprite(_X,_Y,(RLE_SPRITE*)data[TUT_CHOPPER].dat) { frame=0; }
virtual void clippos() {
if (X<0) X=0;
if (X+image->w>SCREEN_W) X=SCREEN_W-image->w;
if (Y<MIN_Y) Y=MIN_Y;
sprite::clippos();
}
virtual bool animate();
};
bool chopper::animate()
{
fix DX=0,DY=0;
if (key[KEY_LEFT]||joy_left) --DX;
if (key[KEY_RIGHT]||joy_right) ++DX;
if (key[KEY_UP]||joy_up) --DY;
if (key[KEY_DOWN]||joy_down) ++DY;
move(DX,DY);
if (frame) { image=(RLE_SPRITE*)data[TUT_CHOPPER1].dat; frame=0; }
else { image=(RLE_SPRITE*)data[TUT_CHOPPER2].dat; frame=1; }
return FALSE;
}
class chopper2 : public chopper {
public:
chopper2(fix _X,fix _Y) : chopper(_X,_Y) {}
virtual void move(fix DX,fix DY) { X-=DX; Y-=DY; clippos(); }
};
int main()
{
allegro_init();
install_keyboard();
initialise_joystick();
data=load_datafile("tutorial.dat");
set_gfx_mode(GFX_VGA,320,200,0,0);
set_palette((RGB*)data[TUT_GAMEPAL].dat);
// create 320x192 backdrop
backdrop=create_bitmap(320,192);
for (int Y=0; Y<128; Y++) hline(backdrop,0,Y,319, (Y/2)+128);
for (int Y=128; Y<192; Y++) hline(backdrop,0,Y,319, ((Y-128)/2)+192);
// create 320x200 double buffer
framebuf=create_bitmap(320,200);
clear(framebuf);
background Back;
chopper Hero(50,100);
chopper2 AnotherHero(250,50);
sprites.push_back(&Back);
sprites.push_back(&Hero);
sprites.push_back(&AnotherHero);
while (!key[KEY_ESC]) {
// draw sprites
{
sprite_list::const_iterator spr=sprites.begin();
while (spr!=sprites.end()) {
(*spr)->draw(framebuf);
spr++;
}
}
// display frame
vsync();
blit(framebuf,screen,0,0,0,0,320,200);
// animate sprites
poll_joystick();
{
sprite_list::const_iterator spr=sprites.begin();
while (spr!=sprites.end()) {
(*spr)->animate();
spr++;
}
}
}
return 0;
}
In the course of writing any application, and games are no exception,
there are often times when parts of the code has to be rethought and
rewritten. That is essentially what we have done here. We have moved
the RLE sprite drawing code and some coordinate manipulation code
into the base class. The base class' default clippos()
makes sure no object falls below ground level.
Its default outside() checks if an object is totally outside
the screen in the horizontal direction, since this is the direction we want
the game to scroll. chopper() overrides clippos()
to keep the helicopter totally on-screen. (Note that it takes care of only
three of the edges, and calls upon the inherited clippos() to
take care of the fourth.) chopper2 overrides the
move() method to reverse the direction moved, thus avoiding
having to replace the entire animate() method. We have also
created the background class that overrides the default
draw() method to draw the backdrop, and put an instance of
this class into the sprite list, thus simplifying the game loop even
further.
animate()
method return a bool. This was done as a preparation for the
dynamic sprites we are now going to create. If a sprite is created dynamically,
it should also be deleted when it is no longer needed, to avoid running out
of memory. To signal this, we will let the animate() method
return TRUE when the sprite is no longer needed. So, let's try
it by writing a class that will handle the lethal stuff we are going to
throw at our poor enemies, and that signals that it should be deleted when
it hits the ground.
Since it's a good idea to have defined the velocity at which our bombs will fall from the helicopter, we will add this somewhere at the top:
#define BOMB_LAUNCH 1 #define GRAVITY 0.1Then we add the weaponry class itself. Add this before
chopper::animate():
class projectile : public sprite {
fix DX,DY;
int force;
public:
projectile(fix _X,fix _Y,fix _DX,fix _DY,RLE_SPRITE*img,int power)
: sprite(_X,_Y,img) { DX=_DX; DY=_DY; force=power; }
virtual bool animate() {
move(DX,DY); DY+=GRAVITY;
return Y+image->h>=MAX_Y;
}
};
To launch bombs whenever Enter is pressed, change
chopper::animate() to:
bool chopper::animate()
{
fix DX=0,DY=0;
if (key[KEY_LEFT]||joy_left) --DX;
if (key[KEY_RIGHT]||joy_right) ++DX;
if (key[KEY_UP]||joy_up) --DY;
if (key[KEY_DOWN]||joy_down) ++DY;
move(DX,DY);
if (key[KEY_ENTER]||joy_b1) {
sprites.push_back(new projectile(X+32,Y+14,
BOMB_LAUNCH,0,(RLE_SPRITE*)data[TUT_BOMB].dat,5));
}
if (frame) { image=(RLE_SPRITE*)data[TUT_CHOPPER1].dat; frame=0; }
else { image=(RLE_SPRITE*)data[TUT_CHOPPER2].dat; frame=1; }
return FALSE;
}
Finally, to delete the sprites that are no longer needed, we need to change
the animation code in the main game loop to:
// animate sprites
poll_joystick();
{
sprite_list::iterator spr=sprites.begin();
while (spr!=sprites.end()) {
sprite*itm=*spr;
if (itm->animate()) {
sprites.erase(spr++);
delete itm;
} else spr++;
}
}
This will save the current sprite, then call animate(). If
it returns true, it will remove the element from the list, move the iterator
to the next element, and then delete the sprite itself; otherwise, it will
move the iterator as before.
With this, the player is now able to pour out bombs at high volume, which is definitely going to be of much value against his enemies, once he gets any.
First, to accurately calculate how much the helicopter has moved (its
current velocity), we will let the base class keep track of what the last
position was, by its animate() method.
class sprite {
protected:
fix X,Y,LX,LY;
RLE_SPRITE*image;
public:
sprite(fix _X,fix _Y) { LX=X=_X; LY=Y=_Y; image=NULL; }
sprite(fix _X,fix _Y,RLE_SPRITE*img) { LX=X=_X; LY=Y=_Y; image=img; }
virtual ~sprite() {}
virtual void draw(BITMAP*dest) {
draw_rle_sprite(dest,image,X,Y);
}
virtual void clippos() { if (Y+image->h>MAX_Y) Y=MAX_Y-image->h; }
virtual void place(fix NX,fix NY) { X=NX; Y=NY; clippos(); }
virtual void move(fix DX,fix DY) { X+=DX; Y+=DY; clippos(); }
virtual int outside(fix _X,fix _Y) { return (_X<-image->w)||(_X>=SCREEN_W); }
virtual bool animate() { LX=X; LY=Y; return FALSE; }
};
Of course, for this to work, the animate() method for the
derived classes has to call this. The chopper class'
animate() method, after adding the difference between the
current and previous coordinates as the velocity to add to the bomb launch
velocity, and doubling the speed of the helicopter itself, is now:
bool chopper::animate()
{
sprite::animate();
fix DX=0,DY=0;
if (key[KEY_LEFT]||joy_left) DX-=2;
if (key[KEY_RIGHT]||joy_right) DX+=2;
if (key[KEY_UP]||joy_up) DY-=2;
if (key[KEY_DOWN]||joy_down) DY+=2;
move(DX,DY);
if (key[KEY_ENTER]||joy_b1||(mouse_b&1)) {
sprites.push_back(new projectile(X+32,Y+14,
X-LX+BOMB_LAUNCH,Y-LY,(RLE_SPRITE*)data[TUT_BOMB].dat,5));
}
if (frame) { image=(RLE_SPRITE*)data[TUT_CHOPPER1].dat; frame=0; }
else { image=(RLE_SPRITE*)data[TUT_CHOPPER2].dat; frame=1; }
return FALSE;
}
#include <allegro.h>
#include <stl.h>
#include "tutorial.h"
#define MIN_Y 8
#define MAX_Y 180
#define BOMB_LAUNCH 1
#define GRAVITY 0.1
DATAFILE*data;
BITMAP*backdrop,*framebuf;
bool usemouse=FALSE;
class sprite {
protected:
fix X,Y,LX,LY;
RLE_SPRITE*image;
public:
sprite(fix _X,fix _Y) { LX=X=_X; LY=Y=_Y; image=NULL; }
sprite(fix _X,fix _Y,RLE_SPRITE*img) { LX=X=_X; LY=Y=_Y; image=img; }
virtual ~sprite() {}
virtual void draw(BITMAP*dest) {
draw_rle_sprite(dest,image,X,Y);
}
virtual void clippos() { if (Y+image->h>MAX_Y) Y=MAX_Y-image->h; }
virtual void place(fix NX,fix NY) { X=NX; Y=NY; clippos(); }
virtual void move(fix DX,fix DY) { X+=DX; Y+=DY; clippos(); }
virtual int outside(fix _X,fix _Y) { return (_X<-image->w)||(_X>=SCREEN_W); }
virtual bool animate() { LX=X; LY=Y; return FALSE; }
};
typedef list<sprite*> sprite_list;
sprite_list sprites;
class background : public sprite {
public:
background() : sprite(0,MIN_Y) {}
virtual void draw(BITMAP*dest) {
blit(backdrop,dest,0,0,X,Y,backdrop->w,backdrop->h);
}
};
class chopper : public sprite {
protected:
int frame;
public:
chopper(fix _X,fix _Y)
: sprite(_X,_Y,(RLE_SPRITE*)data[TUT_CHOPPER].dat) {
frame=0;
position_mouse(X,Y);
}
virtual void clippos() {
if (X<0) X=0;
if (X+image->w>SCREEN_W) X=SCREEN_W-image->w;
if (Y<MIN_Y) Y=MIN_Y;
sprite::clippos();
}
virtual bool animate();
};
class projectile : public sprite {
fix DX,DY;
int force;
public:
projectile(fix _X,fix _Y,fix _DX,fix _DY,RLE_SPRITE*img,int power)
: sprite(_X,_Y,img) { DX=_DX; DY=_DY; force=power; }
virtual bool animate() {
move(DX,DY); DY+=GRAVITY;
return Y+image->h>=MAX_Y;
}
};
bool chopper::animate()
{
sprite::animate();
if (usemouse) place(mouse_x,mouse_y);
fix DX=0,DY=0;
if (key[KEY_LEFT]||joy_left) DX-=2;
if (key[KEY_RIGHT]||joy_right) DX+=2;
if (key[KEY_UP]||joy_up) DY-=2;
if (key[KEY_DOWN]||joy_down) DY+=2;
move(DX,DY);
if (usemouse) position_mouse(X,Y);
if (key[KEY_ENTER]||joy_b1||(mouse_b&1)) {
sprites.push_back(new projectile(X+32,Y+14,
X-LX+BOMB_LAUNCH,Y-LY,(RLE_SPRITE*)data[TUT_BOMB].dat,5));
}
if (frame) { image=(RLE_SPRITE*)data[TUT_CHOPPER1].dat; frame=0; }
else { image=(RLE_SPRITE*)data[TUT_CHOPPER2].dat; frame=1; }
return FALSE;
}
int main()
{
allegro_init();
install_keyboard();
// comment out the next line if you don't want mouse
usemouse=(install_mouse()!=-1);
initialise_joystick();
data=load_datafile("tutorial.dat");
set_gfx_mode(GFX_VGA,320,200,0,0);
set_palette((RGB*)data[TUT_GAMEPAL].dat);
// create 320x192 backdrop
backdrop=create_bitmap(320,192);
for (int Y=0; Y<128; Y++) hline(backdrop,0,Y,319, (Y/2)+128);
for (int Y=128; Y<192; Y++) hline(backdrop,0,Y,319, ((Y-128)/2)+192);
// create 320x200 double buffer
framebuf=create_bitmap(320,200);
clear(framebuf);
background Back;
chopper Hero(50,100);
sprites.push_back(&Back);
sprites.push_back(&Hero);
while (!key[KEY_ESC]) {
// draw sprites
{
sprite_list::const_iterator spr=sprites.begin();
while (spr!=sprites.end()) {
(*spr)->draw(framebuf);
spr++;
}
}
// display frame
vsync();
blit(framebuf,screen,0,0,0,0,320,200);
// animate sprites
poll_joystick();
{
sprite_list::iterator spr=sprites.begin();
while (spr!=sprites.end()) {
sprite*itm=*spr;
if (itm->animate()) {
sprites.erase(spr++);
delete itm;
} else spr++;
}
}
}
return 0;
}
It is reasonably clear what we've done; install_mouse() returns
-1 if a mouse is not installed; mouse_x and
mouse_y contains the current mouse coordinates;
position_mouse sets new coordinates (in case the keyboard or
joystick was used to move the helicopter). You can now hold down the mouse
button and move the mouse around to discover how the law of physics relate
to gameplay (and jerky mouse motion).
For more exciting gameplay, proceed to the next chapter