Do you like computer games? Yes? Me too. Have you ever thought about writing one? Me too!
I think playing games is a lot of fun, and it's even more fun to write one. But as someone wisely said, a game is a complicated thing. In fact, it can be quite challenging to slap together a working game. In this article, I'd like to present a Pong game I've written for Windows Mobile. It's written with VC++, using Visual Studio 2005. It can be played with up to five balls, depending on the skill level. Ball speeds increase with time, and collisions will make the balls go crazy. There are power-ups to help the situation, but still it's hard to beat the computer.
With this article, I'd like to share my ideas on how to divide a complex task into smaller, more manageable parts.
What makes a Pong game?
Two paddles and a ball? If only it was so simple. Here is an overview of the challenges:
- The program should handle different types of resources, e.g., game graphics and music
- It should be properly timed so that animations look almost the same on a slow and a fast device, too
- It should offer a custom game menu with options and settings, along with a proper way to start or quit the game
- Last but not least, there should be a game logic that moves balls, checks for collisions, responds to user input, gives scores etc.
Let's have a look at these challenges.
A game, usually, has many resources: graphics, sounds, text, to name but a few. As you probably know, Windows systems support a way to include various types of resources in executables; that's how icon, cursor, dialog, menu etc. data is stored in most cases. I think game resources should be stored in files, and not in the EXE itself. They could be stuffed into the EXE, but you have more freedom if you're not tied by the built-in resource API.
Resources should be organized some way. I mean, it's not a good idea to just put all kinds of resources into the same folder; instead, there should be some grouping. Graphics and sounds are different sorts, so in case there's a lot of them, at least they should be in two different folders, e.g., Gfx and Sfx.
An interesting question is, how to store the game graphics. In this Pong game, there are paddles, balls, background, power-ups etc. Should they be stored in BMP or JPG files? Does Windows support working with bitmaps? It does, but it's not as flexible or useful as it should be. Should I write parsing routines for bitmap file headers? I had a look at different bitmap header formats, and proclaimed I'd rather walk 500 miles. Or is it a good idea to include a general-purpose image library in the game to handle these formats? A good library, such as CXImage, offers a lot of features so that you could write a complete drawing application around it. But the game does not need 99% of its features; displaying an image would be almost enough.
I decided not to use any of the above; instead, I created a simple image format. Not as complex or fancy as a BMP or JPG file, but enough for the purpose. Every image has a header block:
unsigned char Type;
unsigned char Format;
unsigned short Width;
unsigned short Height;
Every image in the game has a unique ID; these IDs can be found in game.h (
enum InGameImages). The
Name can be useful when debugging, the
Type fields will be interesting later. This header block is immediately followed by the image's 16-bit pixel data: (
Width * Height)
WORDs, stored in the RGB-565 format, as discussed in this article. I've chosen black as the transparent color, so any pixel that has a value of zero will be skipped by the drawing routines.
To construct image (IMG) files from BMP files, I coded a small Windows app called bmp2img. This application takes a filename as a command-line argument, reads the bitmap, converts the pixels to RGB-565 format, pre-pends the
ImageHeader structure, and then writes the resulting data to disk. To handle the input BMP format in this app, I used the CXImage library.
Okay, I've converted every game image to IMG files, but there's plenty of them. So, I decided to pack all IMG files into one data file. To do this, I coded another helper application that runs on PC too: imgman (image manager). Despite the name, this program does not manage much, it just takes filenames as command-line arguments and bundles them into one file. The output file, images.dat, starts with a header, too. But this header is even simpler, and contains the number of images stored in the DAT file, followed by file-offset/size pairs so that it's very easy to load the images one after another. You can see how the loader loop works in
DatLoadGameData() (see dat.cpp). It would be useful to apply some compression to the final DAT file, maybe I'll include it later.
As images are loaded, they're put in a structure and added to a container. Here is how the
Image structure looks:
unsigned char Status;
I don't just want to display images, I also want to erase them! Erasing an image means displaying the pixels that were on the screen before the image was displayed. If the image type is erasable (
enum ImageTypes in img.h), memory must be allocated for background pixels. That's why the background pointer is there.
To handle tasks related to game images, I've coded the following helper routines (see img.cpp):
ImgAdd() adds an image to the internal image container, a
std::map. Accepts the image only if the image format is RGB-565. Also, if the image type is erasable, it will allocate a proper block of memory for the saved background.
ImgRemove() deletes an image from the container.
ImgGet() returns the image structure with the given ID.
ImgBlit() displays the image with the given ID, at the given x, y coordinates. It can save the background before displaying so that the image can be properly erased.
ImgErase() erases the image by displaying the saved background.
ImgBlit2() displays the image with a color override. This is how the main menu logo has a plasma effect applied to it.
ImgBlitPart() displays only a smaller, rectangular part of an image. This is used by the scroller effect.
ImgErasePart() erases an image displayed with
All these routines use the
ZGfx class to take care of drawing to the device display.
A couple more things about "images" I used. Basically, everything you can see in the game is an "image". The logo, menu items, the scroller, balls, paddles, and scores - all images. I wanted to keep things simple, so I didn't use fonts and sophisticated text output routines. For instance, the main menu scroller is the following image:
The code simply displays a part of this image, with a variable shift. The
ImgBlitPart() function takes care of this. Game scores are created dynamically from this image:
You can see how numbers are printed character after character in the function
PrintNumber() in print.cpp. This simple function is incapable of sophisticated text output, but in fact, the game needs only digits to be printed.
This should take care of the game graphics, but how about sounds?
If you want to play a single Wave file in a program, you might want to consider the Windows
PlaySound() function. I think there is even a way to playback MIDI files with Windows, but I haven't tested it. There are also specialized audio libraries, and I think it's a good idea to use them. Usually, they come with a handy API and are well documented. So, if you know the pleasure of using Windows audio functions like
waveOutPrepareHeader(), you'll definitely like sound libs. I think two of the finest audio libraries for PocketPC are Hekkus and FMOD. They're both free for personal use; but FMOD supports more file formats (though in my opinion, needs more memory, too). I picked FMOD for this project because the tune that Horace composed for me was originally in MP3 format. So, I coded the sound routines, and started testing. Then, I realized, an MP3 takes a whole lot of memory when loaded and decoded. In fact, it took too much. So, I already had my routines written for FMOD but ran out of memory. I converted the MP3 to a MIDI file. It still sounded good, but no matter how I converted it, FMOD didn't recognize it despite that it should support MIDI files too. As a last resort, I tried another format supported by FMOD, the protracker MOD format. Eh, another drop in quality, but I was desperate to make it work this or that way. And finally, the MOD worked.
I've put the sound handling routines in snd.cpp. The helper functions are:
SndInit() initializes the FMOD library and loads the tune
SndSetVolume() sets the volume
SndPlayTune() plays the tune
SndStop() stops sound output
I haven't packed the only sound file used in this game. But in case there is a set of them, it's a good idea to pack them into one, and/or apply some compression to large (e.g., WAV) files.
Timing & frames
I think there is a basic but important difference between a game and an ordinary application. Let me explain.
What does an app, let's say Microsoft Word do, if you don't type or don't move the mouse? Nothing much, except for a regular auto-save. It does not update its view unless you type something or click somewhere. Applications usually respond to events (such as user input). Then, they perform a specific task (calculations, drawing etc.) and wait for another event. But, a game has to update the display all the time, even if you don't do anything. That's a big difference. How should a game update the display? Imagine a movie on a good old celluloid tape. There are still images on the tape, they're frames. As frames are displayed quickly one after another, you see motion picture. That's the point. The game will update the display by rendering still images n times a second (where n is the frame rate). This game renders a frame with the
GameDrawFrame() function (game.cpp). To see motion picture, this function must be called regularly. There are different ways to achieve this:
If you want a fixed frame rate, let's say 25, then you call
DrawFrame(), then wait 40ms (1000/25), then call it again, in a loop, as long as the game runs. If an object in the game has to move 100 pixels a second, it would move 4 pixels in each frame. But on a fast machine, where you could draw even 50 frames instead of only 25, you could move the same object two pixels per frame, which would mean a somewhat smoother animation. Another problem is, on a slower machine, the frame rate might drop below the fixed value, and it would cause the game to slow down. In my opinion, the disadvantage of using fixed frame rate is that it has no real advantages :-) So don't use fixed frame rates, instead, make it variable. If it's variable, it will be higher on a fast device, and lower on a slow one. And, it may drop temporarily if there are a lot of things to draw, but it won't hit the game-play that hard. I've put the frame rate logic in the application's message loop in
WinMain (see zpong.cpp):
PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
while(msg.message!=WM_QUIT && g_running)
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
The code works as follows: if there is a message in the application's message queue, it's fetched and processed. Otherwise, if the game has still got the input focus, it checks the system time, and if enough passed since the last update, calls
double variables. The
TimerGetDouble() function returns the value of the high-resolution system timer (performance counter), converted to seconds (see timer.cpp). Notice that
GameDrawFrame() has a parameter:
void GameDrawFrame(double frametime);
This way, the drawing code knows how much time has passed since the last frame, and it can move objects slower or faster - depending on the current frame rate. It's so simple to move an object 100 pixels a second from left to right:
object.x += 100 * frametime;
frametime values will sum up to 1.0 per second, and the object will move 100 pixels. That's it!
Timing is important, and not only in Kung-Fu fighting, if you remember Carl Douglas' one hit wonder. For a "perfect timing", functions like
GetTickCount() should not be used because they are not accurate enough. Instead, use the performance counter functions,
Game states and menus
The concept of states is another thing you need not necessarily have in your "ordinary", event-driven applications. Now, we know the game should have a
DrawFrame() function that will be called n times a second to handle one time-step. Have a look at the following piece of code:
if(some_condition == true)
if(some_condition == true)
DoWork() function will be called regularly (just like
DrawFrame()!). It begins with a
switch statement. Depending on the current state, it performs different, state-specific tasks. Then, it checks if a so-called transition condition is true, and if it is, updates the state. Obviously, if a transition happens, and the state is changed, then the next time the function gets called, it will execute code for another state. That's the idea. Because the game has only one function to do all the drawing work (that is,
GameDrawFrame()), and because the game has different, well-separable tasks, it's a good idea to use a "state machine" and get familiar with "thinking in states". (If you're interested in computation theory, I recommend Googling for deterministic automata and state machines.)
I've separated program tasks to the following states (
enum GameStates in game.h):
gsMenu draws the main menu
gsGame handles the game-play
gsInGameMenu handles the menu that pops up when the screen is tapped during game-play
gsLifeLost draws animation when a life is lost
gsGameOver is the game-over sequence
When the game starts, it's initially in the
gsMenu state. This state draws and animates the main menu. The menu offers skill selection, makes it possible to turn music on/off etc. In this state, it also bounces a number of balls on the screen. Have a look at the
case gsMenu handler in
GameDrawFrame(). First, it erases everything off the screen, then updates every object's position (logo, menu items, balls, scroller), then re-draws everything. It is important to know that erasing is done in reverse order, so anything displayed first will be erased last. The sine-wave effect of menu items uses an array of pre-calculated sine values. This way, it's not necessary to call the slow
sin() function all the time. See
GameUpdateMenuItemPositions(): the sine array index is simply updated using the
frametime value. The plasma effect on the ZPONG logo uses code from this article.
The only transition possible in this state is to enter the
gsGame state, via the "Start Game" menu item. If the player selects "Quit", the global
g_running flag is cleared, and as a result, the message loop exits.
gsGame state is the most complex of them all, as it implements the whole game logic. Let's have a detailed look at it later. Transitions possible in this state:
- If the user taps the screen, the state changes to
gsInGameMenu and the in-game menu pops up
- If the player misses all balls, a life is lost. To handle this situation, the state changes to
gsInGameMenu state draws a simple menu that offers two choices: continue the game, or exit to the main menu. Thus, transitions possible are, entering
gsMenu or going back to
gsLifeLost state, an animated message pops up to inform the player that something went wrong. Then, if all lives are lost, enters the
gsGameOver state. Otherwise, goes back to
This diagram gives an overview of the states and transitions:
Now, it's time to look at the game logic in detail.
case gsGame handler in
GameDrawFrame() begins with an
if statement. It checks a boolean variable
gameplay_inited to see if the game-play has been initialized. If it's
false, it displays the game background image, clears the player score, sets the number of lives to 3 ... that is, performs init functions, then sets
true. I prefer this
bool-controlled approach for one-time init tasks. If game initialization is already done, the handler samples input (keyboard and screen taps) to see if the in-game menu should be popped up. If this is the case, it changes state to
gsInGameMenu. Otherwise, it calls
GameUpdateGame() to do the actual drawing work. This function uses the same approach as the main menu code: erase everything but the background, update objects, then redraw everything. The first step is accomplished by the following calls:
It's easy to erase balls, paddles, and power-ups because the
ImgErase() function does the job for static images. But as I explained before, the "lives" and "score" are created dynamically, from a static image containing numbers. Thus, there is no way to erase score, simply because there is no such thing as a score image. Eh, sounds complicated. To solve this and make dynamically-built graphics erasable, I added special images called placeholders (
igiPlayerScorePlaceHolder etc.). These images are blank (all pixels black), and have the same dimensions as lives, score etc. graphics. When
ImgBlit() is called on them, nothing will be displayed, but the background will be saved, and so anything drawn there will become erasable. Look at the
ImgBlit(igiLivesPlaceHolder, GAME_W-33, GAME_H-50);
sprintf(str, "%d", lives);
PrintNumber(str, GAME_W-33, GAME_H-50, RGB_TO_565(255,255,255));
First, it displays the placeholder (that is blank), then prints the number of lives in white. It's clear that in order to erase the number of lives off the screen, the code simply erases the placeholder:
ImgErase(igiLivesPlaceHolder, GAME_W-33, GAME_H-50);
Simple, isn't it? Now comes the routines to update game objects. The paddles are nothing complicated, they have a horizontal position property (
comp_x variables). The game samples keyboard input with the
GetAsyncKeyState() API function (see input.cpp), and updates the player paddle in the
GameUpdatePlayerPaddle() function accordingly. The computer paddle has a simple mechanism: if there are balls moving up, the code picks the closest one and moves the paddle towards it. Otherwise, it moves the paddle back to the center. The
GameUpdateCompPaddle() function takes care of this.
Balls and power-ups have their special properties that make it necessary to wrap something around them. There are two structures to handle this:
powerup (see ball.h and powerup.h).
Balls have x, y positions and x, y speeds, and an image ID associated with them. They also have a status value and a movement style. Status can be
bsLeftDown. These status values are set by the ball position update functions:
UpdateCircle(). If these functions set the status to other than
bsStillInGame, it means someone has scored, because the other player missed the ball. Collisions, which modify ball speed vectors, are not handled by the
ball object itself, because balls should not have access to the properties of other balls. Balls have their own
Erase() functions, too.
Power-ups have fewer properties, only x, y positions and y speed, plus an image ID. Supporting functions are
Update(). Power-up effects are handled by the game itself, as this object too should not interfere with other game structures.
Ball collisions are handled by the
GameCheckBallCollisions() function. It uses the good old
OVERLAPS macro in
for loops to check ball positions against other balls. To avoid double-checking, it sets the
collision_tested flag for balls, upon processing. If balls collide, there is a chance that their movement style will change to a sine-wave or circle.
The game spawns balls and power-ups depending on the skill level and the number of balls hit back by the player paddle. I've added three power-ups: the red one turns all balls back towards the computer's paddle, the green one temporarily increases the player speed, and the yellow one helps with a force field effect by repelling balls as they get close to the player. Here are they:
The yellow and green ones have a time-out value, while the red one has a one-time effect. It's very simple to handle time-outs with the
frametime value. All we need is, subtract the current
frametime value from the power-up TTL (time-to-live), and if the TTL reaches zero, it's timed out. Power-up effects are handled by the
GameDoPowerUpStuff() function. I have good news. There are only a couple of things left!
The code updates ball positions by calling
GameUpdateBall() in a
for loop. This loop takes care of a couple more things as well: it checks ball status values and removes those that left the screen, gives score if a ball is missed, and makes balls bounce back if they hit the paddle.
After all processing has been done, the code checks if the game state should be changed. That is, if all balls are gone, and the last one was missed by the player, the state changes to
The last thing to do is update the display by re-drawing everything:
Notice that the drawing routines are invoked in reverse order. The first to be erased is the last to be drawn. Otherwise, objects close to each other would be displayed incorrectly.
A game is something complicated indeed! So, it's definitely worth planning every little thing before you start the actual coding. You can also save a lot of time and effort if you have reusable, task-specific code, like image or sound handling stuff. The demo project contains everything the game needs to run: the EXE file, the FMOD DLL file, and the data file. To make the source pack smaller, I've not included the FMOD link library (fmodce.lib) and the CXImage LIB files. Follow the instructions in the readme file if you want to compile the project.