Chapter 4(1): Game Components游戏组件

942人阅读 评论(0) 收藏 举报

Chapter 4: Game Components游戏组件


This chapter talks about the concept behind the Game class and the game components you can add to it. To get your graphic engine up and running in the next chapter you still need some new helper classes before starting with 3D concepts. The BaseGame class is used to implement more features and to include all the other classes you have written so far. It is derived from the Game class to take advantage of all the existing XNA features. In the same way our main test class TestGame is derived from BaseGame to help you execute static unit tests in your game. Then you will add the TextureFont class to your Helpers namespace to allow you to draw text on the screen, which is not possible out of the box in XNA. Finally, you also add some of the existing functionality from the previous chapters such as input, controller handling, and sound output into special classes to make it much easier to write a new game. Instead of just making some general assumptions, this chapter takes the game you are going to develop later in this chapter as a prime example.

本章讲述Game类背后的概念和你可以添加的游戏组件。在你开始建立图像引擎并运行之前你仍然需要一些新的辅助类。BaseGame类用来实现更多的特性并包括你至今所写的所有辅助类。它是从Game类发展来的,并将利用XNA 的所有特性。同样方法,我们主测试类TestGame是从BaseGame发展来的,帮助你运行游戏的静态单元测试。然后你要添加TextureFont类到你的Helpers辅助类命名空间中,用来在屏幕上绘制文字。最后,你还要添加一些在前面内容中已经存在的功能,例如输入、挡板操作和声音输出,利用这些特别的类可以刚容易的编写新游戏。本章后面要开发的游戏只是作为最初的例子,你可以自己改进。

In contrast to the previous chapter you are not going to write any helper classes first, but instead you are going to write the unit tests and the game class first and then add all the game components you need to your project. In the last few projects the problems were fairly simple and once you resolved them there was no need to go through them again. For the game you are going to develop in this chapter many improvements can be made and you will see this becomes even more true the bigger the game projects become. Refactoring is still the most important thing you have to remember when working over existing code and improving your game. Sometimes you will even see the code used in the unit tests ending up somewhere else in the final game code.


As an example game a simple Tetris clone is used. It will feature a big play field with colored blocks falling down, support for keyboard and gamepad input, a next block field showing you what comes next, and a little scoreboard containing the current level, score, highscore, and lines you destroyed. If you are a Tetris fan like me and like to play it every once in a while this game is great fun. Tetris is one of the most popular puzzle arcade games ever. It was invented by the Russian Alexey Pazhitnov in 1985 and became very popular ever since Nintendo released it on the Game Boy system in 1989.

我们使用Tetris的克隆版来作为游戏示例。这个游戏的特点是有一个很大的场地,期中有不同颜色的砖块不断下落,游戏支持键盘和手柄输入,另外一个区域显示下一个要落下的砖块信息,还有一个小的分数板,里面有当前等级、分数、最高分和你消掉的行数。如果你像我一样喜欢玩Tetris,你会再写这个游戏的时候感到更有趣。Tetris曾经是最流行的一款益智游戏。是俄罗斯的Alexey Pazhitnov1985年发明的,然后由任天堂1989年在Game Boy上发布。

The Game Class

You already used the class in the previous chapters, but other than starting the game by calling the Run method from the Program class and your unit tests and using the Initialize, Update, and Draw methods, those chapters did not talk about the underlying design. Well, you don’t really need to know anything else if you are just creating a few simple games, but as games become bigger and have more features you might want to think about the class overview and class design of your game.


The Game class itself is used to hold the graphics device in the GraphicsDeviceManager instance and the content manager in the content field. From the Program class you just have to create an instance of your game class and call the Run method to get everything started. Unlike in the old days with Managed DirectX or OpenGL you don’t have to manage your own window, create your game loop, handle Windows messages, and so on. XNA does all that for you, and because it is handled in such a way it is even possible to run your game on the Xbox 360 platform where no window classes or Windows events are available.


It is still possible to access the window of the game through the Window property of the game class. It can be used to set the window’s title, specify if the user is allowed to resize the window, get the under lying Windows handle for interop calls, and so on. All these methods do nothing on the Xbox 360 platform. There is no window, there is no window title, and it can certainly not be resized. As you already saw in the previous game, you used the Window.Title property to set some simple text to the title for showing the current level and score to the user. The reason you did that is because there is no font support in XNA; to render text on the screen you have to create your own bitmap font and then render every letter yourself. In the next games and even for the Tetris game you will need that feature, and therefore the TextureFont class is introduced in a few minutes.


Additionally, it is worth mentioning that it is possible to set the preferred resolution in the game class constructor by setting the graphics properties like in the following example, which tries to use the 1024×768 resolution in fullscreen mode:



graphics.PreferredBackBufferWidth = 1024;

graphics.PreferredBackBufferHeight = 768;

graphics.IsFullScreen = true;

There is no guarantee that the game will actually run in that mode; for example, setting 1600×1200 on a system that just supports up to 1024×768 will only use the maximum available resolution.


You already know that and Draw are called every frame, but how do you incorporate new game components without overloading the game class itself? It’s time to look at the game classes and components overview of the Tetris clone game (see Figure 4-1).


Figure 4-1

The first thing you will notice is that you have now three game classes instead of just one like in the previous game examples. The reason for that is to make the main game class shorter and simpler. The BaseGame class holds the graphics manager with the graphics device, the content manager, and it stores the current width and height values of the current resolution you use in the game. The Update and Draw methods also handle the new Input, Sound, and TextureFont classes to avoid having to update them in the main game class. Then the TetrisGame class is used to load all the graphics from the content pipeline and initialize all sprites and game components, which you learn about in a second.


Finally, the TestGame class derives itself from the TetrisGame class to have access to all the textures, sprites, and game components, and it is only used in debug mode to start unit tests. The functionality of the TestGame class is very similar to the previous chapters, but this time it is organized in a nice way and separate from your main game class. The TetrisGame class uses several unit tests to make sure each part of the game works as you have planned.



Game Components

The TetrisGame class also holds all game components in the Components property derived from the game class. You can add any class that is derived from the GameComponent class to this list and it will automatically be called when your game is started and when it is updated. It will not be called when you draw your game because the GameComponent does not have a Draw method. You can, however, implement your own drawing methods or just use the DrawableGameComponent class, which does have a Draw method. XNA does not have a direct Draw support for the game components; you have to call it yourself to make sure all components are called in the correct order. Because of this and for several other reasons (forcing you to use this model with almost no advantages, makes unit tests harder, your own game classes might be more effective or specific, and so on), you will not use many game components later in this book. It is generally a nice idea, but you can live without it because you have to create game components yourself anyway and you have to call Draw for them yourself too. Just for the Update method, it does not make much sense to use them.


As I mentioned in Chapter 1 the basic idea is to have users collaborate and share their game components to allow others to use parts of their game engine. For example, a frame counter component or even a fullblown 3D landscape rendering engine could be implemented as a game component, but just because someone does not use a game component does not mean it is harder to copy over. For example, if you have a complicated game component like a landscape rendering module, it will probably involve some other classes too and use its own rendering engine, which might not work out of the box in your engine if you just copy one file over. Either way, plugging in external code often requires quite a bit of refactoring until it is usable in your own game engine. In beta 1 of the XNA Framework a graphical designer was available in XNA Game Studio Express for the game components and you could easily drag and drop components into your game class or even into other game components to add features to your game without writing a single line of code. Because this feature was very complicated, buggy, and did not work on the Xbox 360, it was abandoned in the beta 2 release of the XNA Framework.

就像我在第一章中提到的,基本的想法是和用户沟通,共享他们的游戏组件给其他人用到他们的游戏引擎中。例如,一个帧计数器组件或者甚至是一个完善的3D风景渲染引擎都可以作为一个组件运行,但是并不是说某人不使用组件游戏就变得困难。例如,如果你有一个像风景渲染模块那样复杂的游戏组件,它将可能会和其它的类有关并使用到那些类自己的渲染引擎,如果你只是单纯的拷贝一个文件到你的引擎中这就没什么创造性了。【使用外部代码经常需要大量的重构单元,在你的游戏引擎中是有用的】。在XNAbeta1版时,在XNA GSE中图像设计器是可用的,你可以简单的拖拽组件到你的游戏引擎中,或者甚至是拖到其它游戏组件中添加特性到你的游戏,而不用写任何一行代码。因为这个特性非常复杂,并有很多漏洞,而且不能在Xbox360上运作,因此在XNABeta2版中被放弃了。

It is not a sure thing that game components will not be used, and maybe it does not matter to most programmers that the designer is missing and you have to call the Draw methods yourself. Then a lot of game components might be available and it would be useful to know all the basics about them. In the case of the Tetris game a few components come to mind:


§  The grid itself with all the colored blocks and the current falling block


§  The scoreboard with the current level, score, highscore, and number of lines you destroyed


§  The next block type box for the game


§  More simple things like a frame counter, handling the input, and so on


I decided to just implement the Tetris grid and the next block feature as game components; all the code is just way too simple for implementing several new classes just for them. If you will reuse the scoreboard, for example, you could always put it in a game component, but I cannot think of any other game I would like to write that uses that scoreboard.


Take a closer look at the Game class and the components that were added to it (see Figure 4-2).


Figure 4-2

The gray arrows indicate that these methods are called automatically through the fact that TetrisGrid and NextBlock were added to the Components list of the Game class. In TetrisGame.Draw the Draw method of TetrisGrid is called, which again calls the NextBlock.Draw method. TetrisGame itself holds just an instance of TetrisGrid. The NextBlock instance is only used inside of the TetrisGrid class.


You can see that using the game components for these three classes forced you to think about the calling order and it made your game more organized just by the fact that you did not put everything into one big class. This is a good thing and, though you can do all this by yourself if you are an experienced programmer, it might be a good idea for beginners to anticipate the idea of the game components in XNA.



More Helper Classes更多的辅助类

Didn’t talk enough about helper classes in the last chapter? Yes we did. The two new classes you are going to use for the Tetris game are not discussed here in great detail and they are just stripped-down versions of the real classes you use later in this book. But they are still useful and help you to make the programming process of your game easier.


TextureFont Class

You already learned about the missing font support in XNA and you know that using bitmap fonts is the only option to display text in XNA (apart from using some custom 3D font rendering maybe). In the first games of this book you just used some sprites to display fixed text in the game or menu. This approach was very easy, but for your scoreboard you certainly need a dynamic font allowing you to write down any text and numbers the way you need it in the game.


Let’s go a little bit ahead and take a look at the TestScoreboard unit test in the TetrisGame class, which renders the background box for the scoreboard and then writes down all the text lines to display the current level, score, highscore, and number of lines you destroyed in the current game:

让我们再前进一点,看看TetrisGame 类中的TestScoreboard单元测试,这个用来渲染分数板的背景边框和显示所有的文本行告知当前的等级、分数、最高分和消除的行数。

int level = 3, score = 350, highscore = 1542, lines = 13;
// Draw background box
    TestGame.game.backgroundSmallBox.Render(new Rectangle(
512 + 240- 1540 - 10290 - 30190));

// Show current level, score, etc.
    TextureFont.WriteText(512 + 24050"Level: ");
512 + 42050, (level + 1).ToString());
512 + 24090"Score: ");
512 + 42090, score.ToString());
512 + 240130"Lines: ");
512 + 420130, lines.ToString());
512 + 240170"Highscore: ");
512 + 420170, highscore.ToString());


You might notice that you are now using the TestGame class to start your unit test. For this test you use a couple of variables (level, score, and so on), which are replaced by the real values in the game code. In the render loop you first draw the background box and display it immediately to avoid display errors with sprites you draw later. Then you write down four lines of text with help of the WriteText method in the new TextureFont class at the specified screen positions. You actually call WriteText eight times to properly align all the numbers at the right side of your background box, which looks much nicer than just writing down everything in four lines.


After writing this unit test you will get a compiler error telling you that the TextureFont class does not exist yet. After creating a dummy class with a dummy WriteText method you will be able to compile and start the test. It will just show the background box, which is drawn in the upper-right part of the screen with help of the SpriteHelper class you learned about in the last chapter.


Before you even think about implementing the TextureFont class you will need the actual bitmap texture with the font in it to render text on the screen. Without a texture you are just doing theoretical work, and unit testing is about practical testing of game functionality. You will need a texture like the one in Figure 4-3 to display all the letters, numbers, and signs. You can even use more Unicode letters in bigger textures or use multiple textures to achieve that, but that would go too far for this chapter. Please check out the websites I provided at the top of the TextureFont class comment in the source code to learn more about this advanced topic.


Figure 4-3

Take a look at the implementation of the TextureFont class (see Figure 4-4). Calling the TextureFont class is very easy; you just have to call the WriteText method like in the unit test shown earlier. But the internal code is not very easy. The class stores rectangles for each letter of the GameFont.png texture, which is then used in WriteAll to render text by drawing each letter one by one to the screen. The class also contains the font texture, which is GameFont.png, a sprite batch to help render the font sprites on the screen, and several helper variables to determine the height of the font. For checking how much width a text will consume on the screen you can use the GetTextWidth method.


Figure 4-4

The internal FontToRender class holds all the text you want to render each frame, which is very similar to the process the SpriteHelper class uses to render all sprites on the screen at the end of each frame. In the same way SpriteHelper.DrawAll is called by BaseGame, TextureFont.WriteAll is also called and flushes everything on the screen and clears all lists. To learn more about the TextureFont class, check out the source code and run the unit tests or try stepping through the WriteAll method.


Input Class

Another new class that is used in the Tetris game is the Input class, which encapsulates all the input handling, checking, and updating you did in the previous chapters yourself. Chapter 10 talks about the Input class in greater detail and some nice classes that really need all the features from the Input class (see Figure 4-5).


Figure 4-5

As you can see, the Input class has quite a lot of properties and a few helper methods to access the keyboard, the gamepad, and the mouse data. It will be updated every frame with the help of the static Update method, which is called directly from the BaseGame class. For this game you are mainly going to use the key press and gamepad press methods like GamePadAJustPressed or KeyboardSpaceJustPressed. Similar to the RandomHelper class it is not hard to figure out how this class works, and you already implemented much of the functionally in the previous chapter. For more details and uses you can check out Chapter 10.


Sound Class

Well, you already had sound in the first game in Chapter 2 and you also used it in Chapter 3 for the Breakout game. To keep things simple and to allow you to add more sound functionally later without having to change any of the game classes, the sound management is now moved to the Sound class. Take a quick look at the class (see Figure 4-6). It looks very simple in this version, but in Chapter 9, which also talks about XACT in greater detail, you will extend the Sound class quite a bit and make it ready for your great racing game at the end of this book.


Figure 4-6

As you can see, all the sound variables are now in this class and the Game class no longer contains any sound variables. The Sound constructor is static and will be called automatically when you play a sound for the first time with the Play method. The Update method is called automatically from the BaseGame class.


The Sounds enum values and the TestPlayClickSound unit test depend on the actual content in your current game. These values will change for every game you are going to write from here on, but it is very easy to change the Sounds enum values. You might ask why you don’t just play the sounds with help of the cue names stored in XACT. Well, many errors could occur by just mistyping a sound cue and it is hard to track all changes in case you remove, rename, or change a sound cue. The Sounds enum also makes it very easy to quickly add a sound effect and see which ones are available through IntelliSense.


The Tetris game uses the following sounds:


§  BlockMove for moving the block left, right, or down. It is a very silent sound effect.当方块上下左右移动的时候播放。这是一个非常安静的声音效果。。

§  BlockRotate is used when you rotate the current block and it sounds very “whooshy.当你翻转方块的时候播放,这个声音听起来非常的“whooshy”。

§  BlockFalldown is used when the current block reaches the ground and finally lands.当方块下落到底部的时候播放这个声音。

§  LineKill is played every time you manage to kill a line in the game.当你在游戏中消除了一行的时候播放。

§  Fight is played at the start of each game to motivate the player.游戏开始的时候播放。

§  Victory is used when the player reaches the next level and contains an applause sound.当玩家通过这一关到达下一关的时候播放,其中包含了一段喝彩声。

§  Lose is an old school dying sound and is played when the player loses the game.当玩家输掉这一关的时候播放。



* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    • 访问:15953次
    • 积分:267
    • 等级:
    • 排名:千里之外
    • 原创:1篇
    • 转载:0篇
    • 译文:11篇
    • 评论:10条