# 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.

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.

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


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#region Static constructor to create log filestatic Log()...{  // Open file  FileStream file = new FileStream(    LogFilename, FileMode.OpenOrCreate,    FileAccess.Write, FileShare.ReadWrite);  writer = new StreamWriter(file);  // Go to end of file  writer.BaseStream.Seek(0, SeekOrigin.End);  // Enable auto flush (always be up to date when reading!)  writer.AutoFlush = true;  // Add some info about this session  writer.WriteLine("/// Session started at: "+    StringHelper.WriteIsoDateAndTime(DateTime.Now));} // Log()#endregion

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.

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

 Write log entry#region Write log entrystatic public void Write(string message)...{  DateTime ct = DateTime.Now;  string s = "[" + ct.Hour.ToString("00") + ":" +    ct.Minute.ToString("00") + ":" +    ct.Second.ToString("00") + "] " +    message;  writer.WriteLine(s);#if DEBUG  // In debug mode write that message to the console as well!  System.Console.WriteLine(s);#endif} // Write(message)#endregion
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.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.

### NUnit and TestDriven.Net

To do this you can use the popular NUnit Framework, which you can download at

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.

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:


#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.

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:

 [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).

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:


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).

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.

### 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.

§  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.

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.

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.

### Generate Random Vectors生成随机向量

Here you can see a method from the RandomHelper class:

 /**//// <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.

## 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.

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.

### 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.

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.

 /**//// <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:

 /**//// <summary>/// Test WriteArrayData/// </summary>[Test]public void TestWriteArrayData()...{  Assert.AreEqual("3, 5, 10",    WriteArrayData(new int[] ...{ 3, 5, 10 }));  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.

### 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.

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:

 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.

### 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.

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:

 [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.

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.

 /**//// <summary>/// Empty color, used to mark unused color values./// </summary>public static readonly Color Empty = new Color(0, 0, 0, 0);
• 本文已收录于以下专栏：

## Chapter 2 Classes and Objects

1) Body mercury; This declaration states that mercury is a variable that can hold a reference to an ...

## Chapter 1-3-Digitial image processing（2rd）

• 2015-06-07 11:50
• 6.11MB
• 下载

## [Spring Web Services 2 Cookbook][Chapter-3][Testing-and-Monitoring-Web-Services]

• 2012-10-25 07:05
• 2.13MB
• 下载

## Data Network (2nd Edition) Chapter 3

• 2010-03-29 07:15
• 3.67MB
• 下载

## 别笑我是英文单词书_chapter3_2

• 2008-03-03 07:52
• 3.87MB
• 下载

## ATL Internals 2ed复习.chapter 3.CComGITPtr

Global Interface Table (GIT)提供了进程内interface共享机制，用户可以高效的在套间之间传递interface. 使用GIT通常需要下面几步： 1..源套间在GIT...

## Chapter 3-Digitial image processing using matlab（2rd）

• 2015-06-07 11:15
• 2.07MB
• 下载

## Chapter1-2-Digitial image processing（3rd）

• 2015-06-07 11:23
• 2.85MB
• 下载

举报原因： 您举报文章：深度学习：神经网络中的前向传播和反向传播算法推导 色情 政治 抄袭 广告 招聘 骂人 其他 (最多只允许输入30个字)