Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A C# .NET PacMan game

0.00/5 (No votes)
13 Jul 2004 2  
An article on Pocket PC game development.

Introduction

In order to play, you need a Pocket PC enabled device with the .NET Compact Framework 1.1 installed. To install, simply copy the mypacman.exe file to a new folder on your device. There is apparently no need for any companion files because the three Wav sound files and two GIF image files are incorporated into the executable as "embedded resources".

To start playing the game, press Enter, and then use the direction keys to guide the Pacman. The object of the game is not to be eaten by the red ghosts. There are four purple "yummies" that when eaten, convert the vile red ghosts into gentle blue ones which can then be eaten. This "free time" is limited and its extension is portrayed by a color bar that appears below the scores and starts getting smaller. You have three lives. Once you eat all the dots, you enter a new level in which the "scared" ghosts time gets reduced. Starting the second level, the red ghosts start "tracking" the Pacman (as opposed to randomly switching direction), and you got to be extra careful!

Once you lose all your lives, the game enters a demo mode, flashing a screen and ghosts moving around. To start playing again, simply press Enter.

The current game has only one maze design, so as an improvement, you people can design new mazes for different levels and also design new types of rewards and "villains".

Using the code

This project contains 7 classes in seven source files: GameForm.cs, GhostsManager.cs, PacmanManager.cs, Sound.cs, Sprite.cs, StopWatch.cs, and GameFont.cs. The first three are the important classes; I will describe their functionality briefly. The code is fairly well commented so you can gather conclusions from there too.

The main class of the project is GameForm. It holds all the game flow logic and contains as members, among others, an instance of a GhostsManager class and a PacmanManager class. It is derived from System.Windows.Forms.Form class and is responsible for the main game loop and all the graphics. The key component of this game is an array of 15x15 bytes which is responsible for defining the maze, dots, and yummys:

private byte[] LEVEL1DATA = 
{
  19,26,18,26,26,26,22,7,19,26,26,26,18,26,22,
  37,7,21,3,10,14,21,13,21,11,10,6,21,7,37,
  .....
  .....
  25,26,26,26,26,26,24,26,24,26,26,26,26,26,28
};

The playing ground dimensions are defined by the following constants: BLOCKSIZE = 16, NROFBLOCKS = 15, and SCRSIZE = NROFBLOCKS x BLOCKSIZE.

The game playing area is composed by NROFBLOCKS x NROFBLOCKS blocks, each BLOCKSIZE pixels wide. This means that the playing ground is a square of SCRSIZE pixels wide.

The bit values in every byte of the array decide if the associated block has or has-not (starting with the LSB) a left maze wall, upper maze roof, right maze wall, lower maze floor, dot, and yummy. There are two bits left which are not used and can be used to add more features to the maze in a future enhancement.

Double buffering is used to avoid flickering. At the start of the game, the whole maze is loaded onto a Graphics object conveniently called mazeGraphics. On each iteration of the game loop, this Graphics object is updated according to if a dot or yummy has been consumed, and then the whole Bitmap object, mazeBitmap, represented by mazeGraphics is blitted onto the main offscreen Graphics object, offScreenGraphics. Note that the only change to mazeGraphics is a removal of a dot or yummy, and the whole maze does not need to be constructed in its entirety on every iteration:

public void DotConsumed(int arraypos, Sprite pacman)
//callback function invoked by pacManager 
//notifying that dot has been consumed
{
    ...
    if ((ch&16)!=0) // you just ate a dot! 
    {
        ...
        mazeGraphics.FillRectangle(brush, 
          pacman.x+BLOCKSIZE/2,pacman.y+BLOCKSIZE/2,3,3);
        // clear bits 16 and 32 (no dot or yummy)
        screendata[arraypos]=(byte)(ch&15);
        score++;
    }
    if ((ch&32)!=0) // just ate a yummy 
        {
        ...
        mazeGraphics.FillRectangle(brush, 
          pacman.x+BLOCKSIZE/3, 
          pacman.y+BLOCKSIZE/3,BLOCKSIZE/3,BLOCKSIZE/3);
        screendata[arraypos]=(byte)(ch&15);
        score+=5;
    }
}

and

private void DrawMaze()
{
    offScreenGraphics.DrawImage(mazeBitmap, 0, 0, 
            this.ClientRectangle, GraphicsUnit.Pixel);
}

The other two important classes are GhostsManager and PacmanManager.

PacmanManager, as its name indicates, manages the movement and display of the Pacman. The Pacman is encapsulated in a Sprite object. All the views of the Pacman are included in PacMans.gif. The GameForm delegates the KeyEventHandler (after grabbing focus with a call to this.focus()) calls to the keydown() method of PacmanManager which updates the requested movement directions of the Pacman. The important concept here is that a Pacman is guaranteed to fill each block it visits because it moves in each iteration of the game loop in increments of PACMANSPEED=2 which is a factor of BLOCKSIZE=16. When the Pacman moves fully into a block, it will change direction if the last requested turn in direction is valid, i.e., not crashing into a maze wall:

public void MovePacMan(GameForm form)
{
    byte ch;   
    if (reqdx==-pacman.dx && reqdy==-pacman.dy)
    //this is when it reverses direction
    {
        pacman.dx=reqdx;
        pacman.dy=reqdy;
        viewdx=pacman.dx;
        viewdy=pacman.dy;
    }
    else if (pacman.x%GameForm.BLOCKSIZE==0 && 
      pacman.y%GameForm.BLOCKSIZE==0) //its in the middle of a block 
    {
        arraypos=pacman.x/GameForm.BLOCKSIZE  +  
          GameForm.NROFBLOCKS*(pacman.y/GameForm.BLOCKSIZE);
        ch = form.screendata[arraypos];
        ....
        ....
        OnDotConsumed(arraypos, pacman);
        //notify parent that yummy or dot has been 
        //consumed so as to update maze
    }
    DrawPacMan();
}

GhostsManager is the class in charge of handling all movements and display of the ghosts. The ghosts are contained in an array of Sprite objects, and in the first level of play, they turn in direction randomly. The GhostsManager is also in charge of detecting a collision with the Pacman and setting the appropriate variables. If the game is in "scared" mode and a ghost is hit, it will get "killed" and disappear in the place of impact and will be reborn in the center of the playing field. If a ghost is hit in playing mode (when they are red), a life will be subtracted from the player. All possible speeds of the ghosts are also factors of BLOCKSIZE.

private int[] VALIDSPEEDS = { 1,2,4,8 };
// all these are factors of BLOCKSIZE

Points of Interest

A point of interest is the event handler method DotConsumed(int arraypos, Sprite pacman) in GameForm.cs. This is subscribed to the PacManager.OnDotConsumed delegate instance. Every time a Pacman has consumed a dot or yummy, the parent class GameForm will be notified through this mechanism so it can update mazeGraphics and remove it from the display.

//In GameForm() constructor:
pacManager.OnDotConsumed += new PacmanManager.DotConsumedHandler(this.DotConsumed);

//In PacManager.cs:
public delegate void DotConsumedHandler(int arraypos, Sprite pacman);
public event DotConsumedHandler OnDotConsumed;

Another point of interest is the tracking algorithm (a simple form of Artificial Intelligence) the ghosts use after the first level of play is done. When possible, it will only consider directions of movement in which the ghost reduces the x-y distances between itself and the Pacman:

if(form.pacManager.pacman.y<ghosts[i].y || goRandom)
{
    dx[count]=0;
    dy[count]=-1;
    count++;
}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here