用 XNA 编写的 Zune HD 游戏程序






4.99/5 (53投票s)
使用 XNA 框架为 Zune 编写的关卡程序。
引言
发布后的第二天我就收到了一个 Zune HD。所有 Zune 设备都可以通过 XNA 框架进行用户编程。因此,在“因为我能”的类别下,我决定做一个关卡程序。在完成程序后,我发现有些人想要源代码,并想知道如何将 XNA 框架安装到他们的电脑上。我之前写了一些关于早期 Zune 编程的文字,所以我把那些文字拿来,加上了一些关于加速计和多点触控面板的信息,结果就是这份文档。
您可以在 YouTube 上观看此关卡运行的视频,在此处。
必备组件
要使用本文,您应该已经理解 .NET 和 C# 的基础概念。您还应该有 Windows Forms 编程的经验。虽然本文中的代码没有使用 Windows Forms 命名空间(Zune 不支持),但您在开发 Windows Forms 应用程序时可能遇到的一些概念(如隔离存储)在这里也会用到。
所需软件和硬件
要开始为 Zune 编程,您需要一些东西:
- 运行 Windows XP 或更高版本的 PC
- Microsoft Zune(第一代或第二代),固件版本 2.5 或更高
- XNA Game Studio 3.1(http://creators.xna.com/en-US/downloads)
- Visual Studio 2008 或 Visual Studio 2008 C# Express 或更高版本(不支持 Visual Basic!)(http://microsoft.com/express/download)
- Zune 软件(http://Zune.net)
硬件差异
在撰写本文时,市面上有三种 Zune 设备。第一代 Zune(也称为 Zune 30)有一个 5 向方向键、一个播放按钮和一个返回按钮。第二代 Zune 有多种容量可供选择,从 4GB 到 120GB。80GB 和 120GB 型号使用硬盘,其他型号使用闪存。在这些 Zune 设备上,4 向方向键已被一个我最好将其描述为双模式鼠标触摸板的设备取代。您可以滑动手指在其上移动,或按下它将其用作 5 向方向键。方向键的差异并不大,但在设计用户交互时您应该注意它们;您需要同时考虑这两种硬件差异。Zune 系列的最新成员只有三个按钮(返回、电源和媒体控制按钮)。它没有方向键,但有一个用于输入的加速计和多点触控面板。屏幕支持最多 4 个同步触摸。
重启 Zune
迟早会发生;您会编写一些代码让 Zune 陷入无限循环,并且您无法退出您的应用程序。如果按住返回按钮两秒钟,应用程序将退出。如果由于某种原因您无法退出,您也可以重启 Zune。如果您的 Zune 带有触摸板,请按住返回按钮同时向上按动方向键,或者如果您有 Zune HD,则同时按住电源和返回按钮。几秒钟后,Zune 将重启。
创建新的 Zune 项目
在 Visual Studio 中创建新的 XNA 项目时,您可以将目标定位到 XBox 360、PC 或 Zune。本文仅关注 Zune 项目类型,其他类型将忽略。Zune 项目类型包括“Zune Game (3.1)”和“Zune Game Library (3.1)”。(虽然很明显微软打算将这些项目类型用于游戏,但请记住,您并不局限于此用途。如果您能想到其他非游戏类生产力任务需要您的 Zune 完成,尽管去写吧!)可以认为“Zune Game (3.1)”项目类型类似于创建应用程序,而“Zune Game Library (3.1)”类似于 DLL。
请继续,在 Visual Studio 中为名为“Hello World”的 Zune Game (3.1) 项目创建一个新项目。该项目将自动包含许多存根方法。不用担心它们的作用,我们稍后会进行检查。此程序除了渲染玉米花蓝色的背景(微软的偏爱颜色!)外,什么也不做。将您的 Zune 连接到电脑并运行项目。您会收到一个错误。
No devices are available to deploy the project ‘HelloWorld’.
Register a device using the XNA Game Studio Device Center.
如果 Zune 客户端软件正在运行,请将其关闭。一次只能有一个程序与 Zune 同步,如果它正在运行,您将在接下来的步骤中遇到失败。您可以在“Microsoft XNA Game Studio (3.1)”的程序组下找到“XNA Game Studio Device Center”。启动该程序并选择“添加设备”选项。您会被问到是添加 Zune 还是 XBox 360。选择“Zune”。您的 Zune 名称将显示出来,您可以选择它。选择“完成”后,返回 Visual Studio 并再次尝试运行您的项目。如果这是您第一次在 Zune 上运行 XNA 游戏,那么 .NET Framework 将会部署,然后是您的应用程序。当应用程序运行时,您将看到屏幕变成玉米花蓝色(这就是此程序所做的全部)。要退出程序,请按住返回按钮。
恭喜!您已经编译并运行了您的第一个 Zune 应用程序。
拆解应用程序
我们的第一个程序并没有做什么。我们将为其添加功能,但在这样做之前,让我们先进行拆解。了解 XNA 应用程序的结构将有助于创建新应用程序。Microsoft.Xna.Framework.Game
是 XNA 应用程序的基类。您通过继承此类并覆盖其方法来创建您的应用程序。Visual Studio 为我们生成的应用程序类覆盖了五个方法:
Initialize
– 包含初始化逻辑LoadContent
– 直接在Initialize
之后调用。Update
– 包含您的游戏逻辑。Draw
– 包含您应用程序的渲染代码。Unload
– 在应用程序退出前释放任何资源(通常是内容)。
Update
和 Draw
方法会以循环方式调用,直到应用程序终止。这些方法调用的图形表示如下:
对于第一代 Zune,Draw/Update 循环每秒最多发生 60 次,对于第二代 Zune,每秒最多发生 30 次(更多信息请参见硬件差异部分)。
读取按钮
XNA 框架在设计时考虑到了 XBox 控制器。XBox 控制器(和 XNA)可用于 XBox 360 和 PC。Zune 上的按钮映射到 XBox 控制器的一些按钮,以便您可以以与 XBox 控制器相同的方式与其交互。
XBox 360 控制器上的返回按钮映射到 Zune 上的返回按钮。在 Zune HD 上,这是屏幕正下方的按钮。在其他两个 Zune 型号上,这是方向键左侧的按钮。没有其他按钮映射到 Zune HD。XBox 360 控制器上的 A 按钮映射到方向键中心的“操作”按钮。B 按钮映射到 Zune 上的播放按钮。对于原始 Zune,方向键被映射为 XBox 360 控制器上的 4 向方向键。在第二代 Zune 上,如果用户按下 Zune 触摸板的边缘,则将其视为方向控制器。但是,如果用户将手指滑过 Zune 触摸板表面,则将其视为与左操纵杆相同。
现在我已经介绍了按钮映射,我将讨论如何实际读取控制器。这些信息对 Zune、带有 XBox 360 控制器的 PC 以及 XNA 在 XBox 360 上的应用都是适用的。用于与控制器交互的主要类是 GamePad
类。它是一个静态类,几乎所有方法都需要一个 PlayerIndex
类型的枚举变量。PlayerIndex
枚举有 One
、Two
、Three
和 Four
这些值。这个数字用于标识要与之交互的控制器。仅限 Zune,PlayerIndex.One
是有效的。
GamePad
与许多 XNA 类一样,GamePad
类有一个名为 GetCapabilities
的方法,该方法提供有关控制器功能的***。如果我上面没有提到按钮映射,此方法返回的 GamePadCapabilities
对象将告知您存在哪些按钮映射。GamePad
的另一个感兴趣的方法是 GetState
方法。它将在 GamePadState
对象中返回所有按钮和方向输入的控制器状态。GamePadState
对象中的 Buttons
成员有一组 ButtonState
成员,其值可以是 Pressed
或 Released
(如果按钮不存在,则其状态始终为 Released
)。ThumbSticks
成员包含两个名为 Left
和 Right
的 Vector2
成员。Right
成员在 Zune 上永远不会有任何有用的信息,但 Left
成员将告知您用户正在触摸 Zune 触摸板的位置。
在示例程序中,我仅使用 Zune 上的 Back 按钮来退出程序。
将您的 Zune 连接到 Visual Studio
您需要将您的 Zune 指定为 XNA 可以定位的设备。首先,将您的 Zune 连接到电脑。为了将程序部署到您的 Zune,Visual Studio(或 Visual Studio Express)需要能够通过 XNA Game Studio Connector 与 Zune 通信。必须安装 Zune 软件以确保 Zune 具有正确的驱动程序。但是,如果 Zune 软件在 PC 上运行,Visual Studio 将无法与 Zune 通信。请确保它没有打开。启动 XNA Game Studio Connector,然后选择 **添加设备** 选项。您将看到添加 XBox 360 或 Zune 的选项。选择 **Zune**,然后点击下一步。
如果您的 Zune 已连接到电脑,它将作为可定位的设备列出。选择它,然后点击下一步。另一个屏幕会问您是否希望将此 Zune 设置为默认 Zune。您可以安全地保留此选项的默认值,然后点击下一步完成该过程。对您想要添加的任何其他 Zune 重复相同的过程。
在为一台或多台 Zune 完成该过程后,XNA Game Studio Device Center 将显示您所有的 Zune。奇怪的是,Zune HD 显示的图标与 Zune80/120 相同。Zune30 和 Zune4 显示正确的图标。
显示文本
在 Zune 版 XNA Game Studio 3.0 之前,如果您想在屏幕上绘制文本,您必须加载一组字母图像,并选择性地从您的字体图像中绘制部分到屏幕。截至 3.1 版,SpriteMap
类已自动化了部分过程。我们将更新我们的 HelloWorld 程序,使其显示时间。要显示时间,我们首先需要将字体导入到我们的应用程序中。然后,我们必须使用该字体来渲染文本。
要导入字体,请执行以下操作:
- 右键单击项目中的“Content”区域,然后选择“Add New Item”。
- 将
SpriteFont
选为新的项目类型,并将名称输入为MyFont.spritemap
。 - 一个 XML 文件会打开。在此文件的
<FontName>
元素中,将文本替换为您要使用的字体名称。还可以选择更改字体大小和间距的设置。 - 在
CharacterRegion
区域,确保Start
和End
元素设置为要包含在SpriteFont
中的 Unicode 字符范围。
现在,使用字体:
- 将应用程序的一个类型为
SpriteFont
的新成员添加为应用程序的成员。将其命名为MyFont
。 - 在
LoadContent
方法中,键入以下内容: - 将
Draw
方法的内容替换为以下内容: - 运行程序。
MyFont = Content.Load<SpriteFont>("Myfont");
GraphicsDevice.Clear(Color.CornflowerBlue);
Vector2 TextPosition = new Vector2();
TextPosition.Y=100;
TextPosition.X=20;
spriteBatch.Begin();
spriteBatch.DrawString(MyFont, DateTime.Now.ToString(),TextPosition,Color.Red);
spriteBatch.End();
程序运行时,它将显示当前的日期和时间(精确到秒)。您可以随时按 Back 按钮退出程序。
几点警告:字体与软件一样,是知识产权,您可能无法使用某些字体,因为存在潜在的版权侵权问题。另请注意,字体与其他资源一样,会占用空间。尽可能严格地限制字体包含的字符范围,仅限于您的程序将使用的字符。
显示内容
XNA 在 Zune 上可以显示多种不同的文件格式。在大多数情况下,我建议使用 PNG。以下是您可以使用的一些图像格式的非详尽列表:
- BMP
- JPG
- PNG
- PPM
当前所有 Zune 的分辨率都是 240x320 或 272x480。在调整资源大小时请牢记这一点。由于我们才刚刚开始,我想让图像操作保持简单。我想在我已有的程序中加载一张图像作为背景。我将使用一个 Texture
对象来处理我的图像。使用任何绘图编辑器,创建一个 240x320 大小的图像。将图像添加到您项目的 *Content* 文件夹。XNA Game Studio 将自动将其制作成纹理类型的内容资源。我将我的图像命名为“Background.png”。添加到项目后,应应用以下代码更改:
- 将一个类型为
Texture
的成员添加到应用程序类。将其命名为MyTexture
。 - 在
LoadContent
方法中,添加以下内容: - 在
Draw()
方法中,删除对Clear()
的调用。 - 在方法中使用以下内容作为渲染代码:
MyTexture = Content.Load<Texture>(“Background”);
spriteBatch.Begin();
spriteBatch.Draw(MyTexture, imagePosition, Color.White);
spriteBatch.DrawString(MyFont, DateTime.Now.ToString(), TextPosition, Color.Red);
spriteBatch.End();
现在,当您运行程序时,您将在时间后面看到您的背景。
条件编译
可以将相同的源代码在 Zune、XBox 360 和 PC 之间共享。但是,XNA 的某些方面是这些设备特有的。条件编译可用于保持单个代码库,同时不使用不支持的平台上的功能。对于 Zune 项目,定义了 ZUNE 符号。
#if ZUNE
限制
与为许多其他移动设备编程一样,对于 Zune,您必须记住,您不是在与开发桌面应用程序时相同的资源进行编程。在 XNA 程序中,您将仅限于 16MB RAM,屏幕尺寸对于 Zune HD 为 272x480,对于其他 Zune 为 240x320。此外,Zune 上的色彩深度为 16 位。
由于 Zune 没有键盘或鼠标,如果您尝试查询这些设备在 Zune 上的状态,您会发现它们始终报告没有按键,并且鼠标的 X 和 Y 值始终为零。
与加速计交互
目前,Zune HD 是唯一支持加速计的 XNA 平台。如果您编写通用代码(以便它可以在 PC、XBox 360 和各种 Zune 上运行),那么您将需要能够检测您是否正在运行一个拥有加速计的设备。您可以从名称恰当的 Accelerometer
类中获取您想要了解的关于加速计的所有***。Accelerometer
类是静态的,因此您可以立即开始使用它。GetCapabilities
方法将通过它返回的 AccelerometerCapabilities
实例中的 IsConnected
成员来告知您加速计是否存在。AccelerometerCapabilities
类还具有三个成员(HasXAxis
、HasYAxis
、HasZAxis
),这表明将来可能会存在不检测所有三个平面的加速度的设备。我可以看到一种以这种方式实现的类似赛车方向盘的界面。可以通过 AccelerationResolution
以及 MaximumAcceleration
和 MinimumAcceleration
值来查询设备加速度检测的限制。
要读取加速计的状态,请使用 Accelerometer
类的 GetState
方法。它返回一个 AccelerometerState
对象。该实例包含一个名为 IsConnected
的布尔属性,用于告知您是否存在加速计,以及一个 Vector3
实例,用于告知您加速计沿 X 轴、Y 轴和 Z 轴的读数。
与触摸板交互
触摸板是这个程序的一个小组件。如果您按下屏幕上的返回按钮图标,它就会退出。在此程序中,触摸板没有以任何其他方式使用。但是,使用触摸板类似于使用游戏手柄或加速计。您有一个静态类,其中包含一个 GetCapabilities
方法(用于检测硬件是否可用),以及一个 GetState
方法用于读取硬件的实际状态。您可能会注意到我一直将该表面称为触摸板而不是触摸屏。理论上,触摸表面不必在屏幕上。想想鼠标垫或数位板,它们都是一种触摸表面,但不在屏幕上。“触摸板”这个术语更广泛地包含了将来可能添加到 XNA 支持的其他类型的触摸表面。
静态类名为 TouchPanel
。其 GetCapabilities
方法返回的 TouchCapabilities
对象有一个名为 IsConnected
的成员,用于告知您触摸板是否可用,以及 MaxTouchCount
,用于告知您触摸板可以检测到的触摸次数。TouchPanel
的 GetState
方法返回一个 TouchCollection
对象。TouchCollection
包含一个 TouchLocation
列表,其中包含屏幕上被触摸区域的坐标(作为 Vector2
)。Zune HD 上的触摸板最多可以检测到四次触摸,因此此成员中最多只会包含四个成员。
对于附带的程序,我只检查是否有任何触摸点与退出按钮相交。这里没有以任何其他方式使用触摸。
重制气泡水平仪
规划与设计
似乎任何带有加速计的移动设备上都会有一个气泡水平仪程序。这是具有加速计功能设备的非官方“Hello World”应用程序。所以,为了遵循传统,我为我的 Zune HD 编写了一个气泡水平仪程序。程序的创建始于一张方格纸。我标记了一个与 Zune HD 的 272x480 屏幕成比例的矩形,然后用它来决定视觉元素的布局。我想有一个水平仪、一个垂直仪和一个圆形仪。这是我最终确定的布局:
现在我知道了所需的布局,我就可以开始创建我的图形资源了。由于草图是按比例绘制的,我可以使用它来确定我的每个资源的具体大小。由于我只打算在这个程序上投入几个小时,所以我希望快速完成图形的制作。但同时,我也不想让它们看起来很糟糕。所以,我使用了 Expressions Design 来快速制作我需要的图形。我从几年前的个人项目中保存了一个拉丝金属图像,所以我重新使用了它。生成的图像资源如下:
图形作为内容添加到程序中。我还将一个字体添加到了内容中,以便能够渲染文本。有了所有必需的资源后,下一步就是编写代码。但在编写代码之前,需要理解一些数学知识。
加速计是如何工作的?
加速计测量设备相对于自由落体的加速度。换句话说,它测量设备加速度减去物体所承受的重力。
Accelerometer
类返回一个 Vector3
对象,其中包含 X、Y 和 Z 分量,可用于描述设备的***。对于静止设备,这三个数字描述的向量长度接近于一。如果您还记得 勾股定理(通常写成 的形式),那么您也会意识到这意味着 x*x+y*y+z*z 将约等于 1。如果我将 Zune HD 面朝上放在水平桌子上,加速计读数将是 {X:0, Y:0, Z:-1}。如果我将设备面朝下放置,加速计读数将是 {X:0, Y:0, Z:1}。如果我将 Zune 竖立起来,使其左侧在地面上,右侧在天花板上,那么向量将接近 {X:-1, Y:0, Z:0}。如果我将 Zune HD 平放在桌子上并开始向左或向右倾斜,则 Z 轴上的读数将开始减小幅度,而 X 轴上的读数将增加幅度。这可能难以可视化。因此,我鼓励您运行本文包含的“AccelerometerTest”程序,以感受加速计对运动的响应。
程序背后的数学
现在我们有了加速计的读数,我们需要能够将其转换为角度。如果您还记得三角学,您可能会回想起反正切函数可以用来从 X 和 Y 坐标获取角度。但在我们开始走这条路之前,我们需要记住我们有三个坐标,而不是两个。另外,反正切函数无法区分某些位置的角度。有一个第二种形式的反正切函数,通常标记为 arctan2
,它具有我们所需的功能。
Vector3 accelReading = accelState.Acceleration;
tiltDirection = (float)Math.Atan2(accelReading.Y, accelReading.X);
tiltMagnitude = (float)Math.Sqrt(accelReading.X * accelReading.X +
accelReading.Y * accelReading.Y );
现在我有了倾斜方向和幅度,我需要能够将其转换为气泡指示器的 X 和 Y 位移。这很容易通过正弦和余弦来实现。Math.Sin(tiltAngle)
将把我的角度转换为 1 到 -1 之间的水平位移。-Math.Cos(tiltAngle)
也将角度转换为 1 到 -1 之间的位移。我可以取这些函数的结果并将其缩放到适合我想要允许气泡指示器的最大位移。
Rectangle horizontalBubbleRectangle =
new Rectangle((int)(118 + 102 * tiltMagnitude * -
Math.Cos(tiltDirection)), 22, 36, 36);
Rectangle verticalBubbleRectangle =
new Rectangle(22, (int)(80 + 140 + 124 * tiltMagnitude *
Math.Sin(tiltDirection )), 36, 36);
Rectangle smallBubbleArea = new Rectangle(156 +
(int)(48*tiltMagnitude * -Math.Cos(tiltDirection )), 156 +
(int)(48 *tiltMagnitude* Math.Sin(tiltDirection )), 8, 8);
所有计算都完成了。一旦完成,剩下的就是将对象放置在屏幕上。
spriteBatch.Begin();
spriteBatch.Draw(ProgramBackground, backgroundTextureArea, Color.White);
spriteBatch.Draw(HorizontalLevelTexture, horizontalLevelTextureArea, Color.White);
spriteBatch.Draw(VerticalLevelTexture, verticalLevelTextureArea, Color.White);
spriteBatch.Draw(RoundLevelTexture, roundLevelTextureArea, Color.White);
spriteBatch.Draw(BubbleTexture, horizontalBubbleRectangle, Color.White);
spriteBatch.Draw(BubbleTexture, verticalBubbleRectangle, Color.White);
spriteBatch.Draw(SmallBubbleTexture, smallBubbleArea, Color.White);
spriteBatch.Draw(BackArrowTexture, BackArrowPosition, Color.White);
spriteBatch.DrawString(MainFont, "J2i.net", siteAddressPosition, Color.Blue);
spriteBatch.End();
定义游戏图标
我跳过的一件事是如何定义游戏的图标。当您创建项目时,它会在项目中添加一个名为 GameThumbnail.png 的文件。编辑此文件以更改图标。
下一步
如果您想进一步研究 XNA 程序,网上有很多资源。但我强烈推荐这两个:Zune 编程参考和XNA Creator's Club 论坛。
历史
- 2009 年 9 月 25 日 - 首次发布。