Chapter 3(2): Helper Classes(2)

Logging Error Messages记录错误信息

Debugging game code can be very complicated, especially if you don’t get any exceptions, but something in your render loop goes wrong. Just setting a few breakpoints might not be enough and especially if you experience errors after running the game for a while, debugging is not the right option. You want to know what is going on each frame, but you don’t want to step through 500 frames to find it out. For these kinds of problems you can just throw some text to the console, but this will only work inside Visual Studio and you will lose all the console content the next time you start your project.

调试游戏代码是非常复杂的,特别是如果你没有得到任何异常,但是一些会在你的渲染循环中出错。只设置几个断点是不够的,特别是如果在运行游戏后才发生错误,调试就不是应该的选择。你想知道每一帧发生了什么,但是你又不想跟踪500帧去找它。遇到这种问题,你可以向控制台输出一些信息,但是这只能在VS中工作,会在下一次开始项目的时候失掉所有的输出信息。

One of the most important classes in all bigger projects I have done is the Log class, which just writes information, warning, error, or debugging texts to a simple text file. The class itself is very short and simple, but if you use it in the right way it will make your debugging and testing sessions much more enjoyable. There are also more advanced logging classes and frameworks available like Log4Net, which you can find at http://logging.apache.org/log4net/. Logging can be more than just writing a few lines to a text file. Logging data from your application can be used to find out about user errors remotely with a web service, you can fire Windows error events, and you can do a lot more things. This is not covered in this book because it is a very complex topic. For the simple games in this book, using Log class should be sufficient.

在我所有的大型项目中最重要的一个类就是Log类,这个用来向一个简单的文本文件写信息、警告、错误等文本。这个类本身非常短小简单,但是如果你能正确使用,你会很享受你的调试和测试任务。还有几个更高级的日志类和框架可以加以利用,像Log4Net,你可以在http://logging.apache.org/log4net/找到它。记录可以写很多信息到文本。你程序的记录数据可以用来通过web服务器来远程找出用户的错误,你可以使用Windows的错误事件,然后可以做更多事。这个话题太过于复杂,因此本书不讨论。对于本书简单的游戏,使用Log类足以应付。

Take a look at the Log class (a more complex version can be found in the Breakout game):

来看看Log类(更复杂的版本可以参见Breakout游戏):


  
  
   
    
  
  
public class Log
   
   
{
   
   
  #region Variables
   
   
  private static StreamWriter writer = null;
   
   
  private const string LogFilename = "Log.txt";
   
   
  #endregion

The Log class uses the Log.txt file to store all the log messages with the help of the StreamWriter object stored as a static object to have easy access from your static methods. The first time this class is called it gets instantiated through the static constructor:

Log类使用Log.txt文件来储存所有记录信息,它使用了StreamWriter静态对象,则可以使用静态方法轻松访问。这个类的第一次调用并初始化是在静态构造函数中:


  
  
   
    
    
    
Static constructor to create log file
FileShare.ReadWrite makes sure you can always read and write the file from outside while the game is running. Other than that the writer is set to the end of the file, auto flush is enabled to make sure writing new data is immediately saved to the log file, and finally you add a little text indicating that this session started. For the time stamp you will use a helper method from the StringHelper class you learn about in a second.

FileShare.ReadWrite使你可以在游戏运行时候对外部文件进行读写操作。除了writer是从文件尾部开始之外,自动缓冲(auto flush)可以让新数据立即保存到日志文件中,最后你添加一小段文本表示这一次操作开始的时间。使用StringHelper类的一个辅助方法得到的时间戳让你知道精确到秒的时间。

And finally, here is the most important method and the only one you will ever call from this class:

最后,在这个类中有一个非常重要并且是你会唯一使用到的方法:


  
  
   
    
    
    
Write log entry
First, a simple time stamp is added in front of the message. Then the message is written to your Log.txt file and finally you also add the message to the console if the project is in debug mode. Now you can add a new line to the Log.txt file every time you complete a level in the Breakout game from Chapter 2 by just adding the following line: 
    
    

首先,一个简单的时间戳被添加到信息的前面。然后将信息写入到你的Log.txt文件中,最后如果项目是在调试模式就把信息写到控制台。现在每当你完成一个级别的Breakout游戏后就可以通过下面一行代码来可以向Log.txt中写入信息了:


  
  
   
    
  
  
Log.Write("Level " + level + " completed.");

Unit Testing in XNA XNA中的单元测试

Before going into greater detail about the helper classes for the upcoming projects in this book, this section talks a little bit about unit testing. You already learned about static unit tests in the last chapter. Static unit tests are great for quickly checking visual results, testing physics and the controllers, and building your game in a quick manner. But helper classes and components that do not require user input would require you to think about interfacing with them. That makes no sense because unit testing is all about improving the maintainability of your application and making sure everything runs as errorless as possible. For example, to test if the Log class works you could call the following lines of code:

在进入更详细的辅助类学习之前,这一部分要谈论的是一些关于单元测试的东西。你已经在上一章里学习了静态单元测试。静态单元测试可以快速的检查看到的结果,测试物理还有控制器,还可以快速建立你的游戏。但是辅助类和组件不需要用户输入只需要你考虑将它们组合起来。但是这是没有意义的,因为单元测试是用于改善你程序的可维护性和让尽可能少的出现错误。例如,

要测试Log类是否工作,你可以调用下面的代码:
   
   
FileHelper.DeleteFile(Log.LogFilename);
   
   
Log.Write("New log entry");

Tip 

This code can only be executed inside the Log class because Log.LogFilename is private.

因为Log.LogFilename是私有的,所以这段代码应当在Log类中执行。

Now you could go into the application directory and check if the log file exists and has an entry with the “New log entry” text in it. But checking this file yourself over and over again is a bit of a hassle. Instead of logging every error here you should only put the less important warning messages (e.g. user is not connected to the internet) into the log and throw exceptions when fatal errors occur (e.g. texture not found, no shaders available, etc.). This is especially true if the problems become bigger and the tests are much more complex and involve a very lengthy check process. You can avoid checking for errors yourself by letting these tests check themselves and let them be executed automatically instead of calling them yourself from the Program class like static unit tests.

现在你可以进入到程序目录,看看记录文件是否存在,是否有一个“New log entry”字样的入口点。一遍又一遍的自己检查这个文件是麻烦的。多位替代记录所有的错误,你只需要记录下没错误那么重要的的警告信息(例如永华没有连接到互联网),然后在发生致命错误的时候抛出异常。(例如纹理文件没有找到,没有可用的着色器等等)。当你的问题变得越来越大、测试越来越复杂、包含了一个非常长的检查过程,这么做是很有必要的。你可以避免检查自己检查错误而交由测试程序来自动检查,而不是像在静态单元测试里那样需要你自己从Program类中来调用。

NUnit and TestDriven.Net

To do this you can use the popular NUnit Framework, which you can download at http://www.nunit.org/.

你可以使用流行的NUnit框架,并从http://www.nunit.org/.中下载到。

Alternatively you can also use TestDriven.NET from http://www.testdriven.net/ if you are using Visual Studio 2005 Professional or better. It supports many cool features and you can start tests directly with hotkeys or the popup menu, which is really cool and simple. TestDriven.Net does not work in VC# Express or XNA Studio Express (it did work a year ago, but the developer had to remove the plugin support for Express because Microsoft wants developers to use the Professional Edition for serious programmers). See Chapter 1 on how to get XNA working in Visual Studio 2005; use a dummy project from XNA Studio Express for processing your content, which is not possible in Visual Studio 2005.

作为另一个选择,如果你使用VS2005专业版或更高级的版本,你可以选择使用TestDriven.NET。它支持很多很酷的特性,你可以使用热键或弹出菜单直接开始测试,真是又酷又简单。TestDriven.Net不能工作在VC# Express版或XNA Studio Express版中(一年前曾经支持过,但是开发者不得不将插件移除,因为MS希望真正的开发者们使用专业版本的VS)。看回第一章,如何让XNAVS2005下工作;从XNA Studio Express中使用一个假的项目来处理你的素材,因为VS2005不支持素材处理。

It does not matter which one you install, just add the NUnit.Framework.dll from the installation folder to your project (right-click your project references and add a new reference; use Browse if you can’t find it in the Global Assembly Cache (GAC), which is represented in the first tab). Now you can add the following using directive:

你安装那个并不重要,从安装目录中选择NUnit Framework .dll添加到你的项目中(右击你的项目引用然后添加新的引用;如果你不能在Global Assembly Cache (GAC)中找到它,就用浏览器)。现在你可以添加下面的指令了:


  
  
   
    
  
  
#if DEBUG
   
   
using NUnit.Framework;
   
   
#endif

I usually add this at the very top of the using directives region and the reason why this is used only in debug mode is the fact that you will only use the unit tests in your debug build; for the final game you don’t want the extra NUnit.Framework.dll and all the testing code, because it is not required for your game.

我经常将这个添加在【命令段开始的地方】,为什么这个只是用在调试模式的原因就是事实上你只能在调试生成的时候使用单元测试;对于最终的游戏你不会想要额外的NUnit.Framework.dll和测试代码,因为这在游戏中是不需要的。

As an example, take a look at the first unit test in the StringHelper class, which checks if the IsInList helper method works as expected:

来看一个例子,这是StringHelper类的第一个测试,用来检查IsInList方法是否正常运行:


  
  
   
    
    
    
[TestFixture]
public   class  StringHelperTests
{
  
/// <summary>
  
/// Test IsInList
  
/// </summary>

  [Test]
  
public void TestIsInList()
  
{
    Assert.IsTrue(IsInList(
"whats",
      
new string[] "hi""whats""up?" }false));
    Assert.IsFalse(IsInList(
"no way",
      
new string[]
      
"omg""no no""there is no way!" }false));
  }
 // TestIsInList()
...
Assert is a helper class inside the NUnit framework and it contains methods to check if the return values are as expected. If the value is not as expected an exception will be thrown and you can immediately see which line of your test failed. For example, Assert.IsTrue checks if the return value of IsInList is true. If the return value is false, an exception will be thrown. Luckily the string list contains “whats” and the test should pass. The next test checks for “no way” and that string is not in the second string list and therefore the second test line should return false as it does. Note: “there is no way!” contains “no way,” but you were not checking the Contains method, which also exists in the StringHelper class. IsInList only returns true if the exact string was found in the list.
   
   

AssertNUnit framework里的一个辅助类,它包含有检查返回值是否正确的一些方法。如果返回值不是希望的,它就会抛出一个异常,你可以马上看到哪一行的测试失败了。例如,Assert.IsTrue用来检测IsInList的值是否为真。如果返回值是假,一个异常将被抛出。幸运的是字符串列表包含“whats”,这个测试将会通过。下一个测试检查“no way”,而在字符串列表里没有包含,将会返回false。注意:“there is no way!”包含“no way”,但是不用Contains方法检查,这个方法在StringHelper类中。如果整个字符串可以在列表中找到,则IsInList返回真。

Starting Unit Tests 开始单元测试

You can run the test in TestDriven.Net by clicking the right mouse button and selecting “Run Test” (see Figure 3-6) or you can use the NUnit GUI program if you don’t have or cannot use TestDriven.Net. You can also test static unit tests the same way with TestDriven.Net, but the NUnit GUI does not support static unit tests. For this reason I added the unit tests in Program.cs (or the UnitTesting.cs class in later projects) to support all users and XNA Studio Express. TestDriven.Net can be used to start both dynamic and static unit tests, but since version 2.0 you will have to remove the [Test] attribute from static unit tests in order to work properly (you don’t use the [Test] attributes for static unit tests in this book anyway).

你可以右击鼠标并选择“Run Test”来运行TestDriven.Net测试,或者如果不使用TestDriven.Net你可以使用NUnit GUI程序。你可以在TestDriven.Net中使用同样的方法来进行静态测试,但是NUnit GUI不支持静态单元测试。因为这个原因,我在Program.cs(或者之后项目中的UnitTesting.cs类)中添加单元测试来支持所有用户和XNA Studio ExpreeTestDriven.Net可以用来开始动态和静态的单元测试,但是自从2.0版之后你要从静态测试中移除[Test]属性才能正常运行(在本书中你不需要为静态测试使用[Test]属性)。

 
Figure 3-6

The test will execute without any error, but if you were to change the test by changing “whats” to “whats up” the first Assert test would fail and you would see the following results from TestDriven.Net:

这个测试可以无错运行,但是如果你在第一个Assert测试中把“whats”修改成“whats up”,测试将失败,你会从TestDriven.Net看到下面的结果:


  
  
   
    
  
  
TestCase 'M:XnaBreakout.Helpers.StringHelper.StringHelperTests.TestIsInList' failed:
   
   
  NUnit.Framework.AssertionException
   
   
  at NUnit.Framework.Assert.DoAssert(IAsserter asserter)
   
   
  at NUnit.Framework.Assert.IsTrue(Boolean condition, String message, Object[] args)
   
   
  at NUnit.Framework.Assert.IsTrue(Boolean condition)
   
   
  C:/code/XnaRacer/Helpers/StringHelper.cs(1387,0): at
   
   
    XnaBreakout.Helpers.StringHelper.StringHelperTests.TestIsInList()
   
   

  
  
   
    
  
  

  
  
   
    
  
  
0 passed, 1 failed, 0 skipped,  took 0,48 seconds.

This tells you exactly where to look (you can even double-click the error and jump to the line 1387) and what you should change. The error becomes even more visible if using the NUnit GUI (see Figure 3-7).

这个结果会精确的告诉你要去看哪里(双击错误,会跳到1387行)和什么你应该修改什么。如果你用NUit GUI则错误会变得更明显。


Figure 3-7

The NUnit GUI is a good tool to run many unit tests at once and quickly see which ones did not work properly and then investigate further in the source code. You can use File Load to select your program or just drag and drop any .NET .exe or .dll file onto the NUnit GUI program. Then you see all the tests in that assembly and can test them by clicking Run. The program is nice, but I usually don’t go outside of my programming environment when coding and testing, and therefore I like TestDriven.Net a lot more and use it all the time. To fix the error you just change the “whats up” line back to “whats” and all your tests will pass and you get a green light.

NUint GUI是一个一次性运行多个单元测试的好工具,可以看到那一些没有正常工作,然后可以在源代码中检查。你可以从File->Load来选择你的程序或直接将.NET.exe.dll文件拖拽到NUint GUI程序中。接着你在【assembly】中看到所有的测试,并且可以右击鼠标来运行。这个程序很好,但是当我编码和测试时候,我是不会离开我的编程环境的,为此我更喜欢TestDriven.Net并总是使用它。要改正错误你只需要将”whats up”行改回”whats”,然后你的所有测试将会通过,得到一个绿灯。

Golden Rules黄金法则

I did not go much into writing unit tests here because Chapter 2 already discussed the basic rules, which of course also apply for dynamic unit tests. Keep these guidelines in mind when you start writing your first unit tests:

在这里你不需要了解太多关于单元测试,因为第二章中已经讨论了基本的规则,当然同样适用于动态单元测试。当你开始写单元测试的时候记住下面的规则:

§  Think about your problems and divide them into small manageable parts.

仔细考虑你的问题,把他们分解成小的可管理的部分。

§  Write the tests first and do not think about the implementation, just write them down like you think the final code should look or like you want to have your game code.

一开始写单元测试,不用考虑执行,只用把他们按你想象的最终代码那样写下来。

§  Try to make sure you test as much as possible. For example, the TestIsInList method tests both a successful call to IsInList and a failure from the IsInList call. Spend time with your unit tests, but never more than 50% - you should not have to write 30 checks for a method that has only two lines of code.

试着确认尽可能的测试。例如,用TestIsInList方法测试成功的调用IsInList和调用IsInList后出错两个测试。在单元测试上花时间,但不要超过50%——你不可能去写30个检查来测试两行代码。

§  Start the test constantly from this point on, even when you think it does not make sense. It will force you to see what has to be done and how far you are in the implementation process. At first the test will not even compile because you haven’t implemented anything. Then after implementing empty methods the test should fail because you are not doing anything yet. Later when everything works you will feel much better.

始终从这一点开始测试,即使是当你认为它是没有意义的。则会促使你去观察什么是要被做的,你在执行过程到了什么程度。一开始测试不会编译,因为你还没有运行任何东西。然后你运行空方法后测试会失败,因为你还没有做任何事。稍后当所有工作都运行之后你会感觉好多了。

§  Though you will not test your static unit tests very often, dynamic unit tests can be tested every single time you compile your code (if they all run quick enough). Always try to run all unit tests once a day or once a week to make sure your latest code changes did not add new bugs or errors.

即使你不经常测试静态单元测试,动态单元测试可以在你每一次编译代码的时候进行测试(如果都运行得足够快)。经常试着每天都运行一次所有的单元测试,或者每周一次的确认你最新的代码修改没有添加新的错误。

RandomHelper Class

Take a look at one of the helper classes. RandomHelper will not be used often in a single project, but almost any game uses some random number generation to make the game content appear less periodic and to add more variation to the game.

来看看一个辅助类。RandomHelper在一个单独的项目中不会经常用到,但是几乎任何一个游戏都是用一些随机数生成来让游戏呈现出更少的周期性进而增加游戏的可变性。

In the Breakout game you will write in a little bit, the blocks are generated randomly. For level 1 you use a probability of 10%, level 2 uses a probability of 20%, and so on. This way the level gets more filled and the game gets harder. You could just use the Random class and call the Next method to get a new random number, but in case you want to generate a random normalized vector you would have to write the following lines of code:

Breakout游戏中你要写一些代码,来让砖块随机生成。第一级使用10%的可能性,第二级使用20%的可能性,如此类推。用这种方法游戏的级别会不断的被填满,游戏会变得越来越难。你可以使用Random类,然后调用Next方法来获得新的随机数,但是在本例中你要生成一个随机的标准化向量,你学要添加如下代码:


  
  
   
    
    
    
Random randomGenerator  =   new  Random(( int )DateTime.Now.Ticks);
Vector3 randomNormalVector 
=   new  Vector3(
  (
float )randomGenerator.NextDouble()  *   2.0f   -   1.0f ,
  (
float )randomGenerator.NextDouble()  *   2.0f   -   1.0f ,
  (
float )randomGenerator.NextDouble()  *   2.0f   -   1.0f );
randomNormalVector.Normalize();
Instead of repeating this code over and over again, a helper class like RandomHelper might be very useful. Figure 3-8 shows the basic layout of the RandomHelper class.
   
   

为了不让这段代码一遍又一遍的重复,有一个可能非常有用的辅助类叫RandomHelper。图3-8显示了RandomHelper类的基本设计。


Figure 3-8

As you can see the methods are very simple and it would only take a couple of minutes to write the whole class. However, the class is still useful and thanks to the internal globalRandomGenerator instance of the Random class, the RandomHelper class is much quicker generating random values than creating a new Random class every time you need a random number.

想你看到的那样,类中的方法都非常简单,写整个类只需要花费你几分钟的时间。这个类很有用,并使用了internal globalRandomGenerator来替代Random类,使用RandomHelper类生成随机值比每一次需要的时候都创建一个Random类更快。

Generate Random Vectors生成随机向量

Here you can see a method from the RandomHelper class:

以下是RandomHelper类的一个方法:


  
  
   
    
    
    
/// <summary>
/// Get random Vector2
/// 获得随机Vector2
/// </summary>
/// <param name="min">Minimum for each component</param>
/// <param name="max">Maximum for each component</param>
/// <returns>Vector2</returns>

public   static  Vector2 GetRandomVector2( float  min,  float  max)
{
  
return new Vector2(
    GetRandomFloat(min, max),
    GetRandomFloat(min, max));
}
  //  GetRandomVector2(min, max)
It does not make sense to unit test any method in the RandomHelper class because all return values are random and you don’t really have to check if GetRandomVector2 returns a Vector2; it just does that. There is not much that can go wrong.
   
   

测试RandomHelper类中的方法没有多大意义,因为所有的返回值都是随机的,你没有必要检查GetRandomVector2是否返回的是一个Vector2。这样做没多大可能会出错。

StringHelper Class

The class is one of the biggest helper classes and I guess it was the first helper class I ever wrote because working with strings involves so many things, it is very easy to think about many ways to improve performance, handle lists of strings easier, output string data easily, and so on.

StringHelperlei类是最大的辅助类,我想这也是我最先写的一个辅助类,因为处理字符串包含了很多东西,考虑改进性能的方法也非常容易,更容易操作字符串表,输出字符串数据等。

If you take a look at the StringHelper class (see Figure 3-9) you will immediately notice the many methods and all the method overloads supporting many different parameter types. It also contains quite a lot of unit tests; you just saw a unit test from the StringHelper class a few minutes ago.

如果你看一看StringHelper类你会立即发现有很多方法和所有的方法重载都支持不同的参数类型。它还包含有很多单元测试,几分钟前你还看了StringHelper类的一个单元测试。

 
Figure 3-9

You might ask yourself why there are only a couple of unit tests, but so very many methods in this class. The reason for this is that I started coding this class many years ago, a long time before I started using unit testing. Some methods don’t make much sense in .NET 2.0 because the framework implements them now, but I got used to my own methods. I just hope you can find some useful methods in this class. It might take a while to get used to the many methods, but when you need a complicated string operation you will thank me (or yourself if you have your own helper class) for a useful method.

你也需要问你自己,为什么就这么几个单元测试,在这个类中却会有这么多的方法。这其中的原因是我在很多年前就开始写这个类了,很久以前就开始使用单元测试了。一些方法在.NET2.0中没有什么意义,因为framework中已经为我们提供了具有相同功能的方法,但是我还是习惯使用自己的方法。我只是希望你能在这个类里找到一些有用的方法。习惯使用这些方法可能需要一段时间,但是当你需要一个复杂的字符串操作的时候你会感谢我的(或感谢你自己,如果你有自己的辅助类的话)。

Extracting Filenames

Many of the GetDirectory, CutExtension, and so on methods are available in the Path class from the System.IO namespace too, but one of the most useful methods in StringHelper for filenames is the ExtractFilename method, which cuts off both the path and the extension to just get the name of a file, nothing else. Path.GetFileNameWithoutExtension does a similar thing, but I like my own method better for some reason. It might also be interesting if you want to implement your own methods and need some working code you can start with. Again: You don’t have to write your own Path methods, but sometimes you don’t know what the framework provides or you just want to investigate yourself.

很多诸如GetDirectory, CutExtension等等之类的方法,在System.IO命名空间中的Path类中已经提供了,但是StringHelper中作用于文件名最有用的一个方法是ExtractFilename,用来去掉路径和扩展名。Path.GetFileNameWithoutExtension也提供了这个功能,但是我更喜欢使用我自己的方法。如果你想使用自己的方法这会很有趣。再说一遍:你不需要编写自己的Path方法,除非你不知道framework已经提供了或者仅是出于自己研究。

It’s been a long time since I tested the performance of these methods, but I would still guess that most of the StringHelper methods are faster than some of the Path methods.

我已经很久没有测试这些方法的性能了,但是我始终相信大多数StringHelper方法比Path方法的运行速度快。


  
  
   
    
    
    
/// <summary>
/// Extracts filename from full path+filename, cuts of extension
/// if cutExtension is true. Can be also used to cut of directories
/// from a path (only last one will remain).
/// 从完整的 路径+文件名 形式中分离出文件名,并去掉扩展名(如果cutExtension值为真的话)。
/// 也可以用来从路径中取出目录,仅仅留下最后一个目录名。
/// </summary>

static   public   string  ExtractFilename( string  pathFile,  bool  cutExtension)
{
  
if (pathFile == null)
    
return "";

  
// Support windows and unix slashes
  string[] fileName = pathFile.Split(new char[] '/''/' });
  
if (fileName.Length == 0)
  
{
    
if (cutExtension)
      
return CutExtension(pathFile);
    
return pathFile;
  }
 // if (fileName.Length)

  
if (cutExtension)
    
return CutExtension(fileName[fileName.Length - 1]);
  
return fileName[fileName.Length - 1];
}
  //  ExtractFilename(pathFile, cutExtension)
Writing a unit test for a method like this is also very simple. Just check if the expected result is returned:
   
   

像下面那样写个简单的单元测试。用来测试是否是希望的返回值:


  
  
   
    
  
  
Assert.AreEqual("SomeFile",
   
   
  StringHelper.ExtractFilename("SomeDir//SomeFile.bmp"));

Writing Lists

A little bit more unique is the WriteArrayData method in the StringHelper class, which writes any type of lists, arrays, or IEnumerable data to a text string, which then can be used for logging. The implementation is again quite simple:

StringHelper中的WriteArrayData方法比较独特,它像一个文本字符串中写入各种lists, arrays, 或者 IEnumerable数据,这些会用来做记录。代码也是很简单:


  
  
   
    
    
    
/// <summary>
/// Returns a string with the array data, ArrayList version.
/// 返回一个ArrayList版本的数组数据字符串
/// </summary>

static   public   string  WriteArrayData(ArrayList array)
{
  StringBuilder ret 
= new StringBuilder();
  
if (array != null)
    
foreach (object obj in array)
      ret.Append((ret.Length 
== 0 ? "" : ""+
        obj.ToString());
  
return ret.ToString();
}
  //  WriteArrayData(array)
Lists, even generic ones, are derived from the ArrayList class and therefore you can call this method with any dynamic list. For arrays a special overload exists, also for special collections and byte or integer arrays, which would work with IEnumerable too, but it is faster to use overloads that don’t use the object class.
   
   

List是从ArrayList类派生出来的泛型数组,你可以动态的调用它。数组有特殊的重载,有集合、byte integer数组,都继承了IEnumerable接口,但是使用重载比使用对象类更快。

To test the WriteArrayData method you could write a method like the following one:

下面代码用俩测试WriteArrayData


  
  
   
    
    
    
/// <summary>
/// Test WriteArrayData
/// </summary>

[Test]
public   void  TestWriteArrayData()
{
  Assert.AreEqual(
"3, 5, 10",
    WriteArrayData(
new int[] 3510 }));
  Assert.AreEqual(
"one, after, another",
    WriteArrayData(
new string[] "one""after""another" }));
  List
<string> genericList = new List<string>();
    genericList.Add(
"whats");
    genericList.AddRange(
new string[] "going""on" });
  Assert.AreEqual(
"whats, going, on",
    WriteArrayData(genericList));
}
  //  TestWriteArray()
Other Helpers
  
  

The Helpers namespace contains a few more helper classes; most of them are simple like the RandomHelper class. It is not very exciting to go through all of them, so please review the ones that are not mentioned in this chapter yourself and test them out with the included unit tests if you want to know more about them.

Helpers命名空间包括几个辅助类;大多数都像RandomHelper类一样简单。不用这么兴奋的把它们都使用完,请你自己看看本章中没有提及的的方法,如果你想更多了解这些方法你可以自己测试它们。

Before you go into the Breakout game at the end of this chapter, take a quick look at some of the remaining helper classes, which will be used more frequently in the next chapters: SpriteHelper, EnumHelper, and ColorHelper.

在我开始进入本章最后的Breakout游戏之前,来快速看看剩下的辅助类,这些会在下一章中频繁地用到:SpriteHelper, EnumHelper, ColorHelper

SpriteHelper Class

You used a lot of sprite rendering in the previous chapter and because of the unit tests you forced yourself to write an easy way to handle sprites in XNA. This approach and the fact that you should put useful code that is used more than once into reusable classes leads us to the SpriteHelper class (see Figure 3-10). Basically it provides a constructor to create new SpriteHelpers storing the texture and graphic rectangle data and a few Render methods to easily draw the sprites to the screen like you did in the last chapter with a list of SpriteToRender classes.

在前面章接种你使用了很多精灵渲染,由于单元测试强制你自己去写一个方便的方法来操作XNA的精灵。将要重复使用的代码写成一个复用类形成了SpriteHelper类。它提供了一个构造器构造一个新的SpriteHelpers储存纹理和图像矩形数据,还有一些Render方法可以很方便的在屏幕上绘制精灵,就像你在最后一章要用的SpriteToRender列表一样。

 
Figure 3-10

Most methods here don’t do much; the constructor just sets the values, Render just adds a new SpriteToRender instance to the sprites list, RenderCentered renders the sprite centered at the specified location, and DrawSprites finally draws all the sprites on the screen. Take a look at the DrawSprites method, which will look similar to the DrawSprites method from the previous chapter, but with some improvements:

大多数方法需要做的并不多;构造器仅仅是设置值,Render仅仅是添加一个新的SpriteToRender来代替精灵列表,RenderCentered将精灵渲染到指定位置,DrawSprites最后在屏幕上绘制所有的精灵。再来看看DrawSprites方法,看起来和前面章中的DrawSprites很相像,但是做了改进:


  
  
   
    
    
    
public   static   void  DrawSprites( int  width,  int  height)
{
  
// No need to render if we got no sprites this frame
  if (sprites.Count == 0)
    
return;

  
// Create sprite batch if we have not done it yet.
  
// Use device from texture to create the sprite batch.
  if (spriteBatch == null)
    spriteBatch 
= new SpriteBatch(sprites[0].texture.GraphicsDevice);

  
// Start rendering sprites
  spriteBatch.Begin(SpriteBlendMode.AlphaBlend,
    SpriteSortMode.BackToFront, SaveStateMode.None);

  
// Render all sprites
  foreach (SpriteToRender sprite in sprites)
    spriteBatch.Draw(sprite.texture,
      
// Rescale to fit resolution
      new Rectangle(
      sprite.rect.X 
* width / 1024,
      sprite.rect.Y 
* height / 768,
      sprite.rect.Width 
* width / 1024,
      sprite.rect.Height 
* height / 768),
      sprite.sourceRect, sprite.color);
      
// We are done, draw everything on screen.
      spriteBatch.End();


      
// Kill list of remembered sprites
      sprites.Clear();
    }
  //  DrawSprites(width, height)
This method is called with the current width and height of the render window resolution to scale all sprites up and down depending on the resolution, which is important to support all Xbox 360 screen resolutions. In the DrawSprites method you first check if there is something to render. Then you make sure the static sprite batch was created, which will be used for all sprites you draw here. After beginning the sprite batch you go through all sprites for this frame and rescale their rectangles to fit correctly on the screen, and finally you let everything draw on the screen by calling End. The sprites list is also killed to start fresh the next frame. As an example on how this class works see the Breakout game at the end of the chapter.
   
   

这个方法使用当前要渲染的屏幕分辨率的高和宽最为参数来调用,然后根据分辨率来缩放精灵,这对于要支持Xbox360屏幕分辨率来说是很重要的。在DrawSprites方法中你首先要检查是否有东西需要渲染。接着你要确认静态精灵组已经被创建,这是要用在你所有要绘制的精灵上的。在开始精灵精灵组之后你要检查这一帧所有的精灵,然后重新调整他们的矩形以适应当前屏幕,最后调用end来将所有东西都绘制到屏幕上。精灵列表会被清除为下一帧做好准备。最为一个这个类的工作例子,看看本章结尾的Breakout游戏。

EnumHelper Class

The EnumHelper class (see Figure 3-11) is useful when you want to enumerate through any enum or quickly find out the number of enum values contained in an enum. For the Pong and Breakout games you are not using any enums, but in the next chapter the Enum class becomes useful when going through the types of blocks for the game there. Please also note that the EnumHelper class uses some methods of the Enum class, which are not implemented in the .NET Compact Framework. To avoid any compilation errors the whole class is usually left out of the Xbox 360 project, but you can still use it on the Windows platform if you like.

当你想列举所有枚举或者快速获取枚举值的时候,EnumHelper类是很有用的。对于PongBreakout游戏,你不需要用到枚举,但是在下一章使用blocks类型时Enum类就变得很有用。还要注意EnumHelper类使用了Enum类的方法,这个在精简版的.NET是运行不了的。为了避免编译错误,所有的类都忽略Xbox360项目,但是你可以按你需要的在Windows平台中使用。


Figure 3-11

The unit test for TestGetAllEnumNames looks like this and also explains how GetAllEnumNames works. It goes through each enum value with help of the EnumEnumerator helper class inside of EnumHelper:

TestGetAllEnumNames做的单元测试看起来像这样,也揭示了GetAllEnumNames类是如何工作的。它使用EnumHelper中的EnumEnumerator的帮助来检查每一个枚举值。


  
  
   
    
    
    
[Test]
public   void  TestGetAllEnumNames()
{
  Assert.AreEqual(
    
"Missions, Highscore, Credits, Help, Options, Exit, Back",
    EnumHelper.GetAllEnumNames(
typeof(MenuButton)));
}
  //  TestGetAllEnumNames()
And the GetAllEnumNames just uses the WriteArrayData helper method of the StringHelper class just talked about: 
   
   

GetAllEnumNames仅仅使用刚才提到的StringHelper类的WriteArrayData辅助方法:


  
  
   
    
    
    
public   static   string  GetAllEnumNames(Type type)
{
  
return StringHelper.WriteArrayData(GetEnumerator(type));
}
  //  GetAllEnumNames(type)
ColorHelper Class
   
   

Initially the ColorHelper class (see Figure 3-12) was a lot longer and had many methods, but because the new Color class in XNA is much more powerful than the Color class from System.Drawings that is used in Managed DirectX, many of the methods were not required anymore. It still contains some useful methods you use for color manipulation.

最初ColorHelper类更长并有很多方法,但是因为XNA新的Color类比MDX中的System.DrawingsColor类更强大,因此很多方法不再需要了。这个类中依然包含了一些对颜色操作有用的方法。

 


Figure 3-12

For example, the Color.Empty field is used to initialize shader effect parameters to unused values - 0, 0, 0, 0 is usually not a valid color; it is completely transparent. Even black has 255 for the alpha value.

例如,Color.Empty字段用来初始化着色器效果参数的值为0, 0, 0, 0,这不是一个有效的颜色,这是完全透明的。而alpha值为255代表黑色。


  
  
   
    
    
    
/// <summary>
/// Empty color, used to mark unused color values.
/// </summary>

public   static   readonly  Color Empty  =   new  Color( 0 0 0 0 );
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值