Cheese 游戏编程:第 4 部分 (转自MSDN)
介绍 Smartphone 游戏编程
Rob Miles
赫尔大学计算机系
适用于:
基于 Windows Mobile™ 2003 的 Smartphone
Microsoft® .NET Compact Framework 1.0
Microsoft Visual Studio® 2003
摘要:介绍如何使用 .NET Compact Framework 和 Visual Studio 2003 在基于 Windows Mobile 的 Smartphone 上进行游戏编程。在开发的不同阶段有许多项目可以下载。
从 Microsoft 下载中心下载 Games Programming with Cheese 4.msi。
本页内容
简介 | |
关于功能良好的应用程序 | |
实现继续键 | |
保存游戏状态 | |
Smartphone 程序管理 | |
应用程序图标 | |
部署程序 | |
小结 |
简介
在本系列前面的文章中,已经开发了两个游戏。本文将展示如何准备将第一个游戏部署到实际的 Smartphone 设备上。这意味着,它必须是一个功能良好的应用程序,在没有使用时,它不会影响处理器功能和电池寿命。它还必须能够保存自己的状态,以便可以在需要时暂停或继续游戏。最后,它必须有一个用户可以选择的图标。
关于功能良好的应用程序
目前,已经创建好的程序虽然可以运行,但它们并不是功能良好的程序。程序始终在内存中,它每时每刻都在消耗处理器功能。即使屏幕没有显示,但它后面的程序仍然在后台不断地更新。此外,如果玩家切换到另一个应用程序(或者有电话打进来),游戏屏幕就会消失,并且玩家会失去对游戏的控制。如果在电话打进来时玩家正要获得一个高分,这会让人非常沮丧。
处理焦点丢失
在 Microsoft® Windows® Forms 环境中,具有焦点的窗口是响应键盘和鼠标事件的窗口。通常,用户使用鼠标选择特定窗口或使用快捷键(比如 ALT+TAB)来确定焦点。Windows 桌面可以同时显示许多应用程序窗口,并且总是用深色标题栏来指示具有焦点的窗口。
对于 Smartphone,在给定时间,屏幕上只能有一个应用程序是活动的。但是,焦点的概念仍然很重要,因为它是通知应用程序不再对用户可见的方式。
在 Windows 中,当焦点丢失时会引发 LostFocus 事件。在 Windows 桌面应用程序中,当用户选择另一个应用程序窗口时也会引发该事件。在 Smartphone 中,用户可以按 Back 或 Home 键从一个应用程序切换到另一个应用程序。如图 1 所示,管理 LostFocus 事件的方式与管理其他窗体事件的方式相同。
图 1. LostFocus 事件
在图 1 所示的属性屏幕中,已经将方法 Form1_LostFocus 附加到丢失焦点事件中。这意味着,当游戏应用程序失去对屏幕的控制(即,某个内容显示在它的位置上)时,就会调用该方法。
游戏应用程序可以将一个方法附加到该事件中,该事件可在游戏不可见时将其转入睡眠模式。在睡眠模式中,应用程序仍然在内存中,并且会保留所有的游戏值。但是,屏幕没有更新,游戏也没有进展。
对于目前已创建的游戏来说,将游戏转入睡眠模式的最好方法就是停止窗体上的计时器。正是计时器事件导致游戏更新和屏幕刷新。停止计时器将有效地停止游戏。执行此操作的适当的 sleep 方法如下所示:
private void sleep () { timer1.Enabled = false; }
将从丢失焦点事件中调用该方法,这样当游戏在屏幕上不可见时,更新就会停止:
private void Form1_LostFocus(object sender, System.EventArgs e) { sleep(); }
当然,如果应用程序只是将它保持在这种状态,那么用户可能会非常不高兴。因为游戏一旦丢失焦点,它就会永远停止。因此,程序必须包含一个补充方法,在引发 GotFocus 事件时该方法可以使游戏继续。当用户将该显示返回到屏幕时,会在窗体中引发 GotFocus 事件。
唤醒方法必须与睡眠方法刚好相反。应用程序可以将事件处理程序附加到包含 wake 调用的 GotFocus 事件中:
private void wake () { timer1.Enabled = true; } private void Form1_GotFocus(object sender, System.EventArgs e) { wake(); }
从设计观点来看,这是这样做的正确方法。不应该将处理程序代码直接放在处理程序本身中。或许以后程序可以包含一个睡眠命令来允许用户从菜单选择中暂停游戏。在这种情况下,如果程序只需调用一个方法来完成这项作业,则会更容易达到这种效果。
恰当地使用这些方法,游戏将在窗体隐藏时停止,并在窗体显示时开始。如果您在所提供的项目中运行程序,则会发现如果在应用程序运行时按电话上的 Home 键,显示将会切换到主屏幕。如果您随后使用 Back 键,则会回到您离开游戏时的确切位置。
实现继续键
该方法唯一的问题是,在继续时会直接将用户带回到游戏。提供一种允许用户以更轻松的状态返回游戏的方法可能更切合实际。否则,用户可能没有准备好重新启动游戏,并且可能在重新获得对游戏的控制之前累得要死。
在中断游戏时重命名左边的软键,可以更方便地重新启动。然后,当游戏继续时,玩家必须按左边的软键来重新启动计时器并继续游戏。然而,当游戏显示“吸引”模式时,该行为是不受欢迎的,因为用户不喜欢必须先按 Resume 再按 Start 来开始新游戏。请记住,在没有玩游戏时,游戏处于“吸引”模式,它将通过显示游戏的演示来尽力“吸引”用户来玩。可以用以下方法来实现继续游戏:
private void sleep () { if ( gameLive ) { menuItem1.Text = "Resume"; } timer1.Enabled = false; }
当您正在玩游戏时,gameLive 标志为 true。如果在游戏处于活动状态时调用睡眠方法,则会在计时器停止之前将左边软键的文本更改为 Resume,如图 2 所示。
图 2. 继续游戏。单击缩略图以查看大图像。
现在还必须更改程序,以便在为应用程序提供焦点时,计时器不会重新启动。相反,在按下 Resume 软键时,计时器必须重新启动。要实现这一行为,必须如下修改菜单项目的操作:
private void menuItem1_Click(object sender, System.EventArgs e) { if ( menuItem1.Text == "Resume" ) { timer1.Enabled = true; menuItem1.Text = "Start"; } else { startGame(); } }
该方法使用按钮上的文字作为记住状态的方式。如果已经在 Resume 上下文中按下该按钮,则它会将文本恢复为 Start 操作,并重新启动计时器。否则,按下该按钮来启动一个新游戏。
结束代码的最后项目是唤醒方法。只有当游戏处于“吸引”模式时,才必须恢复计时器 — 即:
private void wake () { if ( !gameLive ) { timer1.Enabled = true; } }
如果在所提供的示例项目中运行该程序并从游戏中切换焦点,则您将看到,当您返回游戏时左边的软键此刻被标记为 Resume。按 Resume 键会导致未完成的游戏继续。
保存游戏状态
在 Smartphone 上玩游戏的模式与其他游戏平台有很大的不同。玩家可能想要在非常短的时间内玩游戏。这意味着,在每次短暂地玩了游戏之后,游戏必须确切地保留它的所有状态,以便它可以在任何时候继续。所有这一切都必须自动发生。因此,游戏应用程序必须包含在适当的时候保存和加载游戏状态的方法。
简单的保存和恢复
保存游戏状态的最简单方法是完成该程序,并记下需要保存的所有对象属性。然后将这些属性中的每一个都保存在文件中。对于 cheese 游戏,这些属性包含诸如 cheese 在屏幕上的位置以及当前的移动方向等项目。简单地保存它们的一个方法是将每个属性都写入文件中:
private string stateFileName = "Games1.bin"; private bool saveState () { System.IO.Stream stream = null; System.IO.BinaryWriter writer = null; try { stream = System.IO.File.Open(applicationDirectory+stateFileName, System.IO.FileMode.OpenOrCreate); writer = new System.IO.BinaryWriter(stream); writer.Write(highScorePlayer); writer.Write(highScoreValue); writer.Write(gameLive); if ( gameLive ) { writer.Write(Cheese.X); writer.Write(Cheese.Y); writer.Write(Bread.X); writer.Write(Bread.Y); writer.Write(xSpeed); writer.Write(ySpeed); writer.Write(goingDown); writer.Write(goingRight); writer.Write(Ham.Visible); writer.Write(Ham.X); writer.Write(Ham.Y); writer.Write(hamTimerCount); foreach ( BackSprite s in tomatoes ) { writer.Write(s.X); writer.Write(s.Y); writer.Write((int)s.DrawState); } writer.Write(tomatoDrawHeight); writer.Write(livesLeft); writer.Write(scoreValue); writer.Write(Field.Message); } } catch { return false; } finally { try { writer.Close(); stream.Close(); } catch {} } return true; }
上述代码中的 saveState 方法将所有游戏对象的状态都存储在一个文件中。当选择退出菜单项,或者当操作系统要求游戏关闭时,会调用该方法。它将打开一个二进制流,然后将所有相关信息写入其中。只有当游戏正在实际进行时,全部游戏信息才会输出;否则,只会写入高分信息和游戏活动标志。如果该方法成功运行,它将返回 true,否则将返回 false。
加载方法与保存方法相反:
private bool loadState () { if ( !System.IO.File.Exists(applicationDirectory+stateFileName) ) { return false; } System.IO.Stream stream = null; System.IO.BinaryReader reader = null; try { stream = System.IO.File.OpenRead( applicationDirectory+stateFileName); reader = new System.IO.BinaryReader(stream); highScorePlayer = reader.ReadString(); highScoreValue = reader.ReadInt32(); gameLive = reader.ReadBoolean(); if ( gameLive ) { Cheese.X = reader.ReadInt32(); Cheese.Y= reader.ReadInt32(); Cheese.Visible = true; Bread.X= reader.ReadInt32(); Bread.Y= reader.ReadInt32(); Bread.Visible = true; xSpeed= reader.ReadInt32(); ySpeed= reader.ReadInt32(); goingDown= reader.ReadBoolean(); goingRight= reader.ReadBoolean(); Ham.Visible= reader.ReadBoolean(); Ham.X= reader.ReadInt32(); Ham.Y= reader.ReadInt32(); hamTimerCount= reader.ReadInt32(); foreach ( BackSprite s in tomatoes ) { s.X= reader.ReadInt32(); s.Y= reader.ReadInt32(); s.DrawState= (BackSprite.BackSpriteState) reader.ReadInt32(); if ( s.DrawState == BackSprite.BackSpriteState.visible) { s.DrawState = BackSprite.BackSpriteState.appearing; } } tomatoDrawHeight= reader.ReadInt32(); livesLeft= reader.ReadInt32(); scoreValue= reader.ReadInt32(); Field.Message= reader.ReadString(); } reader.Close(); stream.Close(); } catch { return false; } finally { try { if (reader != null) reader.Close(); } catch {} try { if (stream != null) stream.Close(); } catch {} } return true; }
为了确保它决不占用资源,加载方法使用与保存方法相同的技术。加载方法还确保在它试图读取文件之前该文件存在。如果找不到文件,或者读取过程失败,则该方法会返回 false。请注意,在读回项目时,必须使用与保存数据类型相对应的读取方法。否则,方法可能会完成,但是返回的数据没有意义。
只有当游戏处于活动状态时,才能提取全部保存信息。此外,通过将 tomatoes 的值设置为 BackSprite.BackSpriteState.appearing,loadState 方法会导致在下一次刷新屏幕时显示 tomatoes。在加载游戏之后再调用该方法,以便自动继续游戏:
if ( !loadState() ) { gameLive = false ; } if ( gameLive ) { // Sleep if the game is active--user can then start by resuming. sleep(); } else { // Display attract mode. // Start the clock. timer1.Enabled = true; }
如果加载失败,则将游戏置于吸引模式。如果加载成功并且游戏将要运行,则将其置于睡眠模式,以便玩家可以选择继续,而不是让它立即启动。
该加载方法有一些错误处理,但并不彻底。如果加载在读取文件的过程中失败,则某些游戏对象将处于错误位置。然而,因为游戏由于错误而无法继续,所以玩家不会受到影响。当启动新游戏时,一切都会恢复为它的原始状态。
save 方法调用应放在退出菜单项目的事件处理程序中:
private void menuItem2_Click(object sender, System.EventArgs e) { saveState(); Application.Exit(); }
示例项目实现了保存和恢复,以便游戏可以在玩的过程中自动保存。
Smartphone 程序管理
Smartphone 用户可以从程序图标的显示中选择活动程序。这些图标显示的精确变化取决于应用于显示的“外观”。图 3 显示了默认的显示。
图 3. 选择应用程序。单击缩略图以查看大图像。
当用户按下电话键盘上的选择按钮时,将激活突出显示的应用程序(图 3 中用红色强调)。在演示的示例中,这将选择 Microsoft Messenger 程序。要使游戏程序成为一个有效的应用程序,它必须有一个图标,以便可以用这种方式进行选择。
应用程序图标
应用程序图标存放在应用程序文件中。它是一个小位图。一个程序文件可以包含几种不同的图标位图大小,这取决于操作系统的要求。对于 Smartphone 应用程序,编程人员必须提供两种图标位图,一种大小为 16 x 16 像素,另一种大小为 32 x 32 像素。这些图标设计将存储在一个图标文件中,该文件是应用程序项目的一部分。在构建程序时,会将该文件中的图像添加到应用程序文件中,以供 Smartphone 操作系统使用。
为了给程序提供一个图标,必须做的第一件事就是创建图标文件。下面的文件是在 Visual Studio 中按照以下顺序创建的:
1. |
从“Project”菜单中选择“Add New Item”(或者使用键盘快捷键 CTRL+SHIFT+A)。 |
2. |
在出现的对话框中,从模板中选择“Icon File”项目,如图 4 所示。 图 4. Icon File 模板 |
3. |
可以将文件名保留为默认的 Icon1.ico。 |
4. |
单击“Open”创建资源,并将其添加到项目中,然后打开它进行编辑。 |
Visual Studio 包含一个图标编辑程序。将要编辑的图标采用 16 x 16 和 32 x 32 像素版本的格式。默认图标使用 16 色,编辑器如图 5 所示。
图 5. 默认图标
图像的蓝色(绿色)部分非常重要;它们是透明的,并且可以让背景显示通过。您应该为图标图像的边缘选择这种颜色。
更改为 256 色的图标
您可以用 16 色来创建图标图像,但是采用更广的色彩范围,它们看起来会好得多。您可能希望用 256 色的图标来替代 16 色的默认图标。这会使程序稍微变大,但这不是问题。要更改图标图像格式,您应该进行如下操作:
1. |
在编辑图标图像时,从顶部的菜单中选择“Image”命令。 |
2. |
从出现的菜单中选择“New Image Type”项目,如图 6 所示。 图 6. 添加一个新图像类型 |
3. |
从出现的对话框中,您可以选择新图像类型,如图 7 所示。 图 7. 选择图标图像格式 |
4. |
所需的两种图像类型是 256 色的 16 x 16 和 256 色的 32 x 32。您应该依次添加每一种类型。 |
5. |
现在,您必须删除图标中的 16 色图像。为此,请依次选择每个图像,然后删除它们。要选择图标图像格式,请打开 Image 菜单,并打开 Current Icon Image Types,如图 8 所示。 图 8. 选择一个图标图像类型 |
6. |
从提供的列表中选择其中一个 16 色图像类型。 |
7. |
再次打开“Image”菜单,并从列表中选择“Delete Image Type”。 |
8. |
对其他 16 色图像重复步骤 5 - 7。 |
现在,必须创建图标图像了。可以用在其他绘图应用程序之间移动图像的相同方式,将要用作图标的实际图像粘贴到图标上。只需选择要使用的图像,然后使用 Visual Studio 中的图标编辑器将图像粘贴到位图中。编辑器会尽可能地将源图形中的颜色映射为可用的 256 色,也可以使用它来缩放图像以适合图标。图 9 中所示的 cheese 图像制作了一个合适的图标。
图 9. cheese 图标
背景已经设置为透明。现在需要将 cheese 图标指定给一个游戏程序。可以从项目属性对话框(该对话框可从 Project 菜单中调出)中完成此操作,如图 10 所示。
图 10. 设置应用程序图标。单击缩略图以查看大图像。
现在,当程序生成时,它会将 cheese 图像用作可执行文件的图标。为了方便起见,示例项目为应用程序提供了一个 cheese 图标集。
部署程序
直到现在,游戏应用程序都是从 Visual Studio 中执行的。要使游戏真正可用,必须将其部署到 Smartphone 本身内。有许多方法可以使程序以这种方式对用户可用。
复制程序文件
部署程序最简单的方法是将程序文件复制到 Smartphone 存储中。如果将程序文件放置在文件存储的正确位置,用户就可以定位到该文件,并从开始菜单中运行它。可以使用 Microsoft ActiveSync 来复制文件。通过选择 ActiveSync 中的 Explore 图标,可以浏览整个文件存储。
注 在更改电话中的文件时千万要小心,因为错误可能会导致电话工作不正常,这一点非常重要。
放置 cheese 程序的最佳位置是开始菜单中的 Games 文件夹。您可以通过定位到适当的文件夹来这样做,如图 11 所示。
图 11. Smartphone Games 目录中的 Bouncer 程序。单击缩略图以查看大图像。
由 Visual Studio 创建的程序可执行文件位于主机 PC 上项目区的 Bin 目录中。包含可执行文件的目录有两个。它们分别名为 Debug 和 Release。每个目录中的实际程序二进制文件都相同,但是在构建 Debug 版本的同时,也构建了 Debug 数据库。只要将可执行文件复制到 Smartphone 中,就可以在电话中访问和使用该程序。
例如,使用 ActiveSync 将 Bouncer.exe 文件复制到电话 Start 菜单的 Games 文件夹中。然后,用户就可以选择该文件。
注在 ActiveSync 的浏览器部分的目录中没有显示程序图标。这些图标只在设备本身上可见。
当您将文件复制到电话中时,可能会花几分钟来重建菜单以便程序可见。您可以按 Home 按钮来强制它们刷新,然后选择左边的软键 (Start) 来定位到一个文件夹(例如,Accessories)。如果您现在按 Back 键,将刷新 Start 菜单。
现在,我们有两个可玩的游戏可以部署到 Smartphone 设备中,并在几分钟的时间内就可以实现它们。
小结
在 Cheese 游戏编程的 4 篇文章中,我们已经看到,使用 .NET Compact Framework 为基于 Windows Mobile 的 Smartphone 创建应用程序是多么的容易(在本例中为一个游戏)。我们已经看到如何在屏幕上绘制图形、如何开发简单的游戏,以及如何添加声音来丰富游戏体验。在本文中,我们已经完成了电话平台编程的最重要方面,即,确保游戏能够处理传入的呼叫和中断的游戏周期,使电话用户玩起游戏十分方便。
我们在开发游戏过程中所学到的技术适用于 Pocket PC 平台和 Smartphone 平台。我们在这篇文章中所学到的东西适合于编写游戏和业务应用程序。良好的用户体验和电话的非干扰性集成是成功应用程序的关键。
祝您游戏愉快。