Bunnyaruga:GAPI、Hekkus、部署基础






3.63/5 (13投票s)
本文展示了一个使用GAPI和Hekkus库的游戏示例。它还展示了一种无需在最终用户计算机上安装.NET Framework 的漂亮且免费的游戏/应用程序部署方式。
引言
Bunnyaruga(发音为bunny-ah-roo-gah)是一款简单的游戏,本质上是《Escape》和《Ikaruga》的融合。本示例/文章将尝试提供使用.NET Compact Framework制作游戏所需的示例。希望您能将其用作从使用带.NET包装器的GAPI库、添加声音到创建游戏安装程序包的所有内容的示例。
背景
我怀疑理解本文需要很多背景知识。参考“要点”部分列出的参考资料应该能帮助解释您可能遇到的任何问题/疑问。一个重要的注意事项是,此示例将无法在模拟器上运行。显然,模拟器没有gx.dll,因此所有使用它的函数都会失败。我找到了一些据称可以模拟模拟器上gx.dll调用的文件,但对我来说从未奏效。所以很遗憾,您必须使用真实的Pocket PC进行开发/测试。
编译
编译此游戏没什么特别的,但正如我之前所说,它无法在模拟器上运行。如果您想在不自己编译源代码的情况下尝试,请下载演示项目,它只是一个安装到Pocket PC上的安装程序文件。它将部署所有必需的DLL。如果您自己编译源代码,则需要将HekkusSS.dll和GAPINet.dll复制到Pocket PC上复制游戏的同一目录(默认情况下是/Bunnyhug/Bunnyaruga)。如果不复制这些文件,在尝试运行游戏时会显示错误消息。
如果以Debug模式编译,则不会创建CAB文件和安装程序。如果想创建CAB文件和安装程序,需要以Release模式编译。Release模式仅在您将源代码提取到C:\projects\bunnyaruga目录(有关更多信息,请参阅“部署”部分)时才生成。如果您第一次提取到正确文件夹并在Release模式下编译,则Customer Installer项目上的预生成事件会失败。再次生成一次应该就可以正常工作了。
游戏玩法
虽然这款游戏并不难玩,但我还是想介绍一下规则和控制。有两种游戏模式:chomp(吞噬)和survival(生存)。在这两种模式下,您都可以点击并按住鲨鱼,然后移动手写笔来移动鲨鱼。在生存模式下,您的目标是躲避鱼。每个关卡都会增加更多的鱼,使生存更加困难。在吞噬模式下,您的目标是吃掉所有的鱼。蓝色的鲨鱼可以吃蓝色的鱼,白色的鲨鱼可以吃白色的鱼。要切换鲨鱼的颜色,请按任意按钮。如果您想随时退出,请按方向键上的任意方向。
使用代码
编辑注释 - 为了避免水平滚动,已对源代码行进行了换行
MainForm
我本来想在这款游戏中加入一个启动画面,但它加载速度很快,而且坦率地说,已经有很多教程了(请参阅参考部分中的任何MSDN文章)。总之,我首先要做的是让窗体全屏显示,没有标题栏或菜单。为此,`OnLoad`方法应该包含以下内容:
private void MainForm_Load(object sender, System.EventArgs e)
{
graphics = this.CreateGraphics();
currentState = State.Title;
game = null;
// set the form to full screen
this.ControlBox = false;
this.Menu = null;
this.WindowState = FormWindowState.Maximized;
this.FormBorderStyle = FormBorderStyle.None;
}
我们还希望在这个类中重写`OnPaint`、`OnPaintBackGround`、`OnMouseDown`、`OnMouseUp`、`OnMouseMove`事件,以便我们自己处理它们。例如,在`OnPaintBackGround`中,如果游戏处于标题状态,我们希望绘制带有菜单的标题屏幕;如果游戏类型是游戏类型选择,我们希望绘制游戏类型选择屏幕;播放状态,我们将让我们的`Game`类处理绘制。最后,由于选项和分数都是普通窗体,我们只需要调用基类的`OnPaintBackground`。
/// <summary>
/// Stubbed out so we can handle drawing
/// </summary>
///
protected override void OnPaintBackground(PaintEventArgs e)
{
switch (this.currentState)
{
case State.Title:
// draw the black background
base.OnPaintBackground(e);
// draw the title screen
System.Drawing.Bitmap titleScreen = new System.Drawing.Bitmap(
System.Reflection.Assembly.GetExecutingAssembly().
GetManifestResourceStream("Bunnyhug.Bunnyaruga.
Images.title.gif"));
this.graphics.DrawImage(titleScreen, 0, 0);
titleScreen.Dispose();
// draw the menu bitmap transparent over the title screen
Bitmap menu = new Bitmap(
System.Reflection.Assembly.GetExecutingAssembly().
GetManifestResourceStream("Bunnyhug.Bunnyaruga.
Images.menu.gif"));
System.Drawing.Imaging.ImageAttributes imageAttributes =
new System.Drawing.Imaging.ImageAttributes();
imageAttributes.SetColorKey(Color.Black, Color.Black);
this.graphics.DrawImage(menu, new Rectangle(0,
this.ClientRectangle.Height - menu.Height, menu.Width,
menu.Height), 0, 0, menu.Width, menu.Height,
GraphicsUnit.Pixel, imageAttributes);
menu.Dispose();
break;
case State.GameType:
// draw the black background
base.OnPaintBackground(e);
// draw the title screen
System.Drawing.Bitmap gameTypeScreen = new System.Drawing.Bitmap(
System.Reflection.Assembly.GetExecutingAssembly().
GetManifestResourceStream(
"Bunnyhug.Bunnyaruga.Images.gameType.gif"));
this.graphics.DrawImage(gameTypeScreen, 0, 0);
gameTypeScreen.Dispose();
break;
case State.Play:
// let GXGraphics handle the drawing
break;
case State.Options:
// draw the black background
base.OnPaintBackground(e);
break;
case State.Scores:
// draw the black background
base.OnPaintBackground(e);
break;
}
}
我想到的另一个有用之处是使位图透明(在游戏过程中绘制精灵时经常使用),这样我们就可以有一个基本的背景图像,并在背景图像上显示不同的菜单位图。我只为标题屏幕这样做了,因为我让选项和分数屏幕显示了一个全新的窗体。总之,要使位图透明,必须使用`ImageAttributes`类。
// draw the menu bitmap transparent over the title screen
Bitmap menu = new Bitmap(
System.Reflection.Assembly.GetExecutingAssembly().
GetManifestResourceStream("Bunnyhug.Bunnyaruga.Images.menu.gif"));
System.Drawing.Imaging.ImageAttributes imageAttributes =
new System.Drawing.Imaging.ImageAttributes();
imageAttributes.SetColorKey(Color.Black, Color.Black);
this.graphics.DrawImage(menu, new Rectangle(0,
this.ClientRectangle.Height - menu.Height, menu.Width, menu.Height),
0, 0, menu.Width, menu.Height, GraphicsUnit.Pixel, imageAttributes);
我处理菜单点击的方式非常糟糕,因为我只是硬编码了每个按钮的位置。想出更好的方法会更好,但我不想在这上面花费时间。
OptionsForm 和 ScoresForm
这两个没什么可说的。`OptionsForm`最初用于测试目的,所以更改各种变量的所有代码仍然在这个类中,但我隐藏了选项卡页面,除非您取消注释它们,否则不会显示额外的选项。要显示它们,请取消注释`InitializeComponent`方法中此部分的两行。
//
// tabControl
//
this.tabControl.Controls.Add(this.generalTabPage);
// this.tabControl.Controls.Add(this.fishTabPage);
// this.tabControl.Controls.Add(this.sharkTabPage);
this.tabControl.SelectedIndex = 0;
this.tabControl.Size = new System.Drawing.Size(240, 256);
我对这个窗体的问题是,我想让用户输入他们的名字,但是由于没有显示菜单栏,所以我无法显示输入面板。因此,我监听`GotFocus`事件并显示`InputPanel`。这效果很好,但缺乏一种方便地隐藏`InputPanel`的功能,因此您必须选择屏幕上的其他位置才能最小化`InputPanel`。`ScoresForm`没什么特别的。我真的很想跟踪每种游戏类型的不仅仅是最高分,但由于某种原因,来自OpenNetCF库的`XmlSerializer`对我不起作用,所以我没有花额外的时间来测试它。
游戏
当我开始开发这款游戏时,我不太确定要使用多大的“鱼”或“鲨鱼”,所以我有很多可以在游戏选项屏幕上更改的选项(现在已被注释掉了)。然而,处理所有这些选项的代码仍然在`Game`类中。游戏支持的一些选项包括:鱼的生成位置、鲨鱼的生成位置、声音和音乐的音量、鱼的速度是在最小和最大之间随机还是仅使用最小速度恒定、鲨鱼的韧性、屏幕边缘是否会导致即时死亡、每种颜色的鱼的数量以及它们在每个级别上的速度和数量增加多少。如果您查看`GameOptions`构造函数,可以尝试游戏的默认设置,但我认为最好将其更具体化,否则如果我达到25级,鱼的数量每级增加2,而您将其增加15条鱼,那么分数将永远无法接近。
鼠标事件
让鲨鱼相对于手写笔的移动而移动非常容易。在`MouseDown`事件中,我检查鲨鱼是否被点击。如果被点击,我将布尔值`mouseDownOnShark`设置为true。在`MouseMove`事件中,如果`mouseDownOnShark`为true,则移动鲨鱼,否则忽略`MouseMove`事件。在`MouseUp`时,只需将`mouseDownOnShark`设置为false。关于移动鲨鱼就这些了。
/// <summary>
/// Mouse move event while the game is in play
/// Check to see if the user pressed the mouse down on the shark
/// </summary>
///
public void MouseMove(MouseEventArgs e)
{
Point p = new Point(e.X, e.Y);
if (mouseDownOnShark)
{
// move the shark relative to the last point clicked
shark.CurrentX = shark.CurrentX - (lastPoint.X - p.X);
shark.CurrentY = shark.CurrentY - (lastPoint.Y - p.Y);
shark.LocationX = (int)shark.CurrentX;
shark.LocationY = (int)shark.CurrentY;
lastPoint = p;
}
}
绘制方法
这是游戏大部分处理过程发生的地方。在初始化任何变量后,它会显示一个从3开始的倒计时。倒计时结束后,它开始计算每一帧的物体应该如何移动,并更新鱼的位置。主游戏循环执行以下操作:
- 绘制鲨鱼
- 检查鲨鱼是否接触到边缘以及是否应该死亡
- 更新鱼的位置和方向。绘制每条存在的鱼并检查它是否与鲨鱼碰撞
- 检查游戏是否应该结束,因为鲨鱼生命值耗尽、时间耗尽(生存模式)或鲨鱼吃掉了所有的鱼
- 绘制状态栏
- 处理所有按键事件
- 如果鲨鱼死亡,则结束游戏
我不会在这里重复所有代码,只需查看`Game`类的`Draw`方法以获取更多详细信息。
Hekkus
Hekkus音效系统被极其轻松地集成到了游戏中。如果您下载了Hekkus Sound System .NET Wrapper随附的示例,它将是一个很好的起点。简单来说,您需要做以下几件事。首先,声明一个`HekkusSound`的实例。如果您打算播放任何声音或音乐,就需要它。如果您想播放音乐,请声明一个`HekkusModule`变量。最后,如果您想播放音效,请为每个要播放的音效声明`HekkusSoundFX`变量。要初始化声音库,只需调用`HekkusSound`对象上的`Open`方法。
try
{
hss = new HekkusSound();
music = new HekkusModule();
soundFishEaten = new HekkusSoundFX();
soundSharkHit = new HekkusSoundFX();
hss.Open(44100, 2, 4, true);
}
catch
{
MessageBox.Show(
"Error opening sound library. Make sure that HekkusSS.dll " +
"is in the application directory");
}
加载音效也很简单。try
{
soundFishEaten.LoadFile(@"sfx/blink.wav");
soundSharkHit.LoadFile(@"sfx/chomp.wav");
if (options.SoundsVolume == 0)
{
soundFishEaten.Volume = 0;
soundSharkHit.Volume = 0;
}
}
catch (System.Exception)
{
// no sound effects to load
}
对于音乐,我希望能够轻松添加音乐而无需更改任何代码。我的`LoadMusic`方法会搜索Music文件夹中的所有mod文件,并为每个游戏随机选择一个来播放。这样,您就可以访问Mod Archive下载任何您想要的mod文件,然后将它们放入音乐目录中。try
{
// filter all the mod files from the music directory
string appPath = Path.GetDirectoryName(
Assembly.GetExecutingAssembly().GetName().CodeBase);
appPath = appPath + "\\";
string[] files = System.IO.Directory.GetFiles(
appPath + @"music", "*.mod");
if (files.Length > 0)
{
// randomly choose a file to play
Random rnd = new Random();
string file = files[rnd.Next(0, files.Length)].Replace(appPath, "");
music.LoadFile(file);
music.Loop = true;
if (options.MusicVolume == 0)
{
music.Volume = 0;
}
}
}
catch (System.Exception)
{
// failure loading music
}
播放音乐或播放音效非常简单。
// play sound effect
hss.PlaySfx(soundFishEaten);
// start music
hss.PlayMod(music);
关于Hekkus库我就说这么多。它非常易于使用,让您可以专注于游戏的其余方面,而不必担心如何播放音乐和音效。
部署
现在谈谈游戏的部署。关于这一点已经有很多文章了,但我想找一种方法,既不需要用户在Windows PC上安装.NET,又能让我从Visual Studio内部构建包。所以,我开始时参考了MSDN上这篇关于部署Compact Framework应用程序的文章。这是一个很好的起点,但问题在于,MSI文件要求用户安装.NET才能使自定义操作生效(例如,调用ActiveSync将CAB文件发送到Pocket PC)。为了替换MSI文件,我发现了一个名为EZSetup的程序,来自SPB Software,网址是EZSetup。 EZSetup制作了一个非常简单的安装程序,它显示一个readme.txt、一个EULA,然后安装设备所需的正确的CAB文件。如果您有兴趣,这里有一个EzSetup指南,提供了一个快速的使用说明。总之,由于MSDN文章涵盖了如何创建Custom Installer以及编辑用于CabWiz的INF文件,而EzSetup Guide涵盖了EZSetup的所有内容,所以我只将介绍为GAPI和Hekkus游戏/应用程序需要添加到INF文件中的内容。
大多数Pocket PC应该已经有了gx.dll文件,所以您不需要将其与您的游戏一起部署。您需要部署的是GXGraphics.dll、GXInput.dll、GAPINet.dll、HekkusSS.dll、音效和音乐。由于GAPINet.dll和HekkusSS.dll是特定于处理器类型的,所以您需要将每种支持的类型包含在您的安装程序中。我只包含ARM、MIPS和SH3。当Visual Studio为您创建.INF文件时(当您右键单击项目并选择“Build Cabs”时,它会为您创建),它会为您创建ARM、MIPS和SH3部分供您填写文件。在我的例子中,我将所有这些DLL存储在Library目录中,每个处理器类型放在不同的文件夹里。INF文件包含以下内容:
编辑注释 - 已对行进行换行以避免水平滚动
[SourceDisksNames.ARM]
2=,"ARM4",,"C:\projects\Bunnyaruga\Bunnyaruga\obj\Release\"
3=,"ARM_Setup",,"C:\Program Files\Microsoft Visual Studio
.NET 2003\CompactFrameworkSDK\v1.0.5000\Windows CE\wce300\ARM\"
10=,"Hekkus",,"C:\projects\Bunnyaruga\Library\Hekkus\ARMRel\"
13=,"GAPINet",,"C:\projects\Bunnyaruga\Library\GAPINet\ARM\"
[SourceDisksNames.SH3]
4=,"SH36",,"C:\projects\Bunnyaruga\Bunnyaruga\obj\Release\"
5=,"SH3_Setup",,"C:\Program Files\Microsoft Visual Studio
.NET 2003\CompactFrameworkSDK\v1.0.5000\Windows CE\wce300\SH3\"
11=,"Hekkus",,"C:\projects\Bunnyaruga\Library\Hekkus\SH3Rel\"
14=,"GAPINet",,"C:\projects\Bunnyaruga\Library\GAPINet\SH3\"
[SourceDisksNames.MIPS]
6=,"MIPS8",,"C:\projects\Bunnyaruga\Bunnyaruga\obj\Release\"
7=,"MIPS_Setup",,"C:\Program Files\Microsoft Visual Studio
.NET 2003\CompactFrameworkSDK\v1.0.5000\Windows CE\wce300\MIPS\"
12=,"Hekkus",,"C:\projects\Bunnyaruga\Library\Hekkus\MIPSRel\"
15=,"GAPINet",,"C:\projects\Bunnyaruga\Library\GAPINet\MIPS\"
[SourceDisksFiles.ARM]
vsd_config.txt.ARM=2
vsd_setup.dll=3
GAPINet.dll=13
HekkusSS.dll=10
[SourceDisksFiles.SH3]
vsd_config.txt.SH3=4
vsd_setup.dll=5
GAPINet.dll=14
HekkusSS.dll=11
[SourceDisksFiles.MIPS]
vsd_config.txt.MIPS=6
vsd_setup.dll=7
GAPINet.dll=15
HekkusSS.dll=12
GXGraphics.dll和GXInput.dll都是.NET CF库,所以它们不需要是ARM、SH3或MIP3特定的,只需将它们包含在.INF文件中的通用文件区域即可。
[SourceDisksNames]
1=,"Common",,"C:\projects\Bunnyaruga\Bunnyaruga\bin\Release\"
8=,"Music",,"C:\projects\Bunnyaruga\Music\"
9=,"SFX",,"C:\projects\Bunnyaruga\SFX\"
16=,"SFX",,"C:\projects\Bunnyaruga\Configuration\"
[SourceDisksFiles]
Bunnyaruga.exe=1
GXGraphics.dll=1
GXInput.dll=1
HekkusNetPpc.dll=1
blink.wav=9
chomp.wav=9
heavybutcool.mod=8
bluemon.mod=8
settings.ini=16
我做的另一件事是设置`CustomInstaller`项目(`CustomInstaller`项目与MSDN上文章描述的相同)的预生成事件,以便它为我构建CAB文件,并且在成功构建CAB文件后,后生成事件会通过调用Deployment\build-setup.bat文件来生成新的安装程序包。所以,我几乎只是使用`CustomInstaller`项目来在Release模式下构建安装程序。
最后,我所有的部署脚本都硬编码为在C:\projects下工作,并且Visual Studio 2003安装在默认目录中。因此,如果您想构建安装程序包,则需要将源代码提取到C:\projects\bunnyaruga目录,或者更改INF文件以指向您提取源代码的目录。如果您不这样做,项目将无法成功构建CAB和安装程序包。
关注点
这个想法从何而来?
我从这个令人上瘾的小游戏中获得了这个游戏的灵感,你只需点击红色方块并躲避其他移动的方块。我觉得在Pocket PC上玩这个会很有趣。嗯,在我为PPC制作了一个快速版本并给一些同事看之后,他们开始给我提建议/想法,其中一个就是“嘿,这个东西需要带激光的鲨鱼!”由于我必须给移动的矩形起个名字,所以我决定称它为“鲨鱼”。不久之后,鲨鱼躲避的矩形就被称为“鱼”。可惜,鲨鱼并没有激光。
我添加的下一个功能是能够更改所有鱼和鲨鱼的大小进行尝试。结果我更喜欢较小的鱼(5x5像素而不是20x20像素)来躲避。在我给同事们看之后,他们提出了下一个建议:“为什么矩形看起来不像鱼?为什么它被称为鲨鱼,如果它只是一个矩形?”所以,凭借我糟糕的图形技能,我试图制作一个带有动画鱼的位图。鲨鱼也差不多糟糕。算了。
最后,我记得玩过Game Cube上的《Ikaruga》(也在Dreamcast上),这款游戏类似于《1942》,但有一个转折。有两个颜色的子弹,黑色和白色,你必须避开。另一个转折是你可以在黑白之间切换你的飞船颜色。当你的飞船是黑色时,它可以吸收黑色子弹,但白色会杀死你;当你作为白色飞船时,你可以吸收白色子弹,但黑色会杀死你。所以我添加了2种颜色的鱼,并让鲨鱼可以在这些颜色之间切换。正是基于此,“Bunnyaruga”这个名字应运而生。
最后的问题是,我最初只想做一个简单的游戏来躲避静态大小的矩形,而现在我有一个游戏,里面有小型多色鱼和有史以来最丑的“鲨鱼”。
参考文献
- MSDN:跳动的矩形:使用GAPI创建托管图形库
- MSDN:跳动的僵尸:向托管图形库添加位图
- MSDN:跳动的粒子:向托管图形库添加点、线、字体和输入
- Hekkus音效系统
- Hekkus音效系统 .NET Wrapper
- EzSetup
- EzSetup指南
- MSDN:开发和部署Pocket PC设置应用程序
- MSDN:创建INF文件
Bug
我相信这款游戏中还有很多我不知道的bug,但到目前为止,这些是我遇到的。
- 每隔一段时间,当您是一条蓝鲨并吃掉一条蓝鱼时,您会损失一个生命点。虽然两鱼速度完全相同的可能性应该非常罕见,但确实会发生。一个权宜之计是确保没有两条鱼在同一个位置生成。
要添加的增强功能/特性
以下是一些我想在某个时候添加的增强功能:
- 更好的碰撞检测。目前碰撞区域仍然是一个矩形。我只需要遵循MSDN:使用Microsoft .NET Compact Framework编写移动游戏文章中的建议。
- 更好的分数记录和设置文件的序列化。我曾计划使用OpenNetCF库中的`XmlSerializer`,但不知何故,当我反序列化XML文件时,它们从未正确反序列化。所以,我没有花时间去弄清楚问题所在,而是使用了一种非常粗糙的方法来序列化/反序列化设置文件,对于分数,我只保留每种游戏类型的最高分。
- 更好的/可重用的菜单按钮。与其硬编码标题和游戏类型屏幕的所有按钮区域,不如以一种可以在其他游戏/应用程序中重用的方式来实现。
注释
在编写完这个示例游戏后,MSDN发布了两篇很棒的关于在Pocket PC上制作游戏的文章。一篇是名为“Bust This”的打砖块游戏,另一篇是名为“Ultimate GMan”的横版卷轴游戏。我推荐您看看这两篇文章。
历史
- 2004年4月19日 - 初始版本