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