This is the second complete game that I have created using the DirectX library
(actually is the 3rd, but I lost the partial project of the 2nd one in some
of mine HD reformatting sessions). The game was created using a library
created by me (called cMain.lib), that works as a wrapper around the DirectX
library. The library source is included with the game source code so that you
can use it to create your own games. I'll start explaining how this library
actually works and them I'll explain how the game works.
Before you compile the project
Before you compile the project, make sure you have the DirectX SDK 8.0
installed (the DirectX SDK, not the run-time). If you have already installed
the SDK and are still having trouble compiling the project, check if the DX
include and library directory is the top of the list in VC++. If this
work, just post a message or send me an email that I'll help you as soon as
The cMain Library
If you open the project workspace file (.DSW) you'll notice that the workspace
is composed of two projects. One project is the library project that works as a
wrapper for the DirectX library and have some other functions that are needed
in almost every game project. This library is composed of 14 classes, each one
with its own function in the game. I will explain each one of the classes so
that you understand their role in the game.
The cApplication Class
cApplication class is basically a wrapper to a simple windows
program. Since we're working with DirectX here, this application class is also
responsible for creating the basic framework needed to use DirectDraw. In the
library we can find a global function that id responsible for the creation
of the application (WinMain). There you'll find a call to a
CreateApplication() function is a virtual function that needs
to be create in the game project itself, and need to return a new instance of
an application class. This new instace will be your own application class,
derived from cApplication class.
There are three important virtual functions in the
cApplication class that are
extremely important in the game creation process, they are
AppInitialize is called when all the application startup procedures called,
so that you can start your own initialization procedures. The
ExitApp is called
when we're exiting the game, and is used to destroy and deallocate anything
that was created in the
AppInitialized function or during the game. The
DoIdle function is where the game actually happens. When we
don't have any
windows messages to process, the
cApplication base class call this virtual
function, allowing you to process your game.
The cWindow Class
If you check the
cApplication class, you'll check that it have a
cWindow class is responsible for creating the main window in the
game. This class is used only inside the library, and there is no need to
change its attributes during the game.
The cInputDevice, cKeyboard and cMouse classes
These three classes take care of the user input for our game. Since the Mouse
and Keyboard input rely on the DirectInput framework, we need a place to put
the DirectInput main objects initialization. This place is the
cInputDevice class we have a pointer to a DirectInput interface and a
reference count. The reference count is used to check how many classes are
currently using the DirectInput main interface. Notice that the reference count
and the interface pointer are static variables. This is done because the
cKeyboard classes are derived from the
cInputDevice class, and use the
same DirectInput main object.
cKeyboard class take care of the keyboard entry. It have a static
variable that represents a buffer containing the state of each keyboard key.
This buffer is a static variable, allowing us to create a
cKeyboard instance anywhere in our code and use the same keyboard buffer (we read the
keyboard state once, and use it along the game iteration).
cMouse class take care of the mouse input. It work very similar to the
cKeyboard class. Each time we call the Process() function, it change its
internal variables to reflect the current mouse position in the screen and the
state of each one of its buttons.
The cSurface and cSprite classes
The surface class is a wrapper around the the DirectX Surface object. It is a
structure that hold the game graphics in the video (or local) memory so that we
can blit this graphics in the screen at each game iteration. If you want more
information about this class and the process of surface blitting, you can read
my other article here in Code Project.
cSprite class is a wrapper to work with sprites. Basically it have an
instance of the
cSurface class and some information about the sprite. The main
difference here is that you can move through the sprite steps automatically,
without worrying about the position and size you need to get from the source
The cSoundInterface, cSound and cWavFile classes
These three classes are responsible for the sound handling in our game. The
cSoundInterface creates the main DirectSound objects that will be used to
create the sound buffers of the game. Its recommended that you initialize
an instance of this class in the
AppInitialize virtual function of the
cApplication class, so that you initialize the Sound Interface before you start
cSound class holds the sound buffers of the game. It have all the
compatibilities of a normal DirectSound buffer, like Frequency and Pan control,
3D sound, and looping. To load the sounds from the resource or from the file,
it uses an instance of the
cWavFile class. This class is basically a loader for
wave files. All its code was taken from Microsoft DirectX Samples (I modified
then a bit, so that they work with WAV files in the resource).
The cMultiplayer and cMessageHandler classes
This classes are wrappers around the DirectPlay interfaces, and are
used to create multiplayer games. The
cMultiplayer class handles all the
DirectPlay functions like device enumeration, session enumeration, and player
connection. It also hold all the information about each one of the players in a
gaming session. It is important to notice that each player have an exclusive ID
that is store in a list in the
cMultiPlayer class. This IDs can be used to
control some behaviors of the game in a multiplayer session.
To handle the multiplayer network messages,
cMultiplayer class have a pointer to
cMessageHandler class. The
cMessageHandler class is a simple class with a
virtual function. This virtual function is called each time the computer
receive a DirectPlay message from a peer. Its recommended that you derive your
application class from this
cMessageHandler class too, so that you can pass the
application class itself as a message handler to the multiplayer game class (as
done in RaceX).
The cMatrix Class
This is a simple class used to create dynamic matrices. There's no big deal
about it, it have a
Create method that allocates the necessary memory and a
Destroy() function to deallocate it. It also have a
GetValue and SetValue
function used to retrieve and set the values of the matrix.
The cHitChecker Class
This class takes care of the hit checking in the game. It creates a GDI region
and use the GDI functions the test if something has hit the region or not. This
class is the same used in
my other article about hit checking. If you want more information about
it, just check the other article.
The Game Classes and Elements
Now that we have a brief description of each one of the classes in the game
library, let's take a look at the game project. The game project has a
dependency of the game lib project, if you load the .DSW file. Each one of the
classes in the game sample describes a unit of the game (the Game itself, the
Track, the Car, the Competition), so I'll explain each one so that you
understand how the game works.
This class is derived from
cApplication and from
cMessageHandler. Since we
derive it from
cApplication, we have to create an implementation to the
AppInitialize, we do the initialization of some objects that are not
initialize in the base class. Notice that in the
cRaceXApp class we have some
member variables to handle the SoundInterface, the Multiplayer and the Mouse and
Keyboard Input. All this member variables are initialized in this virtual
function implementation, calling the Initialize method of each member variable.
Notice that in the initialization of the
cMultiplayer instance we are calling
SetHandler() function, passing
this as a
parameter. We can pass the "this" as a parameter because our application class
is derive from
cMessageHandler. Using this implementation allow us to
handle the DirectPlay network messages from our application class itself. You
can notice that we have an implementation for the
This function is called when we receive a message from a DirectPlay peer (as
described in the
cMultiplayer class description)
The other implementation we have in this function is the
DoIdle is where the entire game logic is build. The first thing we do in
this function is check the
m_iState member variable, to know at
which game state we are working. When you start the game, the game state is
assigned with the
GS_MAINSCREEN value, that represents the menu screen. You can
GS_MAINSCREEN case in the
DoIdle function to see how the menu
structure is built.
Another important variable in the
DoIdle implementation is the iStart
variable. This variable is used to know when we're changing from one game state
to another. When we change the game state, we set this variable to 0 so that in
the next iteration, when the base class call the
DoIdle again, the new game
state can load all its surface and do the extra initialization needed. After
the game state initialize its objects, it sets the
iStart to a value other
than 0 and reset it to 0 when it changes the state of the game again.
When the user select one of the game types in the menu screen (Single or Multi
Player, Single Track or Competition Mode), the game enters in the track
selection screen (or in the start competition screen). When the user enter in
the track selection screen the program initialize some other class
cRaceXApp class, the
cCompetition instance and the
explain these two classes so that you understand how this screen works.
The cCompetiton Class
Even if the name states the this class is responsible for handling all the
competition stuff in the game, this class is used even in Single Track mode.
This class stores some basic information about each one of the players in the
game. When we start a game, we need to call the
AddPlayer method of this class
do add players to our game. Adding player to this class makes them apper in the
race when we change the state of the game to
GS_RACE. If you check the
state, you'll notice that it uses the player list information in the
cCompetition class instance to create each one of the cars to the race. This
class also stores the points and position of each player in the case
playing in competition mode, and its also responsible for telling the program
the track sequence of the competition (by using the
The cRaceTrack Class
cRaceTrack class takes care of the track creation and handling in the game.
Most of the game logic is inside this class. The first thing we need to do when
working with this class is load a Track from a Track file. The class have a
ReadFromFile method that is used to Load the tracks from a .rxt file (Race X
When we load the track from the file, it fills the internal member variables of
the cRaceTrack class with information about the track. The track is structured
as a bidimensional matrix and each one of the matrix cells represent a road
type. When we draw the Track in the screen, we use this road type to draw a
40x40 pixels tile in the place corresponding to the matrix position. In the
track file, an array of
DWORDs describe each one of those tiles in the track.
Since a DWORD can store 4 bytes we use only the
LOWORD to store the road type.
HIWORD of this
DWORD array is used to store other
CheckPoints (in the
LOBYTE) and the Angle Information (in the
The CheckPoint stored in the TrackFile is used to control the sequence that run
throught the Track. So if the car passed throught checkpoint 1, he needs to
pass through checkpoint 2 to fulfill the track, as so on until he reach the
last checkpoint. Using this checkpoint structure prevents the user to run
backward from the start line and pass through the start line several times,
increasing its Laps Completed counter. Since he needs to pass through
check points, its mandatory the he runs the entire race path.
The race lap counter will only increment when we reach the checkpoint 1
again and the last checkpoint we have passed is the last check point we have
avaible in this track.
The Angle information is used to allow the computer to drive the car. It will
point the direction that the computer-driven car should head so that he can
finish the race. We'll now check the
cRaceCar class so that we understand
what is the role of this angle information.
The cRaceCar Class
cRaceCar class takes car of all the car behavior handling in the game. The
most important function of this car class is the
Process function that
process the car behavior in each game iteration.
When we call the
Process function the car class check how this instance of the
car is controlled. The car can be controlled by the computer, by the user or by
If the car is controlled by the user, the car class check the keyboard input to
see if the user is trying to accelerate, break or turn the car. Depending on
the information retrieved from the keyboard, the class call the
If the car is controlled by the computer, the car class checks the current
position of the car in the track and get the angle information associated with
this position. If the angle is different from the current angle of the car, the
computer turns the car to clockwise or anticlockwise. The computer always
accelerate the car when its driving, except when it checks that
it'll hit in a
wall he keep running at the same speed.
If the car is controlled by the "network" we need to check if we're the hoster
of the game or not. If we're hosting the game we need to process the car
information based on the keyboard input from the remote computer. If
the hoster of the game, we need to sned our keyboard infomation to the
multiplayer game hoster.
Putting it all togheter - Processing the Track
When we start a new race in the game, and the game state changes to
create instaces of the
cRaceCar object based on the information found in the
cCompetiton class. Then we call the
AddCar() method of the
cRaceTrack class to add each one of the cars to the race in the track. After
adding all the cars to the race track we can process the race track class, in
order to play the game.
At each iteration of the game we call the
Process() method of the
cRaceTrack class. In this method, we'll loop in the car array,
the cRaceTrack, class to call the Process() method of each car and move them
around the track. We'll also use the
cHitChecker class to check if each one of
the cars hit the wall or hit another car. If the car hit a wall, we change its
CARSTATE_CRASHEDWALL. The cars will keep running around the track
until the user car finish the track (have a Lap count equal to the number of
the laps in the track).
I tried to explain the basic functionality of the game in the article, but there
are lots of comments inside the game sample that will help you to
understand the game much better. If you have any questions about the game
implementation just post a message or send me an email.
I want to send a special thanks to all my beta testers, in special to Colin J
Davies, Isaac Sasson, James T Johnson, Nishant Sivakumar, Nnamdi Onyeyiri and
Smitha (Tweety), for their effort finding bugs in the game, and for all
Mauricio Ritter lives in Brazil, in the city of Porto Alegre. He is working with software development for about 8 years, and most of his work was done at a bank, within a home and office banking system.
Mauricio also holds MCSD, MCSE, MCDBA, MCAD and MCT Microsoft certifications and work as a trainer/consultant in some MS CTEC in his city.
Mauricio also works in his own programming site, aimed to Brazilian Developers: http://www.dotnetmaniacs.com.br
In his spare time he studys korean language...