§10.2游戏屏幕
在图10-2你已经看到了一个简单的主菜单的例子,但您的游戏包含的不仅仅是游戏屏幕和菜单。您通常还需要一个credit屏幕让你出名,有一个Option屏幕让用户轻松设置所有设置和更改屏幕分辨率,最好还有一个Help屏幕解释游戏的基本规则(见图10-3)。
大多数游戏屏使用一个特殊的背景纹理把大多数的信息显示在屏幕上。在任务选择画面,您可以选择四项任务中的其中一个,然后启动游戏。其他屏幕只是显示出一些信息和只有在Option屏幕上允许用户可以一些东西。所有的游戏画面在按下Back键后返回主菜单。
你使用游戏屏幕堆栈自动处理所有游戏屏幕上(见图10-4)。这可以让你使用更复杂的多层次的菜单系统,并可以随时返回以前的画面。如果你想返回到主菜单,只需移除堆栈中除最后一个以外的所有项。另一个我经常使用的技巧是在主菜单前添加另一个屏幕,当用户退出游戏时显示“购买这个游戏”屏幕。这个类本身只有几行代码,你只需在主类中添加一行额外的代码。大多数游戏屏幕类也非常简单。
为什么游戏屏幕堆栈很好?因为您需要做的就是从IgameScree接口继承得到到所有的游戏屏幕类(见图10-5),然后您可以使用下面的代码自动渲染和处理屏幕了。每个游戏屏幕类的Render方法会在推出此屏幕时返回true,这样将返回到前一个屏幕。当所有游戏屏幕都被移除则退出游戏。
// No more game screens? if (gameScreens.Count == 0) { // Then quit Exit(); return; }// if (gameScreens.Count) // Handle current screen if (gameScreens.Peek().Render()) { // Play sound for screen back Sound.Play(Sound.Sounds.ScreenBack); gameScreens.Pop(); } // if (gameScreens.Peek)
帮助屏幕
作为一个非常简单的例子,下面是GameScreens命名空间下的Help类的完整代码。其他游戏屏幕类也不复杂,除了Mission类,它要负责处理整个游戏。
///<summary>
/// Help
///</summary>
class Help : IGameScreen {
#region Properties
/// <summary>
/// Name of this game screen
///</summary>
/// <returns>String</returns>
public string Name {
get {
return "Help";
} // get
}
// Name #endregion
#region Run
/// <summary>
/// Run game screen. Called each frame.
/// </summary>
/// <param name="game" /> Form for access to asteroid manager
public bool Run(RocketCommanderGame game)
{
// Render background game.RenderMenuBackground();
// Show helper screen texture
game.helpScreenTexture.RenderOnScreen( new Rectangle(0, 174 * BaseGame.Height / 768, BaseGame.Width, 510 * BaseGame.Height / 768), new Rectangle(0, 0, 1024, 510));
if (game.RenderMenuButton(MenuButton.Back, new Point(1024 - 210, 768 - 140)) || Input.KeyboardEscapeJustPressed)
return true;
return true;
}
// Run(game)
} // class Help
就是这样。看起来很简单,不是吗?如果你想了解得更多,请自己看一下游戏屏幕类。有些类包含单元测试表明行为方式和使用情况。
游戏中的用户界面
在这一节我要你跟随以下步骤制作XNA Shooter的游戏用户界面。用户界面不是很复杂,但你会遇到几个问题。以下内容被显示在这个游戏中:
- 任务时间:分,秒显示你玩了多久
- 当前分数:你射击,击落,收集物品和武器获得分数
- 最高分:努力成为排名第一
- 您目前的生命值:如果完全失去,则死
- 当前使用的武器和可以投掷的电磁脉冲炸弹图
10-6显示使用下列纹理显示这些内容:
- HudTop.png在屏幕上的最上面一栏,显示时间,分数和最高分
- HudButtom.png在底部,显示生命值和当前武器
- GameFont.png,您前几场游戏中已经使用过的游戏字体
- NumbersFont.png,顶端部分的数字,看起来比默认字体更酷
一个新的类通过NumbersFont.png纹理去显示数字,归功于Texture类和RenderOnScreen方法,这不难。 NumbersFont.png纹理在NumbersFont类中处理,它与第4章的TextureFont类很像,但简单得多,因为你只需要11个矩形,0至9的10个数字和一个冒号显示时间。
private void RenderHud()
{
// Render top hud part hudTop
Texture.RenderOnScreenRelative4To3(0, 0, hudTopTexture.GfxRectangle);
// Time
BaseGame.NumbersFont.WriteTime( BaseGame.XToRes(73), BaseGame.YToRes(8), (int)Player.gameTimeMs);
// Score
BaseGame.NumbersFont.WriteNumberCentered( BaseGame.XToRes(485), BaseGame.YToRes(8), Player.score);
// Highscore
BaseGame.NumbersFont.WriteNumberCentered( BaseGame.XToRes(920), BaseGame.YToRes(8), Highscores.TopHighscore);
// Render bottom hud part
Rectangle bottomHudGfxRect = new Rectangle(0, 24, 1024, 40);
hudBottomTexture.RenderOnScreenRelative4To3(0, 768 - 40, bottomHudGfxRect);
// Health Rectangle healthGfxRect = new Rectangle(50, 0, 361, 24); hudBottomTexture.RenderOnScreenRelative4To3(50, 768 - 31, new Rectangle(healthGfxRect.X, healthGfxRect.Y, (int)(healthGfxRect.Width * Player.health), healthGfxRect.Height)); // Weapon and Emps! Rectangle weaponMgGfxRect = new Rectangle(876, 0, 31, 24);
Rectangle weaponGattlingGfxRect = new Rectangle(909, 0, 27, 24);
Rectangle weaponPlasmaGfxRect = new Rectangle(939, 0, 33, 24);
Rectangle weaponRocketsGfxRect = new Rectangle(975, 0, 24, 24);
Rectangle weaponEmpGfxRect = new Rectangle(1001, 0, 23, 24);
TextureFont.WriteText(BaseGame.XToRes(606), BaseGame.YToRes(768 - 20) - TextureFont.Height / 3, "Weapon: ");
// Show weapon icon!
Rectangle weaponRect = Player.currentWeapon == Player.WeaponTypes.MG ? weaponMgGfxRect : Player.currentWeapon == Player.WeaponTypes.Gattling ? weaponGattlingGfxRect : Player.currentWeapon == Player.WeaponTypes.Plasma ? weaponPlasmaGfxRect : weaponRocketsGfxRect;
hudBottomTexture.RenderOnScreenRelative4To3( 715, 768 - 31, weaponRect);
// And weapon name
TextureFont.WriteText(BaseGame.XToRes(717+weaponRect.Width), BaseGame.YToRes(768 - 20) - TextureFont.Height / 3, Player.currentWeapon.ToString());
TextureFont.WriteText(BaseGame.XToRes(864), BaseGame.YToRes(768 - 20) - TextureFont.Height / 3, "EMPs: ");
// Show emp icons if we have any
for (int num = 0; num < Player.empBombs; num++)
hudBottomTexture.RenderOnScreenRelative4To3( 938 + num * 23, 768 - 31, weaponEmpGfxRect);
} // RenderHud()
这种解决办法在PC很好,但一旦你运行在Xbox 360上并在电视屏幕显示(在写RenderHud方法之前我就写了TestHud单元测试)上你会看到,HUD不是完全可见的,或者更糟,几乎看不到。
如果你从来没在一个需要连接到电视机的游戏平台上编写游戏,这可能是一个新的问题,因为在电脑显示器,您可以使用100%的可视面积,没有安全区域的概念。但对大多数电视屏幕,你看不到100%的屏幕,更多的是90%,这意味着约10%屏幕边界宽度和高度都是不可见的(见图10-7)。
你可能会问,为什么XNA不把所有东西都自动放到安全区域中去呢。因为这不是那么容易。电视机接收全部信号,并根据输入电缆和电视机的模式,你会看到不同的结果。下面是我已经遇到的情况:
- 通过VGA或DVI电缆连接的PC显示器,您可以看到100%
- 通过VGA电缆把Xbox 360连接到电脑显示器,您也能看到100%(如果分辨率不匹配则接近100%)
- Xbox 360连接到一个旧式SCART显示器,约92%可见
- Xbox 360连接到我的新戴尔24寸监视器(HDTV):约93%~95%可见(取决于分辨率)
- 一些旧电视机(据XNA文档和网上的信息)约80%~90%,但我从来没有见过80%的情况,这可能是最坏的情况
如你所见,在Xbox 360上不是正好是90%,不过不用担心。在不同的情况下结果可有很大差异,XNA框架和你的游戏也没法帮你自动检查。只有一点可以肯定的是,在电脑上100%的屏幕像素都是可见,这也就是为什么许多电脑游戏使用的边界显示用户界面元素和其他信息。如果你看一下Xbox 360游戏,你会发现,它们往往只有一个较简单的界面,也不在屏幕边缘放置信息。
你不能只是将HUD显示在90%的安全区域,因为如果使用者能看到更多区域就会发现画面有错误,因为90%区域外没有画面,如果用户看到得更少仍有以前同样的问题。在继续工作之前你应该停在这里,并重新考虑这一问题。如图10-6那样显示用户界面元素对Xbox 360游戏机来说不是个好主意。最好的解决办法是改变用户界面的图片,把他们放在安全区域里。图10-8显示了如何改变HUD的图片使之能适用两种显示情况:在PC上,您把他们在屏幕边界上;在Xbox 360,放在内部92%的区域 (在90%的安全区域仍可见,但如果只有85%或更少就很难看到,,但我从来没有见过这种最坏的情况,大多数电视机都能达到90%~95%)。
图10-9显示了最XNA Shooter游戏的最终屏幕布局。请查看Misson类中的RenderHud方法获取更多细节。它更好地适应了宽屏分辨率,在Xbox 360看上去很好,即使较小分辨率的电脑上看起来也不错。为了测试菜单和游戏屏幕可运行游戏项目示例,这个例子仍基于开始于第5章的XnaGraphicEngine项目,但你有一些新的升级过的类。
诀窍
以我的经验,在Xbox 360开发XNA游戏你必须记住几件事。在PC上这不是个大问题,但需要花额外一点时间让它们正常工作。一个主要的问题是,如果你只在PC编写游戏而最后才在Xbox 360上测试,很多事情可能出差错(.NET Compact Framework上性能不佳、UI元素屏幕边界上导致在一些电视上不可见或对Xbox 360手柄支持差,而手柄是Xbox 360游戏机最主要的输入设备等等)。如果你有兴趣,请到我的博客仔细阅读更多关于这些问题的讨论。
- 测试,测试还是测试。这是最重要的技巧。不断编写单元测试在PC和Xbox 360平台上。我同时打开PC和Xbox 360两个项目(两者使用相同的文件,但我只开发PC解决方案,而Xbox 360的解决方案只用于部署和测试)。我的几乎所有类都有单元测试,类编写完我就不断地测试。
- 不要使用foreach循环,尤其是在渲染循环中。这可能听起来有点疯狂,因为这在PC平台上没关系,现在的CPU速度足够快,能够在每帧处理创建和删除成千上万的物体,大多数游戏甚至都不需要。但在compact framework中,你每开始一个foreach循环都将创建一个新的enumerator实例,会占用很多内存,不久之后就会有很多抛弃的对象,而这些对象系统必须自己收集。这可能需要一些时间并极大地降低游戏性能。在PC版本上可能会超过200帧,但您的Xbox 360版本会停留在30~40帧左右。避免每帧都建立新的数据,并避免foreach循环(只需用正常循环取代,往往只需一条额外的代码)可以让性能提高100%或更多。
- 在Arena Wars(我开发的第一个.NET游戏),我就从来没有在游戏中创建任何数据。所有的对象在任务之前就被创造好,重用他们(这没什么大不了,因为游戏规则不允许无限多的单元,你可以通过dead元创建新单元)。这种做法可以减少垃圾收集性能冲突,这种冲突在慢的电脑NET 1.1情况下可能发生。在以后的项目,我无需关心太多关于建立新物体的问题,我只是用简单的方式编码,因为单元测试能帮你迅速的解决问题,但在.NET Compact Framework情况中未必是最好的。对于XNA Shooter和XNA Racer,我确定大部分的游戏数据在每一关卡开始就被建立,在游戏中不会变化。这使得写代码容易得多,可以让重构变得更加自由,以改善整体设计和代码的执行速度。我建议你采用“clean code”,并在开始时就考虑优化,重要代码要更好的得到优化,不然在项目中很难被管理。归功于NET 2.0中垃圾收集的改进,重用现有对象和异常处理比以前好得多,而且我们今天还拥有快得多的计算机。
- 电视上的可见区域可能会产生问题。只要搜索一下Xbox 360游戏画面的截图,你会发现,GUI (图形用户界面)大多数PC游戏有很大不同。PC游戏界面往往在屏幕边界显示提示,小按钮,以及其他不那么重要的事情。如果在Xbox 360游戏机这样做UI元素会被截除。对于XNA Shooter我不得不返工所有的用户界面元素,因为他们不适合电视屏幕,把它们放在一个栏(如Windows任务栏)中是不切合实际,因为在PC和电视屏幕上有很大不同。所以我把所有的UI元素放在浮动栏中,将根据屏幕自动调整。重要的是让重要的用户界面元素在此90%(或93%,如果你想更接近边缘)矩形之内。这意味着在1920×1080分辨率下只使用90%(1728×945),或在屏幕边缘5%左右的地方(x坐标:96,y坐标:54)绘制用户界面元素。这些像素的位置取决于屏幕分辨率,要在你的游戏主类中计算好,并利用它们绘制用户界面。
有关.NET Compact Framework、如何在Xbox 360上更好地使用XNA框架的更多知识,请阅读.NET Compact Framework小组写的以下文章,网址在http://blogs. msdn.com/netcfteam/archive/2006/12/22/managed-code-performance-on-xbox-360-for-the-xna-framework-1-0.aspx。
发布时间:2008/9/27 下午3:14:07 阅读次数:6468