4.4更多辅助类
上一章我们讨论了很多辅助类的知识,本章并不会特别详细地讲解Tetris游戏中要用到的两个新辅助类,因为它们是本书后面要用到的真正的类的精简版本。但它们还是很有用的,可以让您的游戏开发过程更加容易。
TextureFont类
您已经了解到XNA本身不支持字体,唯一一个显示文本的方式就是使用位图字体(也可能使用自定义的3D字体渲染方式)。在本书的前几个游戏中,您只是使用了一些sprites在游戏或者菜单中显示固定的文本内容。这种方式很简单,但在实现记分板功能的时候,您很显然需要动态字体支持,这样才可以显示任何游戏中需要的文本和数字内容。现在让我们稍微超前一点,先看看TetrisGame类中的TestScoreboard单元测试,它的作用是渲染记分板的背景区域,并显示游戏的当前级别、得分、最高分以及消除的行数:
int level = 3, score = 350, highscore = 1542, lines = 13;
TestGame.Start("TestScoreboard",
delegate
{
// Draw background box
TestGame.game.backgroundSmallBox.Render(new Rectangle(
(512 + 240) - 15, 40 - 10, 290 - 30, 190));
// Show tetris grid in center of screen
TestGame.game.tetrisGrid.Draw(new GameTime());
// Show current level, score, etc.
TextureFont.WriteText(512 + 240, 50, "Level: ");
TextureFont.WriteText(512 + 420, 50, (level + 1).ToString());
TextureFont.WriteText(512 + 240, 90, "Score: ");
TextureFont.WriteText(512 + 420, 90, score.ToString());
TextureFont.WriteText(512 + 240, 130, "Lines: ");
TextureFont.WriteText(512 + 420, 130, lines.ToString());
TextureFont.WriteText(512 + 240, 170, "Highscore: ");
TextureFont.WriteText(512 + 420, 170, highscore.ToString());
});
TestGame.Start("TestScoreboard",
delegate
{
// Draw background box
TestGame.game.backgroundSmallBox.Render(new Rectangle(
(512 + 240) - 15, 40 - 10, 290 - 30, 190));
// Show tetris grid in center of screen
TestGame.game.tetrisGrid.Draw(new GameTime());
// Show current level, score, etc.
TextureFont.WriteText(512 + 240, 50, "Level: ");
TextureFont.WriteText(512 + 420, 50, (level + 1).ToString());
TextureFont.WriteText(512 + 240, 90, "Score: ");
TextureFont.WriteText(512 + 420, 90, score.ToString());
TextureFont.WriteText(512 + 240, 130, "Lines: ");
TextureFont.WriteText(512 + 420, 130, lines.ToString());
TextureFont.WriteText(512 + 240, 170, "Highscore: ");
TextureFont.WriteText(512 + 420, 170, highscore.ToString());
});
请注意此处使用的是TestGame类来启动单元测试。该测试中使用了几个变量(level,score等等),在具体的游戏代码中它们会被真实的数据取代。在渲染循环中,首先画出背景区域并立即显示出来,以避免稍后绘制sprites时出现错误。然后,使用TextureFont类中的WriteText方法在指定的屏幕位置上画出四行文字。实际上WriteText被调用了8次,是为了让数字在背景区域内靠右对齐,这样看起来更好看一点。
写完该单元测试,会得到一个编译错误,告诉您TextureFont类还不存在。创建一个包含WriteText伪方法的伪类(即TextureFont类)之后,就可以编译并开始测试了,不过此时只在屏幕的右上角显示背景区域。
在考虑实现TextureFont类之前,您要有包含字体的位图素材,这样才能在屏幕上渲染文本。没有素材的话您仅仅是在做理论工作,而单元测试却是关乎游戏功能的实践性测试。您需要一个如图4-3所示的素材,来显示所有的字母、数字和符号。您也可以使用更大的包含更多Unicode字符的素材,或者使用多个素材来达到同样的目的,不过这已经超出了本章要讨论的范围。有关这个高级主题,您可以查看一下源代码TextureFont类的注释中所列出的几个网址,来了解更多的相关知识。
(译者注:这几个网址是:
http://blogs.msdn.com/garykac/archive/2006/08/30/728521.aspx
http://blogs.msdn.com/garykac/articles/732007.aspx
http://www.angelcode.com/products/bmfont/ )
图4-3
接下来看一下TextureFont类的实现(见图4-4)。TextureFont类的调用很简单,就像前面的那个测试一样,只需调用WriteText方法。但其内部的代码实现并不那么简单。该类为素材GameFont.png中的每个字符都生成一个Rectangle对象实例,然后在WriteAll方法中把要渲染的文本逐个字符地画到屏幕上。它还包含了其它几个变量,比如字体素材即GameFont.png,用于渲染sprites的SpriteBatch实例,以及用于确定字体高度的相关变量。如果要获取给定文本的宽度,可以使用GetTextWidth方法。图4-4
其中FontToRender类用来保存每一帧要渲染的文本内容,它很像SpriteHelper类在每一帧结尾时渲染sprites的过程。与BaseGame调用SpriteHelper.DrawSprites方法一样,TextureFont.WriteAll也会被BaseGame调用,把所有内容绘制到屏幕上,然后清空所有列表。如果想进一步了解TextureFont类,请查看本章的源代码,运行单元测试,或者试着一步步调试WriteAll方法。Input类
Tetris游戏中要使用的另一个新辅助类是Input类,它封装了前几章中您做过的所有输入处理、检测以及更新操作。第十章将更详细地讨论Input类,而且有些类非常需要Input类提供的相关操作(如图4-5)。图4-5
可以看到,Input类拥有非常多的属性以及一些用于访问键盘、gamepad和鼠标的方法。BaseGame类直接调用Input类的静态Update方法,这样每一帧它都会被更新。而本游戏中您将主要使用键盘和gamepad被按下时的相关处理方法,例如GamePadAJustPressed和KeyboardSpaceJustPressed。和RandomHelper类一样,您也会很容易地理解该类如何工作,而且前一章您已经实现了大部分功能了。有关该类更多内容请参照第十章。Sound类
关于声音的处理,在第二章的第一个游戏以及第三章的Breakout游戏中您已经接触过了。为了简单起见,以及今后在添加更多声音功能时不至于修改已有的代码,本章中把声音的管理都转移到了Sound类中。如图4-6显示了该类的信息。该版本看起来很简单,但是到了第九章,会进一步讨论XACT的内容,您将对Sound类进行大量的扩展,以便为本书最后要做的赛车游戏做好准备。图4-6
可以看出,所有的声音变量都被放到了该类中,Game类中不再包含任何声音变量。Sound类的构造函数是静态的,它会在您第一次调用Play方法播放声音的时候自动被调用。其Update方法将被BaseGame类自动调用。其中Sounds枚举值以及TestPlayClickSound单元测试取决于您要做的当前游戏的实际内容。这些值将随着每一个游戏而改变,不过更改Sounds的枚举值也很简单。您可能会问为什么不直接使用保存在XACT中的cue names来播放声音?理由是,仅仅是误输入sound cue的话就会产生很多错误,而且万一您移除、重命名或者变更sound cue的话会很难跟踪所有的变化。使用Sounds枚举可以很容易地添加一个新的声效,而且通过编辑器的智能感知系统可以知道都有哪些声效可用。
Tetris游戏使用了如下一些声音:
- BlockMove:用于方块向左、向右或者向下移动时,该声效非常非常小
- BlockRotate:用于旋转方块时,它听起来非常地“whooshy.”
- BlockFalldown:用于方块落地时
- LineKill:用于消除一行时
- Fight:用于游戏开始时激励玩家
- Victory:用于玩家升级时,伴有鼓掌欢呼声
- Lose:用于玩家输掉游戏时