Game Engine Coding Strategy
Game Engine Code Structure
Windows Programming Tidbits
Some Programming Practice
Game engines are among the greatest of programming challenges. There are many distinct components, and in many cases those components need to communicate with and call upon each other. For an existing game design, it might be very clear how it should be set up, but it makes future projects much easier if you strive to make your code easily reusable. You wouldn't want to program yourself into a corner, having to redo a lot of your code at a later time just to accommodate a different kind of game. What I am offering in these sections will, I hope, help you to design a basic game engine code structure that lends itself well to making a second game only by modifying and replacing the customized pieces of your first game. The rest of the code would stay the same, reducing the development time.
The best way to avoid having to revamp a program is to assemble it in pieces, keep each piece self-contained, and where possible data-drive the operations. The cleanest way I've found so far to do so is using classes, which are supported by C++. Classes allow you to contain sets of functions within a structure definition, making them easier to find and allowing the programming to be more intuitive. The functions used for rendering will reside in one class, networking calls in another, etc.
DISCLAIMER: Please understand that these initial discussions will NOT generate an entire game engine for you. What I'm discussing here only encompasses the very necessities of what any game engine would need. An optimized 3D scene parse and submittal routine is beyond the scope of what I've developed so far, and the routines that involve AI, physics, game item behaviors, etc, will not be discussed here, since that development is specific for each game. However, once the initial pieces are in place, you should know where to add your own routines to accommodate the development that remains. You can either add classes and make them members of the existing components, or code them as global objects to be accessed by the custom routines of the primary components. You may or may not reuse your routines, depending on the other games you develop.
To understand these discussions, you will need to be familiar with the C++ developing language and understand the following class concepts:
Base and Derived Classes
I learned the most from Teach Yourself Object-Oriented Programming with Turbo C++ in 21 Days. This is the only C++ book I own. It doesn't provide extremely complicated examples of how to use classes or demonstrate any complex models and solutions (which would have been nice), but the necessary concepts are there. It can be hard to figure out exactly how you would use the concepts for practical purposes, but over time I've definitely found uses for them here. Perhaps it will save you the work of coming up with your own designs if you decide to use the one presented in these discussions.
By the way, I use Microsoft Visual C++ 6.0 Professional Edition for development.
Well, let's get started!
Game Engine Code Structure
As I mentioned previously, the best way to keep program code reusable is to build it in pieces. Using classes, I've done just that. Taking advantage of the derived class and virtual function methodologies, this design allows for customizing the base classes to meet any particular solution. To help support this, the class templates are a great utility for creating a storage structure with some powerful organization functions (such as linked lists, arrays, or timed loops), and then declaring that template with any given data type we need.
At first I thought I would take a top-down approach to structuring the program and contain all the components of the engine within a single class. This ended up being a bad idea because there were too many occasions where one component needed to access one of the other components, and it was far too troublesome for the parent class to route requests. So, instead, I took the easy way out and split all the components into global objects. This is generally a no-no in academic style coding, but there are just too many benefits to ignore, which I will explain below.
Wherever there is a need for a function within a component class that might change from game to game, always use a virtual function in your base class definition (I TOLD you that you needed to know about virtual functions!). The base class will NEVER change. Derive a class specific to each game you develop using this base class, and in the derived class rewrite the virtual function as your custom function. These virtual functions are the starting points for any way that your specific game expands off from and adds to the base engine. Any component could have any number of virtual functions, and ALL components of the game engine could potentially be derived from a base class for a specific game. The very best solution is to provide blank functions as the virtuals in your base class, to make it clear to the programmer exactly which functions need to be developed in the derived class to make your game work. Documentation and examples in the base code would help, too!
Before I introduce you to any code, it might be a good idea to plan out exactly what goes into a game engine. If we were to split it into pieces, the list might look like this:
Each of these pieces will be coded as class objects. Only one of each will be declared. The declarations of these objects will be set up such that any given object can access any other, meaning they will be global objects.
Why global objects? Usually if one class object is to be given access to another, we might want to include that class as a member of the one that needs access to it. But what if the member needs access to the parent class? In order to accomplish that, references would have to be set up in each direction in both class's declaration (.h) files. This causes a cyclic dependency in the compilation, creating a messy problem to clean up as you add new objects and need new references. Making them all global objects, taking all references to them out of the declaration (.h) files, and only accessing them in the implementation (.cpp) files keeps the cyclic dependencies from occurring.
Why do we need the class-to-class access to be so flexible? Consider all these possibilities in a single game: One button on the screen, when clicked, will reset the game state. That means the Forms object needs access to the Game object. When one game item meets a certain goal, a message box needs to pop up. That means the Game object needs access to the Forms object. When a player joins the game in multiplayer, a game item needs to be created to give that player control. That means the Network object needs access to the Game object. A button on a chat screen may be used to kick a player out (Forms needs access to Network). See how complicated it can get? There are many more possibilities. Just play it safe and make them all global. Then your bases will be covered.
Each object will be referred to by a pointer in the code. This way, the virtual function capability is taken advantage of. Use these pointers as global variables only! And never prepare functions that use the components as parameters. This keeps the dependencies out of the .h files.
First I'll go into what each piece does, then I'll provide a link to some sample code that illustrates declaring the global objects and referencing them in the implementation files. It won't be a functional game engine, just enough to illustrate the benefits of making the objects global.
This class contains the primary window creation routines and sets up the WndProc function present in any Windows program. Its idle loop is the primary loop for the entire application, and will make calls to the System object to poll the controls, and forward them to the Game object to run a single game 'tick'. It will also detect the application being minimized and restored, and make the appropriate function calls to support those operations. Its derived version might contain the actual window name and routines specific to that game's minimize and restore routines.
This class will be declared within the program's WinMain along with the rest of the components, and can be kicked off by a single call to a function within the Window object. This function returns ONLY when the game program is terminated. This makes the WinMain for the application extremely small, limiting its operations to allocating the components and calling the function in Window.
This class contains information about the system itself. Keyboard, mouse, joystick and system clock data will be stored here. Its functions will handle polling the controls and storing that information in a structure that is readily accessed by the other game components (Game, Forms, etc.).
Any special API's necessary to access the input controls are hidden within this class. Only the public function calls will be made, protecting the programmer from needing to be familiar with the API itself. See the Graphics section for more insight.
A deeper discussion of the System object is here.
This class is the primary caretaker for the game itself. Its base class contains data common among all games to be developed. It also contains the loop routine which makes all function calls that the game needs to properly function. This loop routine is called by Window, performs its tasks (rendering the forms (Forms), receiving and sending network messages (Network), maintaining the sound queue (Sound), performing game item updates (custom class), etc.), and then returns, representing a single game 'tick'. Classes derived off this class will contain data members specific to a particular game being developed. Other classes may exist to support this class's operations, particulary a game state class which might contain all the items active in the current game session. Such a class could also be declared globally, just like the primary components, so all the other components can access it in their custom functions.
Even if you are going to make a game with only one screen, it may be best to code it as a form, with controls and scenes to render. When this form is drawn, all its controls are rendered, including the scenes. This class naturally makes use of the Graphics object to perform all of its rendering. It also refers to the Resources class for the images and fonts it makes use of. Functions that perform mouse and keyboard interpretation (clicking buttons, typing in text fields, etc.) and proper order of control rendering are also handled here. The complete version should support multiple forms and have the ability to make them appear and disappear with simple function calls. This can be one of the most complicated classes to code, because you are basically programming your own Windows-style form system from scratch. It's a lot of work, but it offers a lot of flexibility once it's done.
For a more in-depth look, go here.
This is an important one. When my engine is complete, this class will contain routines that generally describe the operations to be performed (DrawModel, DrawRectangle, DrawText, etc.). Deep inside this class are other objects that contain the actual API calls to the various rendering routines (DirectX and OpenGL, to name two). This class will support switching the resolution and rendering API at any time. The strength in coding this way is that if a new API comes out, all that is necessary is to code a new set of corresponding routines to use the new graphics commands, and none of the rest of the engine will need to change.
Why do I embed the API calls deep within this object? So that the Forms class can make simple function calls to tell the Graphics class what to render, without needing to be concerned about what API or screen resolution is active. The Graphics class will take care of all of that, protecting the Forms class, and the programmer (which is most important), from needing to be that familiar with the API calls. Of course, to even set up the class, you will need to be that familiar, but once it's in place, you can just make the simple calls.
A deeper discussion of the Graphics object is here.
This class contains certain types of data that will be used during rendering. Bitmaps, fonts, 3D models, animations, etc. will be loaded into this class. Typically, I prefer to code such that the resource class simply knows where to get the data when it is actually needed, then loads it only at that time. It will also be necessary for very large projects to release data to make room for more to keep memory from running out.
This class serves to load sounds into memory, monitor the sounds being played, and automatically interrupt sounds to make room for other sounds that are being cued up. It is similar to Resources in that it keeps track of where to get the sounds, but it has specialized routines to handle all sounds being played, performing proper cleanup where necessary.
There will naturally be a sound-based API embedded within this class, similar to System and Graphics. Its public functions will make their own calls to this API, shielding the programmer from being familiar with the API's function set.
For a deeper discussion of the Sound object, go here.
For those of you interested in network programming, this one keeps track of all players in the current session, performs connections, disconnects, etc. The base version contains routines to handle rudimentary messages that deal with synching session times and player statuses. Its derived versions will have custom routines for handling what should happen when connections and disconnections occur, as well as a routine that handles the messages specific to your game.
Just like System, Graphics and Sound, the actual network API is hidden within this class, shielding the programmer from being familiar with it.
For a deeper discussion of the Network object, go here.
Those are the basic pieces. If it looks like a lot of work, well, you're right, it is. What's more, you will need other classes contained within these primary objects to support storing all your bitmap information in a clean fashion, submit messages to parse queues, etc. Thankfully, if you do it right, you will only have to code these things ONCE, and just include customized routines in your derived classes when you finally make your game.
This Visual C++ 6.0 sample application illustrates the basic code structure I've described. All it does is count from 1 to 5 in DOS, but hopefully you get the idea. Soon, I will post an example that illustrates creating custom versions of the classes instead of using their base versions.