Click here to Skip to main content
15,867,488 members
Articles / Programming Languages / C

A Pong game for Windows Mobile

Rate me:
Please Sign up or sign in to vote.
3.93/5 (6 votes)
6 Dec 2006CPOL18 min read 86.8K   1K   44   30
A Pong clone, written using VC++/Visual Studio 2005.

Sample Image - zpong.jpg

Introduction

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.

Game resources

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:

struct ImageHeader
{
    DWORD ID; //image identifier

    char Name[32]; //internal name, useful while debugging

    unsigned char Type; //image type

    unsigned char Format; //pixel 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 Format and 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:

struct Image
{
    ImageHeader Header;
    void *Data;
    void *Background;
    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 ImgBlitPart().

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:

Image 2

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:

Image 3

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):

lastupdate=0; //init variable

g_running=true;
//running flag, will be cleared when the user requests exit


// Main message loop:

PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
while(msg.message!=WM_QUIT && g_running)
{
   if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
   {
       TranslateMessage(&msg);
       DispatchMessage(&msg);
   }
   else
   {
       //if focus got

       if(g_focus>0)
       {
           passed=TimerGetDouble();
           passed-=lastupdate;    //time passed since last update

           if(passed>1.0/25) //limit framerate to max 25 FPS

           {
               lastupdate=TimerGetDouble(); //set to "now"

               GameDrawFrame(passed);
           }
       }
   }
}

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 GameDrawFrame(). passed and lastupdate are 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;

Subsequent 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 Sleep() or GetTickCount() should not be used because they are not accurate enough. Instead, use the performance counter functions, QueryPerformanceFrequency() and QueryPerformanceCounter().

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:

void DoWork() //this function handles one step

{
    switch(g_state)
    {
        case 0:
            //perform tasks for state 0

            ...
            //check if there is a transition

            if(some_condition == true)
                g_state=1; //change state

            break;
        case 1:
            //perform tasks for state 1

            ...
            //check if there is a transition

            if(some_condition == true)
                g_state=0; //change state

            break;
    }
}

The 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.

The 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 gsLifeLost.

The 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 gsGame.

In the 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 gsGame.

This diagram gives an overview of the states and transitions:

Image 4

Now, it's time to look at the game logic in detail.

Game logic

The 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 gameplay_inited to 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:

GameEraseLives();
GameEraseScores();
GameErasePaddles();
GameEraseBalls();
GameErasePowerUp();

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 (igiLivesPlaceHolder, 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 GameDrawLives() function:

void GameDrawLives()
{
char str[32];

    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:

void GameEraseLives()
{
    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 (player_x and 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: ball and 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 bsStillInGame, bsLeftUp, or bsLeftDown. These status values are set by the ball position update functions: Update(), UpdateSine(), and 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 Blit() and Erase() functions, too.

Power-ups have fewer properties, only x, y positions and y speed, plus an image ID. Supporting functions are Blit(), Erase(), and 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:

Image 5

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 gsLifeLost.

The last thing to do is update the display by re-drawing everything:

GameDrawPowerUp();
GameDrawBalls();
GameDrawPaddles();
GameDrawScores(frametime);
GameDrawLives();

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.

Closing thoughts

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.

Have fun!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Hungary Hungary
I'm a 29-year old software developer. I began programming on a ZX Spectrum many years ago. I've programmed a number of high level languages (Basic, Pascal, C/C++, Java, Php, Perl, C#). I also like reverse-engineering and assembler programming.
I'm interested in handheld devices programming, especially Windows Mobile devices.

Comments and Discussions

 
QuestionLink Errors? Pin
spongebooob30-Mar-10 21:18
spongebooob30-Mar-10 21:18 
AnswerRe: Link Errors? Pin
dzolee30-Mar-10 21:37
dzolee30-Mar-10 21:37 
GeneralRe: Link Errors? Pin
spongebooob31-Mar-10 4:12
spongebooob31-Mar-10 4:12 
GeneralRe: Link Errors? Pin
dzolee31-Mar-10 4:21
dzolee31-Mar-10 4:21 
GeneralRe: Link Errors? Pin
spongebooob31-Mar-10 15:12
spongebooob31-Mar-10 15:12 
GeneralRe: Link Errors? Pin
dzolee31-Mar-10 22:09
dzolee31-Mar-10 22:09 
GeneralRe: Link Errors? Pin
spongebooob31-Mar-10 22:33
spongebooob31-Mar-10 22:33 
GeneralLoading 32 bits Bitmaps Pin
Edyb2-Jun-08 2:54
Edyb2-Jun-08 2:54 
GeneralRe: Loading 32 bits Bitmaps Pin
dzolee2-Jun-08 3:33
dzolee2-Jun-08 3:33 
GeneralError on building Pin
Edyb23-Apr-08 2:15
Edyb23-Apr-08 2:15 
GeneralRe: Error on building Pin
dzolee23-Apr-08 2:21
dzolee23-Apr-08 2:21 
GeneralRe: Error on building Pin
Edyb23-Apr-08 2:36
Edyb23-Apr-08 2:36 
GeneralAlpha Blending Pin
Edyb22-Apr-08 2:23
Edyb22-Apr-08 2:23 
GeneralRe: Alpha Blending Pin
dzolee22-Apr-08 2:56
dzolee22-Apr-08 2:56 
GeneralRe: Alpha Blending Pin
Edyb22-Apr-08 3:42
Edyb22-Apr-08 3:42 
GeneralRe: Alpha Blending Pin
dzolee22-Apr-08 3:52
dzolee22-Apr-08 3:52 
QuestionMissing file? Pin
sigfreund14-Mar-08 13:52
sigfreund14-Mar-08 13:52 
AnswerRe: Missing file? Pin
dzolee14-Mar-08 21:42
dzolee14-Mar-08 21:42 
GeneralRe: Missing file? Pin
sigfreund15-Mar-08 0:58
sigfreund15-Mar-08 0:58 
GeneralRe: Missing file? Pin
dzolee15-Mar-08 8:25
dzolee15-Mar-08 8:25 
GeneralRe: Missing file? Pin
sigfreund22-Mar-08 9:14
sigfreund22-Mar-08 9:14 
GeneralScreen resolution Pin
AndrewSmirnov20-Feb-08 4:21
AndrewSmirnov20-Feb-08 4:21 
GeneralRe: Screen resolution Pin
dzolee20-Feb-08 4:35
dzolee20-Feb-08 4:35 
GeneralConnect Us! Pin
FaulstiR18-Apr-07 9:46
FaulstiR18-Apr-07 9:46 
GeneralRe: Connect Us! Pin
dzolee18-Apr-07 22:22
dzolee18-Apr-07 22:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.