So far we have covered some pretty fancy game programming stuff. If you are interested in developing some great games though you should know that the amount of code required is rather big. Managing such a programming project means that you should learn some programming techniques that will help you design and implement your game. These techniques have been used in many programs and are very well documented.
First in the list of tricks is the simplification of the flow of control within our game. Typically our game changes its behavior according to its state. My initial approach to this problem (when the dinosaurs ruled the earth) was to declare a global variable called 'state' and fill every function with 'if' clauses depending on its value. The final program was all in one huge file that I couldn't break it down to smaller files due to the dependencies that tied the functions together.
Then was then and now is now. The advent of OO paradigm and the use of C++ gave us the opportunity to design better approaches to the problem. The actual solution to the flow of control in a state machine such as a game is very simple. At first we create an abstract base class which defines the interface and then derive from it a class for each state in your game. Then your game class needs to have a pointer to the abstract class which points to the correct state every time redirecting the flow of control.
Here is the code for the abstract base class. The only function the derived classes have to implement is the drawing function 'frame_render'. Making 'frame_render' a pure virtual function makes it impossible to instantiate a 'clf_dispatch' object and you will have to derive a state class which will at least handle drawing.
// this is actually a dispatch base class // classes derived from this will handle the gameplay depending on game state class clf_dispatch{ public: clf_dispatch(){ } virtual ~clf_dispatch(){ } // reshape the window when it's moved or resized virtual void window_resize (int width, int height){ } // perform graphics initialization virtual bool graphics_init(){ return true; } // perform graphics termination virtual void graphics_terminate(){ } // virtual void frame_move(clFloat fElapsed){ } // drawing function, the least a state class has to do virtual void frame_render()=0; };
Now let's suppose that the game has just started and the initialization process is rather long. You need some mechanism to handle the game loop and display just a 'please wait' message to the user. We said earlier that our game class has a pointer to the base state class which points to the current state object. So when it is fired up our first responsibility is to instantiate an object that will handle the wait state and make the current state pointer point to this object. Here is the code to do it.
class wait_handler : public clf_dispatch { public: cCamera m_cam; // camera clf_stage m_stage; clf_light m_light; // light wait_handler() : clf_dispatch(){ } virtual void window_resize (int width, int height){ // reshape the window when it's moved or resized m_stage.resize(0,0,width, height); } virtual bool graphics_init(){ // make sure the required font is loaded get_font_manager().create_font_3d("Bookman3D", "Bookman Old Style", 20, .2f); // set up menu light and camera m_cam.setup(clf_vector3D(0,0,-7), // position viewer clf_vector3D(0,0,0), // setup the point of interest (where we are looking at) clf_vector3D(0,1,0)); // which way is up // Light model parameters: // ------------------------------------------- // create and position a light source m_light.set_ambient(0.25f, 0.25f, 0.25f, 1.f); m_light.set_diffuse(0.7f, 0.7f, 0.7f, 1.f); m_light.set_specular(1.f, 1.f, 1.f, 1.f); m_light.set_position(0.f, 0.f, -100.f, 0.f); return true; } virtual void frame_render(){ // clear the screen glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear screen and depth buffer glMatrixMode (GL_PROJECTION); // select the projection matrix glLoadIdentity (); // reset the projection matrix // viewport and perspective m_stage.prepare(); glMatrixMode (GL_MODELVIEW); // select the modelview matrix glLoadIdentity (); // reset glPushMatrix(); // position the viewer m_cam.apply(); glPushAttrib(GL_ALL_ATTRIB_BITS); glEnable(GL_NORMALIZE); // position the light m_light.enable(); // reapply the light m_light.apply(); clf_font* pfont = get_font_manager().get_font("Bookman3D"); pfont->set_color_RGB(0.f,0.f,.95f); pfont->print_3d(ALIGN_CENTER, "initializing..."); glPopMatrix(); glPopAttrib(); } };
All the state class needs to handle is the initialization of the game where it loads resources and initializes internal state, the resize of the window where it resets the projection parameters and finally the screen update where it displays the message to the user.
For the last step we just have to use this state handling mechanism. First we declare a pointer to the 'wait' class as a private member variable of the game class. Then we instantiate an object within the game constructor and set the general state pointer to the new object. Now whenever the 'frame_move' and 'frame_render' functions are called, they can pass control to the active state. The next step is to call the 'window_resize', 'graphics_init' and 'graphics_terminate' functions of ALL the state handling instances from within the respective game functions. This will allow your objects to be updated for every change that might occur in your game and thus react correctly whenever you need them.
class theGame : public clf_application { clf_dispatch* current_dispatch; wait_handler* m_wh; public: theGame() { m_wh = new wait_handler(); current_dispatch = m_wh; ... the rest of the class construction } bool graphics_init() { // first we initialize the idle state handler m_wh->graphics_init(); ... and then all the other objects in our game } void window_resize (int width, int height) { // remember to update ALL the state handlers! m_wh->window_resize(width, height); normal_game->window_resize(width, height); } void theGame::graphics_terminate() { // upon termination as well m_wh->graphics_terminate(); normal_game->graphics_terminate(); get_font_manager().clear(); } ... the rest of the game class follows };
This approach to the dispatching of the game control when we need to support different states can be improved to automate the handling of the cases like 'window_resize' when we have to update all the states so they can be prepared every time they will be called. If you search the internet you will find patterns already tested that can be applied to your games.
Another big problem for starting C++ programmers is defining callback functions. Callbacks are mostly used as notification mechanisms about events that might occur within a system. To be more specific when the user presses a button or selects an option form a menu. When these events take place the interested object, the application or any other, is notified and can take appropriate actions.
If your program is in C then things are very simple from the code writing point of view. All it takes is to define which function will act as callback by passing its pointer via its name.
This can also be done in C++ as well, but it generates several design problems in our application. This callback function will be invoked but it has no clue as to which object to notify about the event. Then it is an external function to the class so it has no access to private members. Sure you can overcome both problems by passing a pointer of the object when setting the callback and declaring the function as a friend to the class. Passing a pointer to the instance interested in the event is something we cannot avoid anyway. Declaring friend functions though is not such an elegant OO solution and we should use it only when all else fails and we design solutions to very complex problems. The next thing we can do is make the callback function static in the class. This does not solve the object reference need since there is no 'this' pointer available for static functions.
What we actually want is a pointer to a member function. Function pointers in C++ are not regular pointers though. They are relative pointers tied to the object they belong. So we need a way to store the information about the object and the function pointer and finally invoke the callback when the time comes. It is not very difficult to create an object that can hold a pointer to some object and a pointer to a member function. Since we need a specialized type for every different receiver type we can use templates and let the compiler generate the code. Finally we can hide all these with an abstract base class and voila we have our mechanism.
Now that I have managed to make you think that this is a solution for the chosen few, take a look at the code and you will realize that whatever sounded so complicated is actually simple. I admit this is a solution I found in book a long time ago and I was really amazed by the simplicity and elegance of the approach.
// abstract base class class clf_functor { public: // virtual cause derived classes will use a pointer to an object // and a pointer to a member function to make the function call virtual void invoke(int option)=0; }; // derived template class template <typename T> class clf_functor_inst : public clf_functor { void (T::*fpt)(int); // pointer to member function T* pt2Object; // pointer to object public: // constructor - takes pointer to an object and pointer to a member and stores // them in two private variables clf_functor_inst(T* _pt2Object, void(T::*_fpt)(int)) { pt2Object = _pt2Object; fpt=_fpt; }; // override function "Call" virtual void invoke(int option) { (*pt2Object.*fpt)(option); }; };
First we create the abstract class which is the actual interface of the calling mechanism. Assuming we have some menu class that needs to call a function upon user selection, all we need is a pointer to the interface class. From this interface we derive the actual worker class that invokes the right function whenever called. This class is defined as a template to allow for automatic specialization whenever needed. Here is a little sample how the mechanism is applied.
class clf_menu : public clf_dispatch { // among all the other stuff within the class we need the callback interface clf_functor* menu_handler; void set_menu_callBack(clf_functor* pf){ menu_handler = pf; } void clf_menu::frame_move(clFloat fElapsed){ // when the user makes a selection if (isKeyPressed(DIK_RETURN)) { // if we have a valid handler invoke it if (menu_handler) menu_handler->invoke(entries[option].entry_id); } } }; // and when we initialize the menu we create a functor static clf_functor_inst<theGame> callback_fkt(this, &theGame::menu_cback); // and pass it to the menu m_menu.set_menu_callBack(&callback_fkt);
It doesn't look that hard now does it? You can download the full sample from here.