PacSnake
PacSnake 是经典吃豆人游戏和经典贪吃蛇游戏的结合体。
引言
首先,PacSnake 现已在 Google Play 商店 上作为一款 Android 游戏推出。它采用了与本文所述相同的技术,但增加了许多新功能,例如平滑的蛇身移动、多关卡等。我将很快撰写一篇新文章,介绍蛇的移动以及我在开发过程中遇到的更多 Android 特定问题。点击此处。
PacSnake 是一款受吃豆人和贪吃蛇游戏启发的游戏。游戏的目标是让蛇在迷宫中移动并吃掉所有的食物。当蛇撞到自己的身体时,游戏结束。
编写这款游戏非常有意思,因为它运用了许多不同的技术(稍后会详细解释)。
- XML:游戏使用 XML 读取不同的关卡。它还使用 XML 文件来存储皮肤和保存/读取用户偏好设置。
- 图形:显而易见。它使用后备缓冲区将图形绘制到屏幕上,因此屏幕在每次重绘时都不会闪烁。
- 面向对象:在开发游戏时,我尝试采用面向对象的方法。
- 声音:游戏使用声音。
游戏使用关卡集和皮肤
- 关卡集:一个关卡集包含一个或多个关卡。您可以创建自己的关卡,将它们放入关卡集文件中,然后就可以玩新关卡了。
- 皮肤:您还可以创建自己的皮肤并将其应用到游戏中。皮肤是图形的集合,决定了游戏的外观。例如,您可以制作一个“火焰”皮肤或一个“金属”皮肤。
类
我将在下面详细解释游戏使用的每一个类(对象)
Snake
Snake
对象负责移动蛇、检查方向和碰撞以及获取蛇的图像。
Snake
由一个矩形数组组成。每个矩形代表蛇身体的一部分(头部、身体或尾部)。每个矩形都有一个位置和一个方向。这样,蛇的每个身体部分都可以朝不同的方向移动(当蛇转弯时)。使用矩形,可以轻松检测蛇是否撞到自身而导致游戏结束。这是检查碰撞的代码片段
/// <summary>
/// Check if the head of the snake crashed into a body part
/// </summary>
/// <returns>True if snake bumps into itself, otherwise false</returns>
public bool CheckCollision()
{
for (int i = 1; i < length; i++)
if (body[i].IntersectsWith(body[0]))
{
isDead = true;
return true;
}
return false;
}
代码获取代表蛇头部的矩形(位于数组的第一个位置),并检查它是否与任何其他身体部分重叠。如果重叠,则游戏结束!
GameData
这是一个相当简单的类,它跟踪当前关卡,记住用户的偏好设置(例如是否播放声音),以及将设置更新/读取到磁盘上的 XML 文件。最后一点值得在此展示
/// <summary>
/// Reads the game variables from the savegame. If there's no savegame
/// we use default values.
/// </summary>
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";
// Create new file savegame.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();
// Load XML document and create all elements
doc.Load(filename);
XmlNode root = doc.DocumentElement;
XmlElement skin = doc.CreateElement("Skin");
skin.InnerText = path + "\\skins\\ice.xml";
// Add all elements to the root element
root.AppendChild(skin);
// Save file
doc.Save(filename);
}
}
首先,我们检查是否存在存档。当游戏首次启动时,存档不存在。在这种情况下,我们创建一个 XML 文件并存储用户的偏好设置。在这种情况下,我们只有一个设置,但随着游戏的不断扩展,未来可能会有更多设置。它使用了 XML 命名空间,当您查看代码时,会发现它相当直观。
信号强度
Level
类的一个重要部分是绘制关卡。这是负责此项的代码片段
/// <summary>
/// This method draws the level. It takes each item in the level and
/// draws the correspoding image.
/// </summary>
public Bitmap Draw(int levelWidth, int levelHeight, Skin skin)
{
img = new Bitmap(levelWidth, levelHeight);
g = Graphics.FromImage(img);
g.DrawImage(skin.Background, 0, 0);
// Draw the level
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
Image image = GetLevelImage(itemsInLevel[i, j], skin);
// No free space
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;
}
首先,我们绘制一个背景,它存储在 Skin
类中。关卡存储在一个二维数组中。该数组由 Item
对象组成。一个 item
对象告诉我们它包含关卡的哪个项目(例如不同的墙壁或食物)。该数组逐行、逐列读取,并为每个 item
绘制相应的图像。代码中看到的迷宫偏移量用于将关卡绘制在屏幕中央,以防关卡尺寸小于屏幕尺寸。
LevelSetInfo
LevelSetInfo
类保存关于某个关卡集的所有信息。关卡集由存储在 XML 文件中的不同关卡组成。这样,您就可以通过将自己的关卡放入 XML 文件中,然后简单地将该文件复制到 levels 目录来创建自己的关卡。
LevelSet
LevelSet
继承自 LevelSetInfo
,并且在这个类中,我们还添加了关卡。虽然我们使用 LevelSetInfo
类在屏幕上显示关卡集信息(我们不需要知道关卡的外观),但当我们真正想玩游戏时,我们会使用 LevelSet
来读取关卡。
LevelSetInfoCollection
这个类简单地保存了一个 LevelSetInfo
对象集合。
Skin
游戏使用皮肤来处理图形。这意味着您可以创建自己的图形。Skin
类从 skin 目录加载这些图形。这些图形文件的位置存储在 XML 文件中。该类从 XML 文件读取位置,并从磁盘检索图形。
SkinCollection
保存 skins 目录中找到的皮肤集合。
WinMM
这是一个用于播放声音的辅助类。这是我在网上找到的一个类,有关播放声音的更多详细信息,请查看该类源代码中提到的网站。
板
Board
是游戏的主窗体。它绘制关卡、蛇等。它还使用计时器来移动蛇,并响应按键。这在以下方法中有所体现
/// <summary>
/// Handles the key presses. When the game is running it controls the
/// snake. When the game is not running it controls the other keys to
/// start the game, etc...
/// </summary>
private void AKeyDown(object sender, KeyEventArgs e)
{
switch (gameData.GameStatus)
{
// If the game is in progress, we control the snake
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:
// Don't respond on other key presses
break;
}
break;
// If the snake has died we press a random key to restart
case GameStatus.SnakeDied:
InitializeGame();
break;
// If the level is finished we press a key to start next
case GameStatus.LevelFinished:
// If this wasn't the last level, start next level
if (gameData.CurrentLevel < levelSet.NrOfLevelsInSet)
{
gameData.CurrentLevel++;
level = levelSet[gameData.CurrentLevel - 1];
InitializeGame();
}
else
gameData.GameStatus = GameStatus.WaitingForKey;
break;
// Level is ready to start, let's wait for a key
case GameStatus.WaitingForKey:
gameData.GameStatus = GameStatus.InProgress;
gameTicker.Interval = level.Speed;
gameTicker.Enabled = true;
mnuSkin.Enabled = false;
break;
}
}
FormLevelSet
在开始游戏之前,我们必须选择要玩的关卡集。这是通过此窗体完成的。
FormSkins
一个简单的窗体,我们可以在其中选择要使用的皮肤。
尽情享用!
历史
- 2005年6月17日:初始版本