So, what we are going to do is to write an OOP backbone that we can base our game objects on, and then use it to write an action game. We will need to make an object base class, and find some way of managing all the independent objects so that they can be used. The latter is where STL comes in. But first, we need to know to make a basic object class.
class chopper { public: int X,Y; };In many ways, this is a lot like a
struct
, and can be used in
much the same way:
#include <allegro.h> #include "tutorial.h" #define MIN_Y 8 DATAFILE*data; BITMAP*backdrop,*framebuf; class chopper { public: int X,Y; }; 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); chopper Hero; Hero.X=0; Hero.Y=100; while (!key[KEY_ESC]) { // build frame blit(backdrop,framebuf,0,0,0,MIN_Y,320,200); draw_rle_sprite(framebuf,(RLE_SPRITE*)data[TUT_CHOPPER].dat,Hero.X,Hero.Y); // display frame vsync(); blit(framebuf,screen,0,0,0,0,320,200); // get user input poll_joystick(); if (key[KEY_LEFT]||joy_left) Hero.X--; if (key[KEY_RIGHT]||joy_right) Hero.X++; if (key[KEY_UP]||joy_up) Hero.Y--; if (key[KEY_DOWN]||joy_down) Hero.Y++; } return 0; }but there is in fact a difference, as you would see if you tried to do
chopper Hero={0,100};The
Hero
object is an instance of the chopper
class, and not just a plain structure, thus it would not work.
What the public clause means is that whatever is defined in the class (or struct) after it, will be accessible to code outside the class definition as well, which is necessary for parts of the class that isn't self-contained. Since this class isn't self-contained at all yet, both fields (X and Y) have to be marked public.
As a first step towards making the object self-contained, we will now move the drawing and input code into it.
#include <allegro.h> #include "tutorial.h" #define MIN_Y 8 DATAFILE*data; BITMAP*backdrop,*framebuf; class chopper { public: int X,Y; void draw(BITMAP*dest) { draw_rle_sprite(dest,(RLE_SPRITE*)data[TUT_CHOPPER].dat,X,Y); } void input(); }; void chopper::input() { if (key[KEY_LEFT]||joy_left) X--; if (key[KEY_RIGHT]||joy_right) X++; if (key[KEY_UP]||joy_up) Y--; if (key[KEY_DOWN]||joy_down) Y++; } 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); chopper Hero; Hero.X=0; Hero.Y=100; while (!key[KEY_ESC]) { // build frame blit(backdrop,framebuf,0,0,0,MIN_Y,320,200); Hero.draw(framebuf); // display frame vsync(); blit(framebuf,screen,0,0,0,0,320,200); // get user input poll_joystick(); Hero.input(); } return 0; }This demonstrates two ways of defining a method. Both
chopper::draw()
and chopper::input()
are methods.
Methods are much like functions, except that they operate on an object.
chopper::draw()
is declared and defined inline, this is often
useful to avoid unnecessary method call overhead.
Hero
.
We will define two constructors, the default constructor that sets a default initial position, and a constructor where it is possible to specify the initial position explicitly. C++ allows several functions, methods, and operators with the same name but with different parameter lists to coexist, a feature called "overloading", where the compiler automatically chooses the right function according to the given parameters. We will use this feature to have two different constructors. Change the class definition to read:
class chopper { int X,Y; public: chopper() { X=Y=0; } chopper(int _X,int _Y) { X=_X; Y=_Y; } void draw(BITMAP*dest) { draw_rle_sprite(dest,(RLE_SPRITE*)data[TUT_CHOPPER].dat,X,Y); } void input(); };Then remove the line
Hero.X=0; Hero.Y=100;and try it. The object is now completely encapsulated, the main loop doesn't know anything about X or Y. We have therefore taken them away from under the public clause; they are thus now private to the object, and hidden from everything else. Note that the helicopter now starts at the top left of the screen, because we put the default position in the default constructor to be 0,0. To try the alternate constructor, replace
chopper Hero;with
chopper Hero(50,100);This will still define the variable
Hero
to be of the
data type chopper
, but since chopper
is a class,
its constructor must be called when it is created. This means that we will
call the constructor that takes two integer values and use it, giving the
values 50 and 100, instead of using the default constructor. This alternate
constructor happens to set the initial position to the given values.
So, what we are going to do is to write a base class that we can use as a
foundation for our game elements. We will also use the Allegro-defined
fix
data type here, to enable sprites to move a fractional
amount of pixels per frame. This will enable objects to move slower than
one pixel per frame without standing totally still. Also, we won't bother
to have a default constructor.
class sprite { protected: fix X,Y; public: sprite(fix _X,fix _Y) { X=_X; Y=_Y; } virtual ~sprite() {} virtual void draw(BITMAP*dest) {} virtual void animate() {} };The protected clause is similar to private, except that it allows derived classes to access the elements defined protected. We have now also declared the methods virtual, which means that they are encoded into the object structure in such a way that they can be overridden transparently by derived classes, even if the code that uses the object thinks it's an instance the base class.
When we use this base class, this is how our program will look like.
#include <allegro.h> #include "tutorial.h" #define MIN_Y 8 DATAFILE*data; BITMAP*backdrop,*framebuf; class sprite { protected: fix X,Y; public: sprite(fix _X,fix _Y) { X=_X; Y=_Y; } virtual ~sprite() {} virtual void draw(BITMAP*dest) {} virtual void animate() {} }; class chopper : public sprite { public: chopper(fix _X,fix _Y) : sprite(_X,_Y) {} virtual void draw(BITMAP*dest) { draw_rle_sprite(dest,(RLE_SPRITE*)data[TUT_CHOPPER].dat,X,Y); } virtual void animate(); }; void chopper::animate() { // seems postfix operators aren't defined for the fix class if (key[KEY_LEFT]||joy_left) --X; if (key[KEY_RIGHT]||joy_right) ++X; if (key[KEY_UP]||joy_up) --Y; if (key[KEY_DOWN]||joy_down) ++Y; } 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); chopper Hero(50,100); while (!key[KEY_ESC]) { // build frame blit(backdrop,framebuf,0,0,0,MIN_Y,320,200); Hero.draw(framebuf); // display frame vsync(); blit(framebuf,screen,0,0,0,0,320,200); // get user input poll_joystick(); Hero.animate(); } return 0; }As you can see, you use colons to inherit a base class, as well as to use an inherited constructor. The base class is inherited public to make public members of the base class remain public, and not turned private.
To learn more, proceed to the next chapter