Introduction
First of all, PacSnake is now available as an Android game in the Google Play Store. It uses the same techniques as described in this article, but has many more features, like smooth snake movement, many levels, etc. I will soon write a new article describing the snake movement and more Android specific issues that I've come across. Click here.
PacSnake is a game that is inspired by PacMan and the Snake game. The object of the game is to move the snake through the maze and eat all the food. When the snake hits his own body, it dies.
The game was very interesting to write because it uses a lot of different techniques (which are explained in more detail later).
- XML: The game uses XML to read the different levels. It also uses XML file for skins and for storing/reading user preferences.
- Graphics, obviously. It uses a backbuffer to write graphics to screen, so the screen doesn't flicker each time it's repainted.
- Object oriented: I tried to use an object oriented approach while developing the game.
- Sounds: The game uses sounds.
The game uses level sets and skins:
- Level sets: A level set consists of one or more levels. You can create you own levels, put them in a level set file and that you can play the new levels.
- Skins: You can also create your own skins and put them in the game. A skin is a collection of graphics that decide how the game looks. You can for example make a 'fire' skin or a 'metal' skin.
The Classes
I'll explain each class (object) that the game uses here in detail:
Snake
The Snake
object is responsible for moving the snake, checking for directions and collisions and for getting the snake images.
The Snake
consists of an array of rectangles. Each rectangle represents a body part of the snake (head, body or tail). Each rectangle has a position and a direction. This way, it's possible for each body part to move in a different direction (when the snake turns in corners). Using rectangles, it's easy to detect is the snake bumps into itself and dies. Here's the piece of code that checks for collisions:
public bool CheckCollision()
{
for (int i = 1; i < length; i++)
if (body[i].IntersectsWith(body[0]))
{
isDead = true;
return true;
}
return false;
}
The code takes the rectangle that represents the snake's head (which is the first in the array) and checks if it overlaps with any of the other body parts. If so, game over!
GameData
This is a fairly simple class which keeps track of the current level, remembers the user's preferences like playing sounds or not, and updating/reading the setting to an XML file on disk. The last point is worth showing here:
public void ReadSavegame()
{
string filename = path + "\\savegame.xml";
XmlDocument doc = new XmlDocument();
if (File.Exists(filename))
{
doc.Load(filename);
skinFilename = doc.SelectSingleNode("//Skin").InnerText;
}
else
{
skinFilename = path + "\\skins\\ice.xml";
XmlTextWriter writer = new XmlTextWriter(filename, null);
writer.Formatting = Formatting.Indented;
writer.Indentation = 2;
writer.WriteProcessingInstruction("xml",
"version='1.0' encoding='ISO-8859-1'");
writer.WriteStartElement("savegame");
writer.Close();
doc.Load(filename);
XmlNode root = doc.DocumentElement;
XmlElement skin = doc.CreateElement("Skin");
skin.InnerText = path + "\\skins\\ice.xml";
root.AppendChild(skin);
doc.Save(filename);
}
}
First, we check if a savegame exists. A savegame doesn't exist when the game is started for the first time. In this case, we create an XML file and store the users preferences. In this case, we only have one setting, but there may come more settings in the future as the game gets bigger. It uses the XML namespace, and when you examine the code, you can see it's pretty straightforward.
Level
An important part of the Level
class is drawing the level. Here's the piece of code responsible for that:
public Bitmap Draw(int levelWidth, int levelHeight, Skin skin)
{
img = new Bitmap(levelWidth, levelHeight);
g = Graphics.FromImage(img);
g.DrawImage(skin.Background, 0, 0);
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
Image image = GetLevelImage(itemsInLevel[i, j], skin);
if (image != null)
g.DrawImage(image, mazeOffsetX + i * 20,
mazeOffsetY + j * 20, 20, 20);
Item item = itemsInLevel[i, j];
if (item.IsCandy && !item.IsCandyEaten)
g.DrawImage(skin.Food, item.Rect.X, item.Rect.Y);
}
}
return img;
}
First, we draw a background, which is stored in the Skin
class. The level is stored in a two-dimensional array. This array consists of Item
objects. An item
object tells us which item of the level it holds (like a different wall or a piece of food). The array is read row by row, column by column, and for every item
a corresponding image is drawn. The maze offsets you see in the code are for drawing the level in the center of the screen in case the level is smaller than the screen size.
LevelSetInfo
The LevelSetInfo
class holds all the information about a certain level set. A level set consists of different levels that are stored in an XML file. This way, you can create your own levels by putting them in an XML file and simply copying this file in the levels directory.
LevelSet
LevelSet
inherits from LevelSetInfo
and in this class, we also add the levels. While we use the LevelSetInfo
class for displaying the level set information on screen (we don't need to know how the levels look), we use the LevelSet
for reading the levels when we actually want to play the game.
LevelSetInfoCollection
This class simply holds a collection of LevelSetInfo
objects.
Skin
The game uses skins for graphics. This means that you can create your own graphics if you wish. The Skin
class loads these graphics from the skin directory. The location of these graphic files are stored in an XML file. The class reads the location from the XML files and retrieves the graphics from disc.
SkinCollection
Holds a collection of skins that are found in the skins directory.
WinMM
This is a helper class for playing sounds. It is a class I found on the web and for more details about playing the sounds, check the website mentioned in the source of this class.
Board
Board
is the main form of the game. It draws the level, snake, etc.. It also uses a timer to move the snake and it responds to key presses. This is shown in the following method:
private void AKeyDown(object sender, KeyEventArgs e)
{
switch (gameData.GameStatus)
{
case GameStatus.InProgress:
switch (e.KeyData.ToString())
{
case "Left":
snake.DesiredDirection = MoveDirection.Left;
break;
case "Right":
snake.DesiredDirection = MoveDirection.Right;
break;
case "Up":
snake.DesiredDirection = MoveDirection.Up;
break;
case "Down":
snake.DesiredDirection = MoveDirection.Down;
break;
default:
break;
}
break;
case GameStatus.SnakeDied:
InitializeGame();
break;
case GameStatus.LevelFinished:
if (gameData.CurrentLevel < levelSet.NrOfLevelsInSet)
{
gameData.CurrentLevel++;
level = levelSet[gameData.CurrentLevel - 1];
InitializeGame();
}
else
gameData.GameStatus = GameStatus.WaitingForKey;
break;
case GameStatus.WaitingForKey:
gameData.GameStatus = GameStatus.InProgress;
gameTicker.Interval = level.Speed;
gameTicker.Enabled = true;
mnuSkin.Enabled = false;
break;
}
}
FormLevelSet
Before we start the game, we have to choose which level set we want to play. This is done by this form.
FormSkins
A simple form where we can choose which skin we want to use in the game.
Enjoy!
History
- 17th June, 2005: Initial version