Using IronPython for Application Testing

Understanding Why You Want to Use IronPython for Testing

Every testing technique you’ve ever used has some drawback. For example, if you include debug statements in your code, you must ensure that you perform a release build to remove the statements before you release the code. Otherwise, the application will run slowly. In addition, using debug statements can cause the application to perform differently from the way it performs when you use it in a production environment, which makes it possible that the very tests that you depend on to check the application will actually hide problems from view.

Using IronPython for testing has a considerable number of benefits over other testing tools. The biggest benefit is that you don’t have to do anything special to the application. The test harness you create exists outside the application and doesn’t affect the application in any way. All the test harness does is monitor application behavior and report on it to you. As a result, if the test harness reviews every aspect of the application and verifies that it runs correctly, the application will run correctly in the production environment, too, because nothing will have changed.

As you’ve seen throughout the book, IronPython is an interpreted environment. That means you don’t have to create the test harness in one piece — you can create it a little at a time as you try things out with the application. In fact, the very nature of IronPython makes it possible for you to play “what if” analysis on your application. You can see just how bad you can make the application environment and try things that users do, such as create unreasonable execution conditions.

Using an IronPython script for testing means that all the testing code is in one place. If you decide that you need to add another test, you don’t have to delve into the inner workings of the application to add it and then create another build. Instead of using this time-consuming process, you simply add a few more lines to an external script using any text editor that you like. There’s nothing complicated about the process — anyone knowledgeable about your application should be able to do it without any problem.

The external nature of IronPython also makes it impossible for your test code to add problems (such as errors, performance issues, or reliability concerns) to the application. In some cases, adding test code actually introduces an application error, making it hard to know whether the error is in the test harness or the application. If there’s a problem in the IronPython test harness, you’ll see an IronPython error telling you about it. In short, you have separation between the test harness and the application, which ensures one won’t affect the other.

There are a few downsides to working with IronPython as a testing tool. The most important of these issues is that IronPython treats your application like a series of black boxes. It provides input to a method and expects a certain output. However, IronPython can’t see into the method to test individual elements within it.

IronPython also can’t see private members of your application, so it can’t test absolutely every aspect of your application. If a private member is causing a problem, you need to use some other tools to find it. Of course, you can use IronPython to infer certain issues in private methods based on their effect on public methods, but this kind of logic can prove more troublesome than direct testing.

Considering the Test Environment

Before you begin writing your test harness, you need to consider the test environment. The test environment determines how you test the application, be it a DLL or a desktop application with user access. The following list provides some criteria you need to consider as part of the test environment.

  • Code access: You must define how the test harness will access the code. It’s important to determine whether the harness will test absolutely every method, property, event, and other application element individually, whether it will test elements in combination, or whether it will use a combination of individual and combined tests.
  • Test ranges: A test harness must test both the possible and the impossible. For example, you might design a method to accept positive numbers from 0 through 5. However, the test harness must also test numbers greater than 5 and less than 0. In addition, it must test unexpected input, such as a string.
  • User emulation: When working with some applications, you must determine how to emulate user activity. For example, you might write down a series of steps that the user will take to perform a certain activity and then execute those steps in your test harness. Of course, users are unpredictable; your script must also perform some haphazard and unpredictable steps and provide unexpected input. If you find that users are doing something you never expected, you must add it to the test harness.
  • Security testing: If you don’t try to break down the walls you erected for your application, someone else will most certainly sign up for the job. Because IronPython tends to treat everything as public, it actually makes a great tool for testing security. You’ll find no artificial walls to keep things neat and tidy. Security is never neat or tidy — it’s all about someone ripping away the veneer of the façade you called security when you put the application together. IronPython lets you test your application brutally, the same way someone else will.
  • System characteristics: Even though you can’t write code to ensure that your application will run on every machine in the solar system, you can do things such as add random pauses in your code to mimic activity on an overloaded system. You can also execute your application and its test harness on a number of different machine configurations to verify that the application will run as expected.

There are probably other criteria that you need to consider for your individual testing scenario. Take time to brainstorm scenarios, worst-case situations, and truly horrifying events, and then test for them. The following sections provide some additional insights about the test environment and the issues you must consider.

Defining Access

The matter of access is an essential part of testing. The word “access” has all kinds of meanings and connotations. Of course, there’s the access of your test harness to the code within the application. The black box nature of IronPython prevents access in depth, but careful programming can provide access to unprecedented amounts of information within your application and make testing relatively complete.

You must also consider the access the user has to the application as part of the test harness. For example, if you use external configuration files, you can count on some number of users accessing them. Even if you work out methods that are seemingly impossible to overcome, a user or two will find a way to overcome them. Anything you can devise will be overcome by someone (it’s always easier to destroy than to create). Consequently, you must consider all forms of user access as part of your test harness — if for no other reason than to determine how bad things can get when a user meddles.

It’s also important to consider external access. Whenever a system has access to the network or the Internet, you must consider the potential for outside sources to access your application (even if your application isn’t designed for such access). Many vendors of shrink-wrapped software have gained notoriety for not taking this kind of access into consideration. The thought was that the application didn’t access the outside source, so there wasn’t any need to consider the outside source during testing. It turns out that any outside access opens avenues of influence and attack for all the applications on a system, so you must test this kind of access.

Access is a two-way street. As part of your testing harness, you must consider application access to external resources. For example, you must consider what happens when an application attempts to access a particular file on disk and can’t find it. Even more important, you need to consider resources on the network or on the Internet. There are many forms of access that your test harness must consider as it tests the various methods inside the application. It isn’t always possible to test simply for strict inputs or outputs; you must test inputs and outputs within the confines of an environment defined by various kinds of access.

Considering a Few Things IronPython Can’t Test

Earlier, you learned that IronPython tests application elements using a black box approach — given a particular input, what should the element provide as output? However, there are other limitations you need to consider in the way IronPython performs testing. For example, IronPython can’t perform stress testing. If you want to test your application in a memory-starved environment, then you need to combine IronPython with another tool. For example, you might want to read the article at http:// msdn.microsoft.com/magazine/cc163983.aspx about a load-generating tool you can build yourself. Web application load testing requires other techniques that you can learn about at http:// support.microsoft.com/kb/231282. If you need to stress test applications in combination with a particular server, check out the site at http://blogs.msdn.com/nickmac/archive/2004/10/06/ server-stress-tools.aspx.

IronPython can perform diagnostic testing of your application with ease, but it doesn’t make a good environment for performance testing. As with stress testing, you need to combine IronPython with another tool to check application performance in various conditions. In fact, you may very well need to combine IronPython, your stress testing tool, and your performance testing tool to obtain statistics for a range of test scenarios and environments.

The point of this section is that while IronPython is a good scripting tool or a good diagnostic tool, it can’t do everything. In many cases, you must combine IronPython with one or more additional tools to obtain the desired information about your application. Your test plan should include all of these contingencies, and you should consider them before you create your test harness.

Creating the Test Harness

An advantage to working with IronPython is that you need not create the test harness in one sitting. You can use an iterative technique to create the test harness. It’s possible to start with a small nugget of tests that you know you must perform, and then add to that nugget as other issues come to light. Eventually, you end up with a full-blown test suite.

Most .NET developers won’t initially understand the benefits of using an interpreter for testing, but the realization will grow with time that interpreters make things easy. If you get an idea, you don’t have to run a complete test or compile anything. All you need to do is open up the IronPython console, load the assembly you want to test, and then try out various tests until you come up with a perfect combination of items to use. At this point, you can click the system menu in the IronPython console, choose Edit ➪ Mark, highlight the text you want to copy from your experiments, and press Enter to copy it to the clipboard. Now you can paste the text you’ve created into your test harness and comment it. In fact, the IronPython console (and all consoles for that matter) provides a number of commands, as shown in Figure 18-1.

As an alternative, if you already have the beginnings of a test-harness check, but want to add to it, you can always paste the text directly into the IronPython console using the Paste command shown in Figure 18-1. The interpreter will automatically execute any statements that you paste into it, so you’ll be ready to start typing new code after you paste it.

Modularity is the name of the game when it comes to a test harness. Try to place the individual tests into separate files so that you can reuse the code later. Simply have a centralized file where you call each of the tests in turn. The tests will output the information you need to screen, so the developer using the test harness need not even know that there are multiple files involved.

Use the text-editing tools to copy and paste text as needed.
Figure 18-1: Use the text-editing tools to copy and paste text as needed.

Testing DLLs

DLLs present one of the easier ways to begin using IronPython to test applications. In fact, you’ve already performed a kind of testing in Chapters 16 and 17 when you created the extensions and then used their content as part of an IronPython application. All that a test harness will do is formalize the testing process so that the output you receive speaks directly about the functionality of the DLL under test, rather than its use in an application. The following sections describe how to perform a test on a DLL using IronPython.

Creating the Test DLL

The DLL used for testing purposes is extremely simple so that the section doesn’t focus more on an interesting DLL than it does on testing techniques. All that this DLL provides is an account where you make an initial deposit to create the account and then make deposits, withdrawals, and transfers. The DLL includes a number of features so that you can try things out, but the code definitely isn’t production quality. For one thing, most of the error-checking code is left out to keep the code clear so you can easily see what will happen next. Listing 18-1 shows the DLL code used for this example.

Listin g 18-1: Defining a DLL to test

[code]
public class Accounts
{
// Contains the current account amount.
private Int32 Total;
public Accounts()
{
// Sets a default acccount amount.
Total = 5000;
}
public Accounts(Int32 Initial)
{
// Set a user supplied initial amount.
Total = Initial;
}
// Provides access to the account total.
public Int32 GetTotal
{
get { return Total; }
}
// Adds a deposit to the account.
public Int32 Deposit
{
set { Total += value; }
}
// Subtracts a withdrawal.
public Int32 Withdrawal
{
set { Total -= value; }
}
public void Transfer(Accounts Account2)
{
// Place the money in the second account in the first account.
this.Total += Account2.Total;
// Withdraw the money from the second account.
Account2.Total = 0;
}
}
[/code]

The example includes two constructors (something you didn’t try in Chapters 16 or 17). The developer can create an account with a default value of 5000 or provide some other initial amount. In either case, you end up with a new Accounts object that has Total defined.

The GetTotal property is read-only and lets the developer obtain the total in the count from Total. Using a property enables you to perform checks before allowing people to have the information. For example, you could place a security code in this property to ensure that only authorized personnel received the information. If a developer were to take this approach, you’d need to write a test to check the GetTotal property using an account other than the developer account.

The Deposit and Withdrawal properties are write-only. The caller doesn’t receive anything back from them. You could use a method to perform the task as well. Using a property makes the test code easier to read, but that’s the only advantage. In both cases, the properties change the value of Total. Of course, you can perform checks in the properties, such as verifying that a withdrawal won’t result in an account with a value less than 0.

The Transfer() method moves all the money from one account to the other. Typically, you’d provide some type of transaction support in a method of this type, but the example doesn’t include it. This is one situation where IronPython can test the method’s inputs and outputs, but can’t test the internal workings of the method. You’d need another tool to test issues such as whether the transaction support actually worked as intended.

Creating the DLL Test Script

It’s time to build an IronPython script to test the DLL shown in Listing 18-1. In this case, the test script is a bit short and doesn’t test every contingency (such as starting with a negative amount in the account), but it demonstrates how you’d create a test script for a DLL. Listing 18-2 contains the code needed for this example.

Listin g 18-2: Developing a DLL test harness

[code]
# Creates a new heading.
def CreateHeading(Test, Title):
print ‘n########################################‘
print ‘Test ID = ‘, Test
print ‘Test Title = ‘, Title
# Displays the values.
def ShowValues(Expected, Received):
print ‘Expected Value = ‘, Expected
print ‘Received Value = ‘, Received
if Expected == Received:
print ‘Test Passed’
else:
print ‘Test Failed’
# Ends the test.
def CreateFooter():
print ‘########################################‘
# Print out statements of everything the test is doing.
print ‘Beginning Test’
print ‘Loading clr’
import clr
print ‘Loading test module’
clr.AddReferenceToFile(‘TestDLL.DLL’)
from TestDLL import *
CreateHeading(‘0001’, ‘Creating Account1’)
Account1 = Accounts()
ShowValues(5000, Account1.GetTotal)
CreateFooter()
CreateHeading(‘0002’, ‘Making a Deposit’)
Account1.Deposit = 1000
ShowValues(6000, Account1.GetTotal)
CreateFooter()
CreateHeading(‘0003’, ‘Making a Withdrawal’)
Account1.Withdrawal = 500
ShowValues(5500, Account1.GetTotal)
CreateFooter()
CreateHeading(‘0004’, ‘Creating Account2’)
Account2 = Accounts(3000)
ShowValues(3000, Account2.GetTotal)
CreateFooter()
CreateHeading(‘0005’, ‘Transferring Money’)
Account1.Transfer(Account2)
print ‘nAccount1 = 8500’
ShowValues(8500, Account1.GetTotal)
print ‘nAccount2 = 0’
ShowValues(0, Account2.GetTotal)
CreateFooter()
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)
[/code]

Let’s begin with the three functions at the beginning of the script: CreateHeading(), ShowValues(), and CreateFooter(). It may seem a bit silly at first to create these functions, but they provide a method for changing the output of the tests quickly, should you need to do so. In addition, you don’t want to write the same print statements hundreds of times as you create your script. It’s far easier to simply call the functions.

The CreateHeading() and CreateFooter() functions don’t have much logic in them — they simply display information onscreen. The ShowValues() function does have a bit of logic. In this case, it simply compares the expected value to the result and displays the appropriate output text. However, you could perform any number of checks required by your application. For example, if you’re working with strings, you might need to check the string length and determine precisely how it differs from another string.

Notice that the __main__() code begins with print ‘Loading clr‘. It’s important to describe every event that occurs in the test script. Otherwise, you won’t know where a script has failed during testing. Make sure you describe the mundane acts of loading and unloading modules, as well as the actual tests.

The first test begins with a call to CreateHeading() with the test number and title. The code then performs a test, Account1 = Accounts() in this case, calls ShowValues() to test the result, and finishes with CreateFooter(). Almost all of the tests follow the same pattern.

The final test is a little different than the rest. To perform the test correctly, you must evaluate the content of both Account1 and Account2. This is a case where you can infer what is happening inside a method with the test code. The method, Transfer(), could perform the task correctly with Account1, but not with Account2, which would tell you something about the content of the method and where to look for the problem.

This final bit of script also shows the flexibility of using the three functions presented earlier. By separating the individual tasks into three parts, you can call the ShowValues() function multiple times as needed. You might also consider creating a second form of ShowValues() to accept a comparison string for output (the print ‘nAccount1 = 8500‘ part of the script).

Performing the DLL Test

It’s time to run the DLL test. If you configured your project using the techniques in Chapters 16 and 17, you should be able to click Start Debugging (or press F5) to start the build process. During the build process, the compiler checks your DLL for major errors.

After the DLL is built, the IronPython script runs. Remember that this script is running outside of the IDE, so nothing it does will actually affect the performance of your code. The diagnostic tests will run and provide the information shown in Figure 18-2.

Notice that the use of formatting, test numbers, titles, comparison values, and so on makes the test results extremely easy to read. Of course, a large DLL could overwhelm the capacity of the console to display information. In this case, you could just as easily send the output to a text file, HTML page, or an XML file. The point is that the script makes it possible to view diagnostics about your application almost immediately after you build it.

Testing Applications

You can use IronPython for more than DLL testing — you can also use it to test your applications. Applications are more of a challenge than DLLs because you have to find a way to emulate user input. Of course, many developers just aren’t as creative as users. A developer would never think about putting text where a number is expected. Many developers discover, to their chagrin, that users will also try implanting scripts and doing other weird things to the application that aren’t easy to test. Some users will even try odd character combinations looking for hidden application features or just to see what will happen. Tests will only work as well as your ability to outguess the user. The following sections show how to test a simple Windows Forms application.

The output shows a list of all of the tests run by the IronPython script on the DLL.
Figure 18-2: The output shows a list of all of the tests run by the IronPython script on the DLL.

Creating the Test Application

The test application is very simple, but it does include some internal code you can use for testing purposes. The following sections describe the test application.

Defining the Form

A Windows Forms application need not be complex to test it using IronPython. All you really need are a few controls and some buttons with code for their event handlers. Figure 18-3 shows the simple form used for this example.

As with Windows Forms you use in a DLL, you must make an important change to test an application using IronPython. All the controls you want to access must have their Modifiers property set to Public. The default setting of Private prevents you from accessing them directly in IronPython.

Building the Code

You can see that the form in Figure 18-3 has three Button controls in it. Each of the controls has a Click() event handler associated with it, as shown in Listing 18-3.

The simple form used for this example provides enough inputs to test.
Fi gure 18-3: The simple form used for this example provides enough inputs to test.

Listin g 18-3: Defining an application to test

[code]
private void btnQuit_Click(object sender, EventArgs e)
{
Close();
}
public void btnAdd_Click(object sender, EventArgs e)
{
txtResult.Text = (Int32.Parse(txtValue1.Text) +
Int32.Parse(txtValue2.Text)).ToString();
}
public void btnSubtract_Click(object sender, EventArgs e)
{
txtResult.Text = (Int32.Parse(txtValue1.Text) –
Int32.Parse(txtValue2.Text)).ToString();
}
[/code]

The btnQuit_Click() event handler is as you might expect. It simply closes the form using the Close() method. You won’t test this functionality using the IronPython script.

The btnAdd_Click() event handler converts the values of txtValue1.Text and txtValue2.Text to Int32 values using Int32.Parse(). It then adds the numbers together, converts the result to a string using ToString(), and places it into txtResult.Text. Because IronPython needs to test this event handler, the visibility is set to public. If you don’t change the visibility of the event handler, IronPython won’t be able to access it. The btnSubtract_Click() event handler works the same as the btnAdd_Click() event handler, except that it subtracts the two numbers.

Creating the Application Test Script

As long as you’re willing to make the required visibility changes to your application, you can use IronPython to test it. Creating a test project for an application works precisely the same as creating a test project for a DLL. Here’s the short list of changes you must perform:

  1. Change the build output location for both the Debug and Release builds to the solution folder.
  2. Add IPY.EXE as an existing project to your solution.
  3. Set the ipy project as the startup project so that the IDE executes it instead of the Windows Forms application.
  4. Configure the ipy project to start your script and to use the appropriate working directory.
  5. Add a new IronPython script to the solution folder.

This test script uses the three functions described in Listing 18-2 to provide output. It also adds the following two output functions:

[code]
# Verify the type.
def CheckType(Object, Type):
if Object.GetType().__str__() == Type:
print ‘Test Passed’
else:
print ‘Test Failed’
# Show initial values.
def ShowInit(Value1, Value2):
print ‘Value1: ‘, Value1
print ‘Value2: ‘, Value2
[/code]

The CheckType() function compares the type of an object you create against an expected type. If the type is incorrect, then it displays a failed message. You can use this function when creating a form or other object that could fail for any number of reasons.

The ShowInit() function displays the initial values for a binary operation or perhaps just two values used for some other task. You could probably create a version of the function that accepts any number of arguments in the form of an array. The point is that you can create some specialized functions to display data for a particular test and then find that you can use it for other purposes later.

As previously mentioned, this test script also uses the three functions found in Listing 18-2. Listing 18-4 shows the actual test script for this application. It doesn’t provide a complete test but does provide enough information that you could easily complete it if you wanted.

Listin g 18-4: Developing an application test harness

[code]
# Print out statements of everything the test is doing.
print ‘Beginning Test’
print ‘Loading clr’
import clr
print ‘Loading System assembly support’
import System
print ‘Creating a blank event argument.’
EventArg = System.EventArgs()
print ‘Loading test module’
clr.AddReferenceToFile(‘TestApplication.EXE’)
from TestApplication import *
CreateHeading(‘0001’, ‘Creating a test form’)
MyForm = Form1()
CheckType(MyForm, ‘TestApplication.Form1’)
CreateFooter()
CreateHeading(‘0002’, ‘Testing a default add’)
MyForm.btnAdd_Click(object, EventArg)
ShowInit(MyForm.txtValue1.Text, MyForm.txtValue2.Text)
ShowValues(‘2’, MyForm.txtResult.Text)
CreateFooter()
CreateHeading(‘0003’, ‘Testing a default subtract’)
MyForm.btnSubtract_Click(object, EventArg)
ShowInit(MyForm.txtValue1.Text, MyForm.txtValue2.Text)
ShowValues(‘0’, MyForm.txtResult.Text)
CreateFooter()
CreateHeading(‘0004’, ‘Testing add with one change’)
MyForm.txtValue1.Text = ‘5’
MyForm.btnAdd_Click(object, EventArg)
ShowInit(MyForm.txtValue1.Text, MyForm.txtValue2.Text)
ShowValues(‘6’, MyForm.txtResult.Text)
CreateFooter()
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)
[/code]

The test script begins by loading the required code for the test, beginning with clr. Because this test has to work with event handlers, it needs to load the System assembly and create a System.EventArgs object, EventArg. Because the event handlers in this application don’t actually use the event arguments, EventArg is actually a default object with no content. The call simply won’t succeed without it, however, so you must create it.

After the script finishes the prerequisites, it performs the first test, which is to create the Windows Forms object, Form1, as MyForm. The creation process could fail; you want to verify that MyForm isn’t null, so that’s the first test that relies on the CheckType() function. You don’t have to show the form to test it, so the code doesn’t call ShowDialog(). If you do decide to show the form, you’ll actually need someone to work with it. The script is suspended during the time the form appears onscreen.

The next step is to perform some tasks with the form. The code performs a default add and subtract. The two value fields, MyForm.txtValue1.Text and MyForm.txtValue2.Text, contain default values that you can use for testing. Actually, it’s good application design to always include default values for the user so that the user has some idea of what kind of content to provide.

The MyForm.btnAdd_Click() and MyForm.btnSubtract_Click() event handlers perform the actual addition and subtraction. In order to call these two methods, you must supply both a sender object and event arguments. The sender object can simply be an object because the code doesn’t use it.

The final test in the example is to change one of the values and perform another addition. To perform this task, the script changes the value of MyForm.txtValue1.Text and calls MyForm.btnAdd_Click(). Normally, you’d provide a wealth of additional tests to check various values and see how they react with the code. For example, you might provide some negative values to ensure that the event handlers work properly with them. You might also test incorrect input, such as providing a string. The point is that you can completely automate any level of testing using this IronPython script technique.

Performing the Application Test

At this point, you have an application to test and the script to test it. It’s time to run the application. One of the problems you could encounter is not making something public (such as an object, control, or property) that you need to test (the default is to create private objects, controls, and properties). Unfortunately, the need to make class members public is one of the problems of using IronPython for desktop application testing. It’s not a big problem, but you need to consider it. When working with an extremely large application, changing the required member visibility could prove problematic. In addition, making some members public could pose security risks.

Let’s hope everything works as anticipated when you run the test. Figure 18-4 shows typical output from this application.

As with the DLL testing script, this script outputs text that’s easy to read and results that are easy to decipher. You know immediately whether certain tests have failed and precisely what inputs were used to conduct the test. As with DLL testing, you may need to use some other form of output, such as an XML file, when performing testing on complex applications because the content won’t fit entirely onscreen.

Desktop applications can prove difficult to test, but the results are worth it.
Fi gure 18-4: Desktop applications can prove difficult to test, but the results are worth it.

Performing Command Line Tests

For many developers, testing must be formal or it really isn’t testing. Actually, ad hoc testing is sometimes better because you get to play with the application while you test it. Testing in an ad hoc manner at the command line is possible in IronPython because it’s an interpreted environment. In fact, we’ve been performing ad hoc testing throughout the book. Every time you reviewed the content of an application, no matter what type it was, using the dir() function, you were performing a kind of ad hoc testing because you were reviewing the content of the application.

The test demonstrated that the DLL had added the overrides correctly and that you should be able to access them from an application. In addition, you discovered that IronPython views the content of the DLL in a slightly different manner than another environment might view them.

Let’s look at a specific test example, the TestDLL.DLL file. For the purposes of this example, you want to use the dir() function to determine whether the Accounts class contains everything it should (and nothing it shouldn’t), as shown in Figure 18-5. Notice that there’s no mention of Total in this list, but you can see all of the properties and methods described in Listing 18-1.

Make sure you check the actual content of the DLL against the expectations you have for it.
Fi gure 18-5: Make sure you check the actual content of the DLL against the expectations you have for it.

If you remember from Chapters 16 and 17, the __doc__() function is undefined for an assembly that you import into IronPython, but the help() function does produce a result. One of the next checks you should perform manually is to verify that the assembly provides the kind of information you expect from help. Figure 18-6 shows the output of the help() function for the Accounts class. Notice that it contains all of the information you expect, including the fact that there are two forms of __new__(), the constructor, and the read/write state of the various properties.

Of course, you’ll want to perform other sorts of manual testing that could eventually appear in your test script. For example, you might decide to check whether the Accounts class will let you create an account with a negative starting amount (it will).

It would be also helpful to know whether someone could circumvent some of the properties in the Accounts class. You wouldn’t want someone to use code such as Account2 = Account2 + 20 to overcome the protections in the Deposit property. In this case, the test displays an error. Another check might include adding two accounts together, such as Acccount3 = Account1 + Account2.

By now, you should have the point of using manual testing. You can creatively think of ways that someone might try to overcome protections in your code. It probably isn’t possible to find every avenue of entry into a DLL, but testing in this way helps you think through more potential problems that other forms of testing allow. Interactively probing your code is a unique method of testing the impossible.

Verify that the help() function doesn’t show any surprises about your assembly.
Fi gure 18-6: Verify that the help() function doesn’t show any surprises about your assembly.

Reading and Writing Files Using Storage, Windows Phone Isolated Storage

Using Windows Phone Isolated Storage

XNA potentially supports many storage devices across all the game systems it targets, but on the WP7, there is only one type: isolated storage. Any type of game asset can be imported into the Content Manager through an existing or user-created content importer, and this is the preferred way to read game data that does not change. For data that might change, such as generated data files or user-created game levels, or saved games, we can access the file system to stream data to our game from isolated storage. Any type of data file can be opened and written to or read from using Storage.IO classes, which we will learn about here. Since XML is recognized as a resource file by Content Manager, we will use XML for our example in this hour. XML has the benefit of being versatile and human readable at the same time. But binary and text files can be used as well.

Saving a Data File

We will first learn to create a new file in isolated storage and then read the data back out of the file afterward. The first thing that must be done is to add the library System.Xml.Serialization to the project via the references. The Serialization library makes it very easy to convert a class or struct into a file and read it back again without our having to decode the file manually (by setting individual properties one at a time). Let’s add it to the project.

Adding XML Support to the Project

  1. Right-click References in the Content project and choose Add Reference from the pop-up context menu.
  2. Locate the library called System.Xml.Serialization in the list, as shown in Figure 19.1.

    The System.Xml. Serialization library.
    FIGURE 19.1 The System.Xml. Serialization library.

Now that the reference is set, we can use XML files in the project more easily.

Isolated Storage

To access a file in isolated storage, we have to create a file object using the IsolatedStorageFile class:

[code]
IsolatedStorageFile storage =
IsolatedStorageFile.GetUserStoreForApplication();
[/code]

IsolatedStorageFile.GetUserStoreForApplication() is a rather verbose method that creates the new storage object with linkage to the application’s (or game’s) private storage area, making it available for accessing files, directories, and so on. If the object doesn’t need to be made global to the project, a shorthand declaration can be used:

[code]
var storage = IsolatedStorageFile.GetUserStoreForApplication();
[/code]

Creating a New Directory

Next, a required step must be taken: A directory must be created for the application to store files in. The private or isolated storage area has room for dictionary-style key/value data as well as SQL database tables, so we can’t just toss files in there like one large file system—we have to create a directory. If you don’t create a directory first, an exception error will occur when you try to create a new file. We will use the storage object to create a directory. The IsolatedStorageFile class has a method called DirectoryExists() that returns true if a passed directory exists. CreateDirectory() is used to create a new directory. So, if the directory doesn’t already exist, we want to create it:

[code]
const string directory = “StorageDemo”;
if (!storage.DirectoryExists(directory))
storage.CreateDirectory(directory);
[/code]

Creating a New File

Now, we can create a file inside the directory. First, we have to check to see whether the file exists. WP7 does not support the FileMode.CreateNew option, which is supposed to overwrite a file if it already exists. Trying to do this generates an exception error, even though it works on Windows and Xbox 360. So, we have to delete the file first before creating it again. Usually this is not a problem because savegame data tends to be rather simple for most games. If you are working on a large, complex game, like an RPG, and there’s a lot of data, of course the game might support multiple savegame files, and you’ll have a mini file manager built into the game. But we’re just learning the ropes here, so we’ll do it the simple way to get it working. We use the FileExists() and DeleteFile() methods to get rid of the old save file:

[code]
const string filename = directory + “\savegame.dat”;
if (storage.FileExists(filename))
storage.DeleteFile(filename);
[/code]

Now we’re ready to create a new savegame file and write data to it. This is done with the IsolatedStorageFileStream() class:

[code]
var fstream = new IsolatedStorageFileStream(
filename, FileMode.CreateNew, storage);
[/code]

The FileMode enumeration has these values:

  • CreateNew = 1
  • Create = 2
  • Open = 3
  • OpenOrCreate = 4
  • Truncate = 5
  • Append = 6

Writing Data to the File with Serialization

Although any type of data file can be created, XML is quite easy to use, and an entire class or struct variable (full of game data) can be written to the file with only a couple lines of code. If you want to just write binary or text data to the file, that will work also at this point, but it’s so much easier to use serialization! Here is a simple struct we can use for this example:

[code]
public struct SaveGameData
{
public string Name;
public int Score;
}
[/code]

A new SaveGameData variable is created and the two properties are filled with data. This is where you would store actual game data in the properties in order to restore the game to this gameplay state later when the savegame file is loaded:

[code]
savedata = new SaveGameData();
savedata.Name = “John Doe”;
savedata.Score = rand.Next(500, 5000);
[/code]

Now, to write the data to the file, we have to create an XmlSerializer object, and then write the serialized object out to the file:

[code]
XmlSerializer serializer = new XmlSerializer(typeof(SaveGameData));
serializer.Serialize(fstream, savedata);
[/code]

At this point, the file has been created and data has been written to it that was contained in the savedata struct variable.

Loading a Data File

Loading a serialized XML file is very similar to the writing process. Of course, you may read a simple text or binary file and parse the data if that is more suitable for your needs, but I’m using serialization and XML because it’s so easy and likely to be the approach most game developers take with WP7 savegame data. The same storage object is created, but we don’t need any of the code to create a directory or delete the existing file (obviously), so the code to load the savegame file is much simpler:

[code]
var storage = IsolatedStorageFile.GetUserStoreForApplication();
[/code]

Likewise, the IsolatedStorageFileStream object is created in the same way:

[code]
var fstream = new IsolatedStorageFileStream(
filename, FileMode.CreateNew, storage);
[/code]

There is a second way to create the fstream file object variable: by creating the object in a using statement and then adding code that uses the object in the bracketed code block:
[code]
using (var fstream = new IsolatedStorageFileStream(
filename, FileMode.Open, storage)) { }
[/code]

The XmlSerializer object is created in a similar manner:

[code]
XmlSerializer serializer = new XmlSerializer(typeof(SaveGameData));
[/code]

The only difference really is a call to Deserialize() instead of Serialize(), and this method returns our savegame data as an object:

[code]
data = (SaveGameData)serializer.Deserialize(fstream);
[/code]

Just for curiosity’s sake, here is what the XML file looks like that is created by our code. If you were to serialize a more complex data type, like a Vector4, then the parameters within that class or struct would become sub-items in the XML structure.

[code]
<?xml version=”1.0”?>
<SaveGameData xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:xsd=”http://www.w3.org/2001/XMLSchema”>
<Name>John Doe</Name>
<Score>1245</Score>
</SaveGameData>
[/code]

Creating the Storage Demo Example

We will go over a complete program that demonstrates how to save data to a save game file and then load it again, based on some rudimentary user input. Two buttons are created and displayed using our familiar Button class (which inherits from Sprite). This class requires a bitmap file called button.png, so be sure that it exists in the content project.

To verify that the example is working, we will want to run the program, save the data, close the program, and then rerun it and choose the load option to see that the data is still there. So, the example should read and write the data only when the user chooses to, not automatically. When the emulator is being used, exiting the program still preserves it in memory, but closing the emulator will erase all traces of the program and data files.

Closing the WP7 emulator will wipe the storage memory, including data files created by our example here, and any programs previously loaded from Visual Studio. But closing the program and rerunning it will reveal an intact file system. This happens because the emulator creates a new emulation state system when it is run, and that is not saved when it closes.

Figure 19.2 shows the output of the Storage Demo program.

The Storage Demo example shows how to read and write data.
FIGURE 19.2 The Storage Demo example shows how to read and write data.

Button Class

Just for the sake of clarity, Listing 19.1 shows the source code for the Button class. We have seen the code before, but it is required by the Storage Demo and is included again for clarity.

LISTING 19.1 Source Code for the Button Class

[code]
public class Button : Sprite
{
public string text;
private SpriteBatch p_spriteBatch;
private SpriteFont p_font;
public Button(ContentManager content, SpriteBatch spriteBatch,
SpriteFont font)
: base(content, spriteBatch)
{
p_spriteBatch = spriteBatch;
p_font = font;
Load(“button”);
text = ““;
color = Color.LightGreen;
}
public void Draw()
{
base.Draw();
Vector2 size = p_font.MeasureString(text);
Vector2 pos = position;
pos.X -= size.X / 2;
pos.Y -= size.Y / 2;
p_spriteBatch.DrawString(p_font, text, pos, color);
}
public bool Tapped(Vector2 pos)
{
Rectangle rect = new Rectangle((int)pos.X, (int)pos.Y, 1, 1);
return Boundary().Intersects(rect);
}
}
[/code]

Storage Demo Source

Here in Listing 19.2, we have the source code for the Storage Demo program, with the definition of the SaveGameData class as well.

LISTING 19.2 Source Code for the Storage Demo Program

[code]
public struct SaveGameData
{
public string Name;
public int Score;
}
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
SpriteFont font;
Random rand;
TouchLocation oldTouch;
Button[] buttons;
int current = -1;
bool loaded = false;
SaveGameData savedata;
const string directory = “StorageDemo”;
const string filename = directory + “\savegame.dat”;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
oldTouch = new TouchLocation();
rand = new Random();
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
font = Content.Load<SpriteFont>(“WascoSans”);
//create save button
buttons = new Button[2];
buttons[0] = new Button(Content, spriteBatch, font);
buttons[0].text = “Save”;
buttons[0].position = new Vector2(100, 100);
//create load button
buttons[1] = new Button(Content, spriteBatch, font);
buttons[1].text = “Load”;
buttons[1].position = new Vector2(300, 100);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
//get state of touch input
TouchCollection touchInput = TouchPanel.GetState();
if (touchInput.Count > 0)
{
TouchLocation touch = touchInput[0];
if (touch.State == TouchLocationState.Pressed)
{
current = -1;
int n = 0;
foreach (Button b in buttons)
{
int x = (int)touch.Position.X;
int y = (int)touch.Position.Y;
if (b.Boundary().Contains(x, y))
{
current = n;
break;
}
n++;
}
}
oldTouch = touch;
}
if (current == 0)
{
savedata = new SaveGameData();
savedata.Name = “John Doe”;
savedata.Score = rand.Next(500, 5000);
SaveData(savedata);
loaded = false;
current = -1;
}
else if (current == 1)
{
savedata = LoadData();
loaded = true;
current = -1;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin(SpriteSortMode.FrontToBack,
BlendState.AlphaBlend);
print(0, 0, “Storage Demo”, Color.White);
foreach (Button b in buttons)
b.Draw();
if (loaded)
{
print(100, 200, “Loaded data:nn” +
“Name: “ + savedata.Name + “n” +
“Score: “ + savedata.Score.ToString() + “n”,
Color.White);
}
spriteBatch.End();
base.Draw(gameTime);
}
void print(int x, int y, string text, Color color)
{
var pos = new Vector2((float)x, (float)y);
spriteBatch.DrawString(font, text, pos, color);
}
private void SaveData(SaveGameData data)
{
var storage = IsolatedStorageFile.GetUserStoreForApplication();
//create directory for data
if (!storage.DirectoryExists(directory))
storage.CreateDirectory(directory);
//delete any existing file
if (storage.FileExists(filename))
storage.DeleteFile(filename);
//create new savegame file
using (var fstream = new IsolatedStorageFileStream(filename,
FileMode.CreateNew, storage))
{
XmlSerializer serializer = new XmlSerializer(
typeof(SaveGameData));
serializer.Serialize(fstream, data);
}
}
private SaveGameData LoadData()
{
SaveGameData data;
var storage = IsolatedStorageFile.GetUserStoreForApplication();
using (var fstream = new IsolatedStorageFileStream(filename,
FileMode.Open, storage))
{
XmlSerializer serializer = new XmlSerializer(
typeof(SaveGameData));
data = (SaveGameData)serializer.Deserialize(fstream);
}
return data;
}
}
[/code]

We now have the ability to create a savegame file and load it again! This greatly enhances the replay value of a game that would otherwise appear to have been freshly installed every time it is run. Use this feature to store game settings, player names, and high score lists, as well as generated game levels and anything else that needs to be remembered by the game for the next time.

Working with XML Data

Using the .NET XML Functionality

If you know how to work with XML in .NET languages such as C#, then you already know how to perform the same tasks in IronPython because you can import the System.Xml assembly to gain full access to this functionality. Because the XML capabilities of the .NET Framework are so well defined, using the System.Xml assembly may be all you need to perform tasks within your application. The main issue to consider is how you plan to use your application later. For example, if you plan to move your application to another platform, then using the .NET Framework solution won’t work. In addition, you need to consider data type translation in IronPython. The .NET data types that you use normally are translated into their IronPython counterparts, which could prove confusing for some developers. With these factors in mind, the following sections provide an overview of XML support in the .NET Framework from the IronPython perspective.

Considering the System.Xml Namespace

The System.Xml namespace provides access to the various classes used to interact with XML data. You use these classes to read, write, interpret, edit, build, and otherwise manage XML data. For example, you might use the XmlDeclaration class to begin building an XML data file from scratch when needed. All of these classes depend heavily on standards to ensure the file you create using IronPython is readable by other languages and applications. In fact, the System.Xml namespace supports these standards and specifications.

  • XML 1.0 (including Document Type Definition, DTD, support): http://www.w3.org/ TR/1998/REC-xml-19980210
  • XML Namespaces (both stream level and Document Object Model, DOM): http://www.w3.org/TR/REC-xml-names/
  • XSD Schemas: http://www.w3.org/2001/XMLSchema
  • XPath expressions: http://www.w3.org/TR/xpath
  • XSLT transformations: http://www.w3.org/TR/xslt
  • DOM Level 1 Core: http://www.w3.org/TR/REC-DOM-Level-1/
  • DOM Level 2 Core: http://www.w3.org/TR/DOM-Level-2/

Developing a Basic .NET XML Application

A .NET XML application will follow most of the same principles you use when working with a static language such as C# or Visual Basic.NET. In fact, you might not notice much difference at all except for the obvious structural requirements of a Python application. Consequently, you should find it easy to move your XML code over to IronPython because you really don’t have anything new to worry about. Listing 13-1 shows a simple XML application that creates an XML document, saves it to disk, reads it from disk, and then displays the content onscreen.

DOM-Only Support in the .NET Framework

It’s important to note that the .NET Framework supports DOM and not Simple API for XML (SAX). However, if you want SAX support, you can use the Python modules instead (see the “Working with xml.sax” section of this chapter). XML files include both data and context. In order to reconstruct the original dataset described by an XML file, you need a parser to read the text and then convert it to a usable object. DOM and SAX represent two different methods for interacting with XML documents without forcing the developer to create a parser. If you want more information about the DOM versus SAX approach to parsing XML parsers, check out the information at http://developerlife.com/tutorials/?p=28 and http://www.jamesh.id.au/ articles/libxml-sax/libxml-sax.html. Here’s a summary of the DOM features.

  • Object-based.
  • Object module is created automatically.
  • Element sequencing is preserved.
  • High memory usage.
  • Slow initial data retrieval.
  • Best for complex data structures.
  • In-memory document updates are supported.

SAX takes a completely different approach than DOM. Here’s a summary of the SAX features.

  • Event-based.
  • Object module is created by the application.
  • Element sequencing is ignored in favor of single events.
  • Low memory usage.
  • Fast initial data retrieval.
  • Best for simple data structures.
  • No document updates.

Listing 13-1: Reading and writing an XML document

[code]
# Import clr to add references.
import clr
# Add the required reference.
clr.AddReference(‘System.Xml’)
# Import the System.Xml classes.
from System.Xml import *
# This function creates the document and writes it to disk.
def CreateDocument():
# Create a document.
Doc = XmlDocument()
# Add the XML Declaration.
Declaration = Doc.CreateXmlDeclaration(‘1.0’, ‘utf-8’, ‘yes’)
Doc.AppendChild(Declaration)
# Create the root node.
Root = Doc.CreateNode(XmlNodeType.Element, ‘root’, None)
# Add child elements to the root.
MsgNode = Doc.CreateNode(XmlNodeType.Element, ‘Message’, None)
MsgNode.InnerXml = ‘Hello’
Root.AppendChild(MsgNode)
MsgNode = Doc.CreateNode(XmlNodeType.Element, ‘Message’, None)
MsgNode.InnerXml = ‘Goodbye’
Root.AppendChild(MsgNode)
# Add the root node to the document.
Doc.AppendChild(Root)
# Save the document to disk.
Doc.Save(‘Test.XML’)
def DisplayDocument():
# Create a document.
XMLDoc = XmlDocument()
# Load the XML data.
XMLDoc.Load(‘Test.XML’)
# Process the document.
for Nodes in XMLDoc:
if type(Nodes) == XmlElement:
for MsgNodes in Nodes:
print ‘Message:’, MsgNodes.InnerXml
# Interact with an XML document.
CreateDocument()
DisplayDocument()
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)
[/code]

The code begins by importing clr, which the application uses to add the required reference to System.Xml using the clr.AddReference() method. The code then imports the System.Xml classes.

The example relies on two functions to keep the code simple: CreateDocument(), which creates and saves the document to disk, and DisplayDocument(), which reads the document from disk and displays the content on screen. The example calls each of these functions in turn.

The CreateDocument() function begins by creating an XmlDocument object, Doc. As with any .NET application, Doc doesn’t contain anything when you create it. The first task is to add the XML declarations so that the result is a well-formed XML document using Doc.CreateXmlDeclaration(). Calling Doc.AppendChild() adds the declaration to the document.

Now it’s time to create some content. All XML documents have a root node, which is Root for this example. The code creates Root using Doc.CreateNode() with an XmlNodeType.Element type and ‘root‘ for a name. The example doesn’t work with XML namespaces, so the third argument is set to None.

The most efficient way to create an XML document from scratch is to add all the child nodes to Root before you add Root to the document. The code creates MsgNode using the same technique as for Root. It adds content to MsgNode using the MsgNode.InnerXml property and then adds the node to Root using Root.AppendChild(). The example provides two ‘Message‘ nodes.

At this point, the code adds Root to the document using Doc .AppendChild(). It then saves the document to disk using Doc.Save(). Figure 13-1 shows the typical output from this example when viewed in Notepad (you can use any text editor to view the output because the Doc .Save() method includes spaces and line feeds).

The XML document output looks much as you might expect.
Figure 13-1: The XML document output looks much as you might expect.

The DisplayDocument() function begins by creating a document, XMLDoc, using the XmlDocument class constructor. It then loads the previously created XML document using XMLDoc.Load(). At this point, XMLDoc contains everything the code created earlier and you can easily explore it using the IronPython console.

If you’ve worked with XML documents using C# or Visual Basic.NET, you know that these languages sometimes make it hard to get to the data you really want. IronPython makes things very easy. All you need is a for loop, as shown in the code. Simple if statements make it easy to locate nodes of a particular type, XmlElement in this case.

By the time the code reaches the second for loop, it’s working with the ‘Message‘ elements. The code simply prints the MsgNodes.InnerXml property value to the screen, as shown in Figure 13-2. By now you can see that IronPython makes it incredibly simple to work with XML documents using the .NET Framework approach.

The example outputs the message content in the XML document.
Figure 13-2: The example outputs the message content in the XML document.

Loading and Viewing the XMLUtil Module

The example in this section assumes that you’ve loaded the XMLUtil module from the IronPython Tutorial directory. The following steps show you how to load this module manually so you can see the content.

  1. Open the IronPython console.
  2. Type import sys and press Enter. This command imports the sys module so that you can add the required directory to it.
  3. Type sys.path.append(‘C:/Program Files/IronPython 2.6/Tutorial‘) and press Enter (make sure you change the path information to match the location of your IronPython installation). The XMLUtil.py module exists in the Tutorial directory. Using this module is fine for experimentation, but be sure you copy the XMLUtil.py module to another location for other uses.
  4. Type print sys.path and press Enter. You should see the new path added to the list.
  5. Type dir(XMLUtil) and press Enter. You see the list of methods available in XMLUtil (as shown in Figure 13-3), which includes the Walk() method used in the example.
The Walk() method makes viewing XML data easier.
Figure 13-3: The Walk() method makes viewing XML data easier.

Loading and Viewing the XMLUtil Module

As previously mentioned, the XMLUtil.py file isn’t anything so advanced that you couldn’t put it together yourself, but it’s an interesting module to work with and use. Listing 13-2 shows a short example of how you could use this module in an application.

Listin g 13-2: Walking an XML document using XMLUtil

[code]
# Add the path required to import xmlutil.
import sys
sys.path.append(‘C:/Program Files/IronPython 2.6/Tutorial’)
# Import xmlutil to access the Walk() function.
import xmlutil
# Import clr to add references.
import clr
# Add the required reference.
clr.AddReference(‘System.Xml’)
# Import the System.Xml classes.
from System.Xml import *
# Create a document.
XMLDoc = XmlDocument()
# Load the XML data.
XMLDoc.Load(‘Test.XML’)
# Walk the file contents.
print ‘Contents of Test.XML’
for Node in xmlutil.Walk(XMLDoc):
print ‘nName:’, Node.Name
print ‘Value:’, Node.Value
print ‘InnerXml’, Node.InnerXml
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)
[/code]

The example begins by importing sys, appending the Tutorial folder path, and importing XMLUtil. The code then imports clr, adds a reference to System.Xml, and imports the System.Xml classes. There isn’t anything new about any of this code.

The example makes use of the Text.XML file created in the “Developing a Basic .NET XML Application” section of this chapter. It creates an XmlDocument object, XMLDoc, and loads Text.XML into it using the XMLDoc.Load() method. At this point, you have an XML document that you can walk (go from node-to-node and examine). The XMLUtil.Walk() method can walk any sort of XML document, so you should try it out with other files after you’ve worked with the example for a while.

The next step is to call on XMLUtil.Walk() to walk the XML document for you. The example shows output from the Name, Value, and InnerXml properties. However, you have access to all the properties provided for the various XML data types that the .NET Framework provides. Consequently, you can use XMLUtil.Walk() to display any information needed, or to manage that information. Just because the example displays properties doesn’t mean you have any limitation on how you interact with the output of XMLUtil.Walk(). Figure 13-4 shows the output of this example.

Screen shows the output of the Walk() method for Test.XML.
Figure 13-4: Screen shows the output of the Walk() method for Test.XML.

The XMLUtil.Walk() function is so important because it demonstrates a Python generator (described later in the section when you have the required background). Most languages don’t provide support for generators, so they require a little explanation. The issue at the center of this whole discussion is the variant list. You know that an application will need to process some number of items during run time, but you have no idea of how long this list is or whether the list will exist at all. A producer function is one that outputs values one at a time in response to a request. The producer keeps processing items until it runs out, so the length of the list is no longer a concern (even if the list contains no items at all). Most languages rely on a callback, an address to the requestor, to provide a place to send the producer output. The problem with using a callback is that the code must provide some means of retaining state information to remember previous values. In some cases, using callbacks leads to unnatural, convoluted coding techniques that are hard to write, harder to understand, and nearly impossible to update later.

Developers have a number of alternatives they can use. For example, the developer could simply use a very large list. However, lists require that the developer know what values should appear in the list during design time, and lists can consume large quantities of memory, making them a less than helpful solution in many cases. Another solution is to use an iterator to perform the task. Using an iterator makes it easier to get out of a loop when the processing is finished and eliminates the memory requirements. However, using an iterator shifts the burden of maintaining state information to the producer, complicating an already difficult programming task because the producer may not know anything about the caller. There are other solutions, as well, such as running the requestor and producer on separate threads so that each object can maintain state information without worrying about the potential corruption that occurs when running the code on a single thread. Unfortunately, multithreaded applications can run slowly and require a platform that fully supports multithreading, making your application less portable. In short, most languages don’t provide a good solution to the problem of working with data of variant length.

A generator creates a situation where the producer continuously outputs individual results as in a loop, maintaining its state locally. The requestor actually views the function as a type of iterator, even though the producer isn’t coded to provide an iterator. To accomplish this task, Python provides the yield statement shown in Figure 13-5. The yield statement returns an intermediate result from the producer to the requestor, while the producer continues to process a list of items.

The code in Figure 13-5 begins with the definition of a function named Walk(). This function accepts some kind of XML as input. The first yield statement sends the entire xml input back to the requestor (the example application shown in Listing 13-2). Consequently, you see #document as the Name and the entire XML document as the InnerXml.

The second call to Walk() moves past the first yield statement. Because the second item doesn’t meet the hasattr(xml, “Attributes“) requirement, the code moves onto the loop statement at the bottom of the code listing shown in Figure 13-5. The effect of this loop is to obtain the child elements of the entire document. So the second call to Walk() ends with yield c, which returns the XML declaration element. As a result, you see xml for the Name, version=“1.0“ encoding=“utf-8“ standalone=“yes“ for the Value, and nothing for the InnerXml. This second call ends processing of the XML declaration.

The XMLUtil.Walk() function is interesting because it provides a generator.
Figure 13-5: The XMLUtil.Walk() function is interesting because it provides a generator.

The third call to Walk() begins processing of the root node. It’s interesting to trace through the code in the debugger because you see the for loops in XMLUtil.Walk() used to trace through each element of the input xml as if it were using recursion or perhaps some type of iteration, but the fact is that the code merely combines the for loop with a yield statement to feed each partial result back to the requestor. Using the Python debugger is actually a bit more helpful in this case than using the Visual Studio debugger because the Visual Studio debugger won’t show you the value of xml, child, or c so that you can see the changing values. The example code for this book includes XMLUtilDemo2.py for the purpose of using the Python debugger. Follow these steps to load the debugger so you can trace through the example yourself.

  1. Open the IronPython console.
  2. Type import sys and press Enter. This command imports the sys module so that you can add the required directory to it.
  3. Type sys.path.append(‘C:/Program Files/IronPython 2.6/Tutorial‘) and press Enter (make sure you change the path information to match the location of your IronPython installation).
  4. Type import XMLUtil and press Enter to import the support file (important if you want to see how the generator works).
  5. Type import XMLUtilDemo2 and press Enter to import the source code file.
  6. Type import pdb and press Enter to import the debugger.
  7. Type pdb.run(‘XMLUtilDemo2.main()‘) to start the debugger. At this point, you can single step through the code to see how everything works.

Using the Python Modules

At one point, the Python modules were stable and straightforward to use, but later versions are less stable and, when it comes to IronPython, may be missing required elements completely. Consequently, you might see tutorials such as the one at http://www.boddie.org.uk/python/XML_intro.html and wonder why they don’t work. These tutorials are based on earlier versions of Python and don’t account for the missing CPython elements in IronPython. The following sections describe how to overcome these problems in your application when you use the Python approach to XML file management in IronPython.

Working with xml.dom.minidom

The xml.dom.minidom module is designed to help you work with XML using the DOM approach. However, this module is far from complete in IronPython, partly due to the CPython support required in standard Python. The actual document support is complete, so you won’t have a problem building, editing, and managing XML documents. It’s the write and read support that are lacking.

Fortunately, you can overcome write issues by using a different approach to outputting the document to disk (or other media). Standard Python development practice is to use the xml.dom.ext .PrettyPrint() method, which simply doesn’t exist in IronPython. You get around the problem by performing the task in two steps, rather than one, as shown in Listing 13-3.

The reading problem isn’t as easy to solve. Standard Python development practice is to use the xml .dom.minidom.parse() method. This method does exist in IronPython, but it outputs an error stating

[code]
ImportError: No module named pyexpat
[/code]

This module actually is missing. In order to fix this problem, you must download the pyexpat. py file from https://fepy.svn.sourceforge.net/svnroot/fepy/trunk/lib/. Place this file in your Program FilesIronPython 2.6Lib, not the Program FilesIronPython 2.6Lib xmldom folder as you might think. As shown in Listing 13-3, the standard Python techniques work just fine now.

Listin g 13-3: Managing XML documents using the Python approach

[code]
# Import the required XML support.
import xml.dom.minidom
def CreateDocument():
# Create an XML document.
Doc = xml.dom.minidom.Document()
# Create the root node.
Root = Doc.createElement(‘root’)
# Add the message nodes.
MsgNode = Doc.createElement(‘Message’)
Message = Doc.createTextNode(‘Hello’)
MsgNode.appendChild(Message)
Root.appendChild(MsgNode)
MsgNode = Doc.createElement(‘Message’)
Message = Doc.createTextNode(‘Goodbye’)
MsgNode.appendChild(Message)
Root.appendChild(MsgNode)
# Append the root node to the document.
Doc.appendChild(Root)
# Create the output document.
MyFile = open(‘Test2.XML’, ‘w’)
# Write the output.
MyFile.write(Doc.toprettyxml(encoding=’utf-8’))
# Close the document.
MyFile.close()
def DisplayDocument():
# Read the existing XML document.
XMLDoc = xml.dom.minidom.parse(‘Test2.XML’)
# Print the message node content.
for ThisChild in XMLDoc.getElementsByTagName(‘Message’):
print ‘Message:’, ThisChild.firstChild.toxml().strip(‘nt’)
CreateDocument()
DisplayDocument()
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)
[/code]

The first thing you should notice is that the code for this example is much shorter than its .NET counterpart, even though the result is essentially the same. Despite the problems with the Python libraries, you can write concise code for manipulating XML using Python.

The code begins by importing the only module it needs, xml.dom.minidom. It then calls CreateDocument() and DisplayDocument() in turn, just as the .NET example does. In fact, the output from this example is precisely the same. You see the same output shown in Figure 13-2 when you run this example.

The CreateDocument() function begins by creating an XML document, Doc, using xml.dom .minidom.Document(). The XML document automatically contains the XML declaration, so unlike the .NET version of the code, you don’t need to add it manually. So the first processing task is to create the root node using Doc.createElement(‘root‘).

As with the .NET example, this example creates two MsgNode elements that contain different messages. The technique used is different from the .NET example. Instead of setting an InnerXml property, the code creates an actual text node using Doc.createTextNode(). However, the result is the same, as shown in Figure 13-6. The last step is to add Root to Doc using Doc.appendChild().

A big difference between IronPython and Python is how you write the XML to a file. As previously mentioned, you can’t use the xml.dom.ext.PrettyPrint() method. In this case, the code creates a file, MyFile, using open(). The arguments define the filename and the mode, where ‘w‘ signifies write. In order to write the text to a file, you use a two-step process. First, the code creates formatting XML by calling Doc.toprettyxml(). The function accepts an optional encoding argument, but there isn’t any way to define the resulting XML document as stand-alone using the standalone=“yes“ attribute (see Figure 13-1). Second, the code writes the data to the file buffer using MyFile.write().

The Python output is similar, but not precisely the same as the .NET output.
Figure 13-6: The Python output is similar, but not precisely the same as the .NET output.

[code]
Calling MyFile.write() doesn’t write the data to disk. In order to clear the file buffer, you must call MyFile.close(). Theoretically, IronPython will call MyFile.close() when the application ends, but there isn’t any guarantee of this behavior, so you must specifically call MyFile.close() to ensure there isn’t any data loss.
[/code]

The DisplayDocument() function comes next. Reading an XML document from disk and placing it in a variable is almost too easy when using IronPython. All you need to do is make a single call to xml.dom.minidom.parse(). That’s it! The document is immediately ready for use.

The second step is to display the same output shown in Figure 13-2. Again, all you need in IronPython is a simple for loop, rather than the somewhat lengthy .NET code. In this case, you ask IronPython to retrieve the nodes you want using XMLDoc.getElementsByTagName(). The output is a list that you can process one element at a time. The print statement calls on a complex-looking call sequence.

[code]
ThisChild.firstChild.toxml().strip(‘nt’)
[/code]

However, if you take this call sequence apart, it really isn’t all that hard to understand. Every iteration of the loop places one of the MsgNode elements in ThisChild. The first (and only) child of MsgNode is the Message text node, so you can retrieve it using the firstChild property. The firstChild property contains a DOM Text node object, so you convert it to XML using the toxml() method. Unfortunately, the resulting string contains control characters, so you remove them using the strip(‘nt‘) method. The result is a simple value output.

Working with xml.sax

It’s important to remember that SAX is an event-driven method of working with XML. An application looks at a small number of bits out of an entire document. Consequently, SAX can be a good method for processing larger documents that you can’t read into memory at one time. A SAX application normally relies on three constructs:

  • One or more sources as input
  • A parser (normally, only one is used)
  • One or more handlers to respond to input events

There are many different Python SAX modules. Each of these modules provides different implementations of the three constructions. The default SAX implementation provides just four handlers. These handlers are implemented as classes that you use to interact with the events generated by the input file.

  • ContentHandler: Provides the main SAX interface for handling document events. Most applications use this interface as a minimum because it provides the basic support required for any document. The example shows how to use this handler, which is provided as part of the xml.sax module.
  • DTDHandler: Manages all of the Document Type Definition (DTD) events.
  • EntityResolver: Resolves external entities such as files referenced by processing instructions.
  • ErrorHandler: Reports any errors or warnings that the parser encounters when it processes the XML. Provided as part of the xml.sax module.

Now that you have a little better idea of what to expect, it’s time to look at an actual example. Listing 13-4 shows a simple SAX implementation that includes all of the constructs you normally need. Of course, you can easily add to this example to make it do considerably more than it does now.

Listin g 13-4: Parsing an XML document using SAX

[code]
# Import the required module.
import xml.sax
# Create a handler based on the default ContentHandler class.
class MessageHandler(xml.sax.ContentHandler):
# Contains the message text.
Message = ‘’
# Determines when the content is a message.
IsMessage = False
# Check for the kind of element before processing it.
def startElement(self, name, attrs):
if name == ‘Message’:
self.IsMessage = True
self.Message = ‘’
else:
self.IsMessage = False
# If this is the right kind of element, display the message for it.
def endElement(self, name):
if name == ‘Message’:
print ‘Message:’, self.Message.strip(‘nt’)
# Add each of the characters of the message to the Message variable.
def characters(self, ch):
if self.IsMessage:
self.Message += ch
# Create a parser.
Parser = xml.sax.make_parser()
# Create a handler for the parser and tell the parser to use it.
Handler = MessageHandler()
Parser.setContentHandler(Handler)
# Open a source and parse it using the parser with the custom handler.
Parser.parse(open(‘Test2.XML’))
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)
[/code]

The code begins by importing the required xml.sax module. You don’t need anything fancy to create a basic SAX handler. Remember that SAX processes the file one character at a time and generates events based on the characters that the parser sees. Consequently, the code may seem a little odd for someone who is used to working with complete elements, but SAX gives you fine control over the processing cycle, including locating errors within the file.

The centerpiece of this example is the MessageHandler class. This class includes a variable to hold the message (Message), an indicator of whether an element is a message (IsMessage), and the three methods described in the following list.

  • startElement(): The parser calls this method at the beginning of an element.
  • endElement(): The parser calls this method at the end of an element.
  • characters(): Every character read from the source generates a call to characters().

For this example, the startElement() method checks the element name. If the element is a ‘Message‘ element, then the code sets IsMessage to True and clears Message of any existing content. This is a preparatory step.

When the characters() method sees that IsMessage is True, it appends every character it receives to Message. Remember that these are individual characters, so you can’t assume much about the content except that the flow is from the beginning of the file to the end of it. In other words, you won’t receive characters out of order.

The endElement() checks the element name again. When the element name is ‘Message‘, the code prints the content of Message. Because Message contains all of the characters from the source, you must use strip(‘nt‘) to remove any control characters. The output from this example is the same as shown in Figure 13-2.

Now that you understand the handler, it’s time to see how you put it to work. The main part of the code begins by creating a parser, Parser, using xml.sax.make_parser(). Remember that the parser simply generates events based on the input characters it sees. The handler performs the actual interpretation of those characters.

The next step is to create an instance of MessageHandler named Handler. The code uses Parser .setContentHandler() to assign the handler to Parser. Otherwise, Parser won’t know which handler to use to process the XML characters.

In order to process the XML file, the code still requires a source — the third construct. The open(‘Test2.XML‘) call opens Test2.XML as a source and passes this source to Parser through the Parser.parse() method. It’s the call to the Parser.parse() method that actually begins the process of generating events.

Printing Text

Creating the Font Demo Project

A font in XNA is nothing more than a text file—at least, from the programmer’s point of view. When the project is compiled, XNA uses the text file to create a bitmap font on a memory texture and use that for printing text on the screen.

This is a time-consuming process, which is why the font is created at program startup rather than while it’s running. Let’s create a new project and add a font to it.

Creating a New XNA Project

Follow these steps to create a new XNA project in Visual C# 2010:

  1. Start up Visual Studio 2010 Express for Windows Phone (or whichever edition of Visual Studio 2010 you are using).
  2. Bring up the New Project dialog, shown in Figure 3.1, from either the Start Page or the File menu.

    Creating the Font Demo project.
    FIGURE 3.1 Creating the Font Demo project.
  3. Choose Windows Phone Game (4.0) from the list of project templates.
  4. Type in a name for the new project (the example is called Font Demo).
  5. Choose the location for the project by clicking the Browse button, or by typing the folder name directly.
  6. Click OK to create the new project.

The new project is generated by Visual Studio and should look similar to the project shown in Figure 3.2.

The newly generated Font Demo project.
FIGURE 3.2 The newly generated Font Demo project.

Adding a New Font to the Content Project

At this point, you can go ahead and run the project by pressing F5, but all you will see in the Windows Phone emulator is a blue screen. That is because we haven’t written any code yet to draw anything. Before we can print text on the screen, we have to create a font, which is added to the Content project.

In XNA 4.0, most game assets are added to the Content project within the Solution, where they are compiled or converted into a format that XNA uses. We might use the general term “project” when referring to a Windows Phone game developed with XNA, but there might be more than one project in the Solution. The “main project” will be the one containing source code for a game. Some assets, however, might be located just within the source code project, depending on how the code accesses those assets. Think of the Content project as a container for “managed” assets.

A Visual Studio “Solution” is the overall wrapper or container for a game project, and should not be confused with “projects” that it contains, including the Content project containing game assets (bitmap files, audio files, 3D mesh files, and so on).

In this example, both the Solution and the main project are called “Font Demo,” because Visual Studio uses the same name for both when a new Solution is generated. Now, let’s add a new font to the Content project. Remember that the Content project is where all game assets are located.

  1. Select the Content project in Solution Explorer to highlight it, as shown in Figure 3.3.

    Highlighting the Content project.
    FIGURE 3.3 Highlighting the Content project.
  2. Open the Project menu and choose Add New Item. Optionally, you can right-click the Content project in Solution Explorer (Font DemoContent (Content)) to bring up the context menu, and choose Add, New Item.
  3. The Add New Item dialog, shown in Figure 3.4, appears. Choose Sprite Font from the list. Leave the name as is (SpriteFont1.spritefont).

    Adding a new Sprite Font.
    FIGURE 3.4 Adding a new Sprite Font.

A new .spritefont file has been added to the Content project, as shown in Figure 3.5. Visual Studio opens the new file right away so that you can make any changes you want to the font details. The default font name is Segoe UI Mono, which is a monospaced font. This means each character of the font has the same width (takes up the same amount of horizontal space). Some fonts are proportional, which means each character has a different width (in which case, “W” and “I” are spaced quite differently, for instance).

A new Sprite Font has been added to the Content project.
FIGURE 3.5 A new Sprite Font has been added to the Content project.

The SpriteFont1.spritefont file is just a text file, like a .CS source code file, but it is formatted in the XML (Extensible Markup Language) format. You can experiment with the font options in the .spritefont descriptor file, but usually the only fields you will need to change are FontName and Size. Here is what the font file looks like with all comments removed:

[code]
<?xml version=”1.0” encoding=”utf-8”?>
<XnaContent xmlns:Graphics =
“Microsoft.Xna.Framework.Content.Pipeline.Graphics”>
<Asset Type=”Graphics:FontDescription”>
<FontName>Segoe UI Mono</FontName>
<Size>14</Size>
<Spacing>0</Spacing>
<UseKerning>true</UseKerning>
<Style>Regular</Style>
<CharacterRegions>
<CharacterRegion>
<Start>&#32;</Start>
<End>&#126;</End>
</CharacterRegion>
</CharacterRegions>
</Asset>
</XnaContent>
[/code]

Visual Studio Solution (.sln) and project (.csproj) files also contain XML-formatted information!

Table 3.1 shows the royalty-free fonts included with XNA 4.0. Note that some fonts come with italic and bold versions even though the SpriteFont description also allows for these modifiers.

XNA Fonts

Learning to Use the SpriteFont Class

We can create as many fonts as we want in an XNA project and use them at any time to print text with different styles. For each font you want to use in a project, create a new .spritefont file. The name of the file is used to load the font, as you’ll see next. Even if you want to use the same font style with a different point size, you must create a separate .spritefont file (although we will learn how to scale a font as a rendering option).

Loading the SpriteFont Asset

To use a SpriteFont asset, first add a variable at the top of the program. Let’s go over the steps:

  1. Add a new variable called SpriteFont1. You can give this variable a different name if you want. It is given the same name as the asset here only for illustration, to associate one thing with another.
    [code]
    public class Game1 : Microsoft.Xna.Framework.Game
    {
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    //create new font variable
    SpriteFont SpriteFont1;
    [/code]
  2. Create (instantiate) a new object using the SpriteFont1 variable, and simultaneously load the font with the Content.Load() method. Note the class name in brackets, <SpriteFont>. If you aren’t familiar with template programming, this can look a bit strange. This type of coding makes the code cleaner, because the Content.Load() method has the same call no matter what type of object you tell it to load.
    [code]
    protected override void LoadContent()
    {
    // Create a new SpriteBatch, which can be used to draw textures.
    spriteBatch = new SpriteBatch(GraphicsDevice);
    // TODO: use this.Content to load your game content here
    SpriteFont1 = Content.Load<SpriteFont>(“SpriteFont1”);
    }
    [/code]

If the Content class did not use a templated Load() method, we would need to call a different method for every type of game asset, such as Content.LoadSpriteFont(), Content.LoadTexture2D(), or Content.LoadSoundEffect().

There is another important reason for using a template form of Load() here: We can create our own custom content loader to load our own asset files! XNA is very extendable with this capability. Suppose you want to load a data file saved by your own custom level editor tool. Instead of manually converting the level file into text or XML, which XNA can already read, you could instead just write your own custom content loader, and then load it with code such as this: Content.Load<Level>(“level1”)

The ability to write code like this is powerful, and reflects a concept similar to “late binding.” This means the C# compiler might not know exactly what type of object a particular line of code is referring to at compile time, but the issue is sorted out later while the program is running. That’s not exactly what’s happening here, but it is a similar concept, and the easiest illustration of template programming I can think of.

These are just possibilities. Let’s get back to the SpriteFont code at hand!

Printing Text

Now that we have loaded the .spritefont asset file, and XNA has created a bitmap font in memory after running the code in LoadContent(), the font is available for use. We can use the SpriteFont1 object to print text on the screen using SpriteBatch.DrawString(). Just be sure to always have a matching pair of SpriteBatch.Begin() and SpriteBatch.End() statements around any drawing code.

Here are the steps you may follow to print some text onto the screen using the new font we have created:

  1. Scroll down to the Draw() method in the code listing.
  2. Add the code shown in bold.
    [code]
    protected override void Draw(GameTime gameTime)
    {
    GraphicsDevice.Clear(Color.CornflowerBlue);
    // TODO: Add your drawing code here
    string text = “This is the Segoe UI Mono font”;
    Vector2 position = new Vector2(20, 20);
    spriteBatch.Begin();
    spriteBatch.DrawString(SpriteFont1, text, position, Color.White);
    spriteBatch.End();
    base.Draw(gameTime);
    }
    [/code]

Run the program by pressing F5. The WP7 emulator comes up, as shown in Figure 3.6.

Printing text in the Font Demo program.
FIGURE 3.6 Printing text in the Font Demo program.

The version of SpriteBatch.DrawString() used here is the simplest version of the method, but other overloaded versions of the method are available. An overloaded method is a method such as DrawString() that has two or more different sets of parameters to make it more useful to the programmer. There are actually six versions of DrawString(). Here is an example using the sixth and most complex version. When run, the changes to the text output are dramatic, as shown in Figure 3.7!

[code]
float rotation = MathHelper.ToRadians(15.0f);
Vector2 origin = Vector2.Zero;
Vector2 scale = new Vector2(1.3f, 5.0f);
spriteBatch.DrawString(SpriteFont1, text, position, Color.White,
rotation, origin, scale, SpriteEffects.None, 0.0f);
[/code]

Experimenting with different DrawString() options.
FIGURE 3.7 Experimenting with different DrawString() options.

As you have learned in this hour, the font support in XNA takes a little time to set up, but after a font has been added, some very useful and versatile text printing capabilities are available. We can print text via the SpriteFont.DrawString() method, with many options available such as font scaling and different colors.

Windows Phone Performance Optimization, Fast! to Faster!

Optimizing your game’s performance

Games belong to a class of real-time software. This means that they are not only expected to produce the correct result, but they must also complete this within a fixed time window. In general, game developers shoot for a minimum of displaying 30 frames per second in order to produce smooth, glitch-free animations; and most prefer 60 frames per second. This means that all of the game calculations getting the player input, implementing enemy AI, moving objects, collision detection and handling, and drawing each frame must be completed within 16.7 milliseconds! When you consider that most modern video games have hundreds, or even thousands, of objects that have to be updated and drawn within that time period, it is no wonder that programmers feel they have to optimize every line of code.

However, many XNA programmers are not familiar with the tools and methods for determining when, where, how, or even if, they should optimize their code. The point of this recipe is to help you answer these questions.

Getting ready

The following section will help you to optimize your game’s performances

Design versus implementation

A common response by those who question, or even outright disagree, with the idea that optimizing the code early is a bad idea, is to point out that it is far easier to change software early in its lifecycle than after it has been written. That is, of course, very true. That is why it is important to understand the difference between the design optimization and implementation optimization.

While designing a game (or any software), you must take into account the size and complexity of your game, and select the correct data structures and algorithms that can support it. A simple 2D shooter or a platformer with no more than a hundred objects interacting at any given time can probably get away with a brute force approach for handling movements and collisions. Maintaining a simple list or an array of objects and iterating through it each frame will most likely work fine, and will be very simple to implement and debug.

However, a more complex game world, with perhaps thousands of active objects, will need an efficient method of partitioning the game space to minimize the number of object interaction tests in each frame. Similarly, games requiring detailed enemy AI will need to rely on algorithms that can produce “intelligent” actions as quickly as possible.

There are many resources available that discuss game programming algorithms. Some of them are as follows:

  • The use of quadtrees and octrees for partitioning the game world to minimize collision detection tests
  • The minimax algorithm with alpha-beta pruning for efficiently finding the “best” move in two player strategy games (please check the wiki link for more information at http://en.wikipedia.org/wiki/Alpha-beta_pruning)
  • The A* algorithm for efficient path finding (for more detail about the A* algorithm, please check the wiki link at http://en.wikipedia.org/wiki/A*_search_ algorithm)

The selection of appropriate data structures and algorithms during the design phase has a far greater impact on the eventual performance of your game than any implementation optimization you will make, as your algorithms determine the maximum number of operations your game will have to perform during each frame.

In order to demonstrate this point, imagine that for your first game you write a simple 2D shooter that relies on a brute force approach to collision detection. In every frame, you simply test every active object against every other active object to see if they intersect. As you decide to have only a limited number of enemies active at a time, it works well and easily runs at 60 frames per second.

With that experience under your belt, you now want to write a second game that is far more ambitious. This time you decide to write a Zelda-like adventure game with a large scrolling game board and hundreds of objects moving around it simultaneously. (The Legend of Zelda, an NDS game from Nintendo. You can find out more about this game at: http:// en.wikipedia.org/wiki/The_Legend_of_Zelda.) Using your existing code as a starting point, you get well into the game’s implementation before you discover that the brute force approach that worked very well in your simple game does not work so well in this new game. In fact, you may be measuring screen draws in seconds per frame instead of frames per second!

The reason is that, comparing every object against every other object is what is known as an O(n2) algorithm (for more information on estimating the algorithm time complexity, please see the classic book Introduction to Algorithm second edition, http://www.amazon. com/Introduction-Algorithms-Thomas-H-Cormen/dp/0262033844). That is, the number of operations that have to be performed is related to the square of the number of objects on which you are operating. If you have 10 objects in your game, you only have to perform a hundred tests to see if there are any collisions. If you have a hundred objects, you have to perform ten thousand tests, which may still be possible on a modern PC if each test can be done quickly enough. However, if you have five hundred just five times as many as the last example you will have to perform 250,000 collision tests. Even if each test took only 67 microseconds, you would still be using the entire 16.7 milliseconds frame time (usually at 60 frames per second) just for collision detection. The point is that it does not matter how efficiently you implement that algorithm in a code, its performance will still devolve exponentially with the number of objects in your game, and will therefore be the single greatest limiting factor to the size of your game.

Game runs slow?

Ok, so your game is playable with most of the features you want to be implemented. However, when you test the application, you find that the animation runs like a robot, the character should run, but it is crawling. What is wrong there? You might say, it is about the compiler features, such as the foreach keyword, or ask whether you need to pass the matrices by reference, not by values.

You have two choices: stop there and take a step back or fix it, and start going into each method trying to figure out how to find your way around the problem on a case-by-case basis. Maybe you will even succeed and get the game back into the runnable state that you had it in hours earlier. Maybe you are even lucky enough to have not introduced yet more bugs into the process. However, in all likelihood, you have not fixed the problem and now you have code that does not run any better than when you started, but is harder to understand, harder to debug, and has kludges in it to get around problems that you introduced trying to fix the wrong problem. Your time will be much better spent finding out where your problems are before you try to fix them.

Measuring the running time

A prototype is just a simplified version of software (in this case, your game), that focuses on one particular aspect of it. Prototypes are often used as proofs of concept to show that the software will be able to work as expected. As prototypes don’t have to deal with all of the details that the final software will, they can be written quickly so that, if necessary, different approaches can be evaluated.

Prototypes are frequently used to evaluate user interfaces, so that customers can provide early feedback. This can be useful for game programming too, as if you can implement a working display and control scheme, you may be able to find out what works and doesn’t work before you get too far along in the actual implementation of the game. However, the use of prototypes that we are concerned with here is to determine whether an algorithm is fast enough for the game we want to write. To do that, we will want to benchmark it. Benchmarking is just the process of timing how long an algorithm takes to run.

How to do it…

Fortunately, the .NET framework makes benchmarking very easy by providing the System. Debug.Stopwatch class. The Stopwatch class provides a Start and a Stop method. It keeps track of the total number of clock ticks that occur between calls to Start and Stop. Even better, like a real stopwatch, it keeps a running count of ticks between successive calls to Start and Stop. You can find out how much time has passed by querying its ElapsedTicks or ElapsedMilliseconds properties. A Reset() method lets us reset Stopwatch back to zero.

Now, follow the steps to take advantage of the Stopwatch class:

  1. As a showcase, the following code gives you a general picture on how to use the Stopwatch class for time measuring:
    [code]
    public abstract class Sprite
    {
    public Vector2 Position { get; set; }
    public Color Color { get; set; }
    // Sprite’s collision rectangle in screen coordinates.
    public BoundingRectangle BoundingBox { get; }
    public Sprite(
    string imageName,
    BoundingRectangle boundingBox);
    public virtual void Initialize();
    public virtual void LoadGraphicsContent(
    ContentManager content);
    public virtual void Update(GameTime time);
    public virtual void Draw(SpriteBatch spriteBatch);
    // Tests for collision with another Sprite. If a
    // collision occurs, it calls the Collide method for
    // both Sprites. Returns true if images collide.
    public bool TestCollision(Sprite item);
    // Called when the TestCollision method detects a
    // collision with another Sprite.
    //
    protected virtual void Collide(
    BoundingRectangle overlap,
    Sprite item);
    }
    [/code]
  2. As it is an abstract, it is intended to be used as a parent to other Sprite classes that will implement its behavior, so we will create our own TestSprite class. TestSprite will generate a random starting position, directional movement vector, and speed (in pixels per second), as shown here:
    [code]
    public override void Initialize()
    {
    // Set starting position.
    Position =
    new Vector2(
    random.Next(screenWidth),
    random.Next(screenHeight));
    // Create a random movement vector.
    direction.X = (float)random.NextDouble() * 2 – 1;
    direction.Y = (float)random.NextDouble() * 2 – 1;
    direction.Normalize();
    // Determine random speed in pixels per second.
    speed = (float)random.NextDouble() * 300 + 150;
    }
    [/code]
  3. In each frame, the following code will update its position based on its movement direction, speed, and the amount of time that has elapsed. It will also test to see if it has hit the edge of the screen, and deflect away from it:
    [code]
    public override void Update(GameTime time)
    {
    // Reset color back to white.
    Color = Microsoft.Xna.Framework.Graphics.Color.White;
    // Calculate movement vector.
    Vector2 move =
    (float)time.ElapsedGameTime.TotalSeconds *
    speed * direction;
    // Determine new position.
    UpdatePosition(move);
    }
    private void UpdatePosition(Vector2 move)
    {
    Position += move;
    if ((BoundingBox.Left < 0) ||
    (BoundingBox.Right > screenWidth))
    {
    direction.X = -direction.X;
    Position -= new Vector2(move.X, 0);
    }
    if ((BoundingBox.Top < 0) ||
    (BoundingBox.Bottom > screenHeight))
    {
    direction.Y = -direction.Y;
    Position -= new Vector2(0, move.Y);
    }
    }
    [/code]
  4. We will talk more about collision testing next. For now, we will see what it takes to time just moving our TestSprite around the screen. Inside our game, we will create a TestSprite object and call its Initialize() and LoadGraphicsContent() methods at appropriate places. And we will create SpriteBatch for our game and pass it to Draw(). Now all we need is to use Stopwatch to time it in the Update() method. In order to do this, we will create a couple of helper methods that start and stop Stopwatch, and print the amount of time it takes for each update:
    [code]
    private Stopwatch updateTimer;
    private int updates = 0;
    private int framesPerSecond;
    private void StartTimer()
    {
    updateTimer.Start();
    }
    private void StopTimer()
    {
    updateTimer.Stop();
    updates++;
    // Show the results every five seconds.
    if (updates == 5 * framesPerSecond)
    {
    Debug.WriteLine(
    updates + ” updates took ” +
    updateTimer.ElapsedTicks + ” ticks (” +
    updateTimer.ElapsedMilliseconds +
    ” milliseconds).”);
    int msPerUpdate =
    (int)updateTimer.ElapsedMilliseconds / updates;
    Debug.WriteLine(
    “Each update took ” +
    msPerUpdate + ” milliseconds.”);
    // Reset stopwatch.
    updates = 0;
    updateTimer.Reset();
    }
    }
    [/code]
  5. By putting calls to StartTimer and StopTimer around the calls to our sprite’s Update() method, we will get a report of the average time each call takes:
    [code]
    300 updates took 34931 ticks (9 milliseconds).
    Each update took 0.03 milliseconds.
    300 updates took 24445 ticks (6 milliseconds).
    Each update took 0.02 milliseconds.
    300 updates took 23541 ticks (6 milliseconds).
    Each update took 0.02 milliseconds.
    300 updates took 23583 ticks (6 milliseconds).
    Each update took 0.02 milliseconds.
    300 updates took 23963 ticks (6 milliseconds).
    Each update took 0.02 milliseconds.
    [/code]

How it works…

In step 1, the Initialize(), LoadGraphicsContent(), Update(), and Draw() methods are the standard methods for Windows Phone 7 XNA Game Programming. Additionally, it provides properties for getting and setting the position and color. For collision detection, the Collide() method called by TestCollision() tests for collision with another Sprite BoundingBox values intersect.

In step 3, an actual game may want to determine the actual point of intersection so it could deflect away from that point more realistically. If you need that level of realism, you would probably want to go ahead and implement your strategy here, so you could time it. However, all we are trying to prototype here is a basic update time, so that this version is fine for our needs.

Note that the Update() method does not test for collisions. We don’t want individual sprite testing for collisions because to do so, our Sprite class would have to know about other game objects and we would be severely limiting our design options for collision testing. Any change to our collision-testing algorithm could, and likely would, affect our Sprite class. We want to avoid anything that limits future design changes, so we will give our Sprite class the ability to test for collisions, but require another part of our code to determine what objects should be tested.

In step 6, each call took on average of 20 microseconds (on my development laptop your results will vary). However, notice that the very first set of updates took almost one and a half times as long to run as the others. That is because the first time these methods are called, the JIT compiler compiles the code and our Stopwatch is timing that as well. It is also possible, as this is a fairly small amount of code that is being called repeatedly, that some or all of it may be fitting in the cache, which will increase the speed of later calls.

These show some of the problems with benchmarking code. Another problem is that we are adding some time by using Stopwatch itself. Thus, benchmark times for prototype code can be used as a general guide, but cannot be relied upon for exact values. In fact, exact values of the time it takes for functions to run are very hard to determine. Although intended only to describe quantum phenomena, a variation of the Heisenberg Uncertainty Principle is at play here: the act of measuring something affects the thing being measured.

There’s more…

Now let’s expand our prototype to help us determine whether we can get away with a brute force approach to collision detection.

First, let’s look at the collision handling code that I have already placed in the Collide method. Remember that this gets called, for both sprites, whenever the TestCollision() method determines a collision between two sprites. All it does is set the Sprite’s color to red:

[code]
protected override void Collide(
BoundingRectangle overlap,
Sprite item)
{
// Turn the sprite red to indicate collision.
Color = Color.Red;
}
[/code]

Let’s give this a test by replacing our single TestSprite with an array of TestSprites. Every place we referenced TestSprite in the original code, we now have to loop through the array to handle all of our TestSprites. In order to make this a little easier to manage, we will refactor our original sprite.Update() call in the Update() method into a new UpdateSprites() method that updates every sprite. We will add a new HandleCollisions() method to our game to test for collisions. Finally, we will change the Update() method, so that it only calls StartTimer and StopTimer around the call to HandleCollisions(). The relevant sections look like the following code:
[code]
private TestSprite[] sprites = new TestSprite[10];
protected override void Update(GameTime gameTime)
{
if (Keyboard.GetState().IsKeyDown(Keys.Escape))
{
this.Exit();
}
UpdateSprites(gameTime);
StartTimer();
HandleCollisions();
StopTimer();
base.Update(gameTime);
}
private void UpdateSprites(GameTime gameTime)
{
foreach (Sprite sprite in sprites)
{
sprite.Update(gameTime);
}
}
private void HandleCollisions()
{
// This is brute force approach
for (int i = 0; i < sprites.Length; i++)
{
for (int j = i + 1; j < sprites.Length; j++)
{
sprites[i].TestCollision(sprites[j]);
}
}
}
[/code]

Looking at that, you may wonder why I am not using foreach for the HandleCollisions call. It is simply because with foreach, we have no way of knowing what sprites we already tested. This algorithm tests every sprite against every other sprite exactly once.

What are the results? On my machine, with 10 sprites, I get the following:
[code]
300 updates took 48827 ticks (13 milliseconds).
Each update took 0.04333333 milliseconds.
300 updates took 42466 ticks (11 milliseconds).
Each update took 0.03666667 milliseconds.
300 updates took 42371 ticks (11 milliseconds).
Each update took 0.03666667 milliseconds.
300 updates took 43086 ticks (12 milliseconds).
Each update took 0.04 milliseconds.
300 updates took 43449 ticks (12 milliseconds).
Each update took 0.04 milliseconds.
[/code]

Wow! Handling collisions for 10 sprites takes only twice as long as it did just to move one sprite. How could that be? It is partly due to the overhead of using the Stopwatch class and making method calls, and partly due to the fact that we are measuring very fast operations. Obviously, the closer you get to the resolution of the underlying timer, the more error you get in trying to time things.

Before we go on, notice also that the impact of the JIT compiler during our first set of updates is significantly less. This shows how effective the JIT compilation is and why we don’t need to worry about it affecting the performance of our game. We may take a performance hit the first time a section of code is running, but it is relatively miniscule to our overall performance.

Now let’s see what happens when we increase the number of sprites to 100:

[code]
300 updates took 2079460 ticks (580 milliseconds).
Each update took 1.933333 milliseconds.
300 updates took 2156954 ticks (602 milliseconds).
Each update took 2.006667 milliseconds.
300 updates took 2138909 ticks (597 milliseconds).
Each update took 1.99 milliseconds.
300 updates took 2150696 ticks (600 milliseconds).
Each update took 2 milliseconds.
300 updates took 2169919 ticks (606 milliseconds).
Each update took 2.02 milliseconds.
[/code]

Whether you should be impressed or dismayed depends on how you want to use this collision-handling algorithm. On one hand, averaging 2 milliseconds per frame is still a miniscule part of our 16.7 millisecond frame timing. If you are not planning to have more than a hundred sprites or so, this algorithm will suit your needs perfectly. However, looking at the relative time difference per sprite gives a completely different perspective. It takes us 50 times as long to handle 10 times the number of sprites.

How about when the number is increased to 500? I urge you to run this code, so that you can see the results for yourself!

[code]
300 updates took 28266113 ticks (7896 milliseconds).
Each update took 26.32 milliseconds.
300 updates took 28179606 ticks (7872 milliseconds).
Each update took 26.24 milliseconds.
300 updates took 28291296 ticks (7903 milliseconds).
Each update took 26.34333 milliseconds.
300 updates took 28199114 ticks (7877 milliseconds).
Each update took 26.25667 milliseconds.
300 updates took 28182787 ticks (7873 milliseconds).
Each update took 26.24333 milliseconds.
[/code]

At this time there is no way to hide the dismay. The movement is clearly getting far less than our desired 60 frames per second! In fact, just the HandleCollisions() call alone is taking almost twice our allotted 16.7 milliseconds per frame. Multiplying the number of objects by 5 increased our time by 13! The times are not increasing exactly in quadric, due to overhead, but the rate of increase is clear.

Does this mean we should never consider this algorithm? Hopefully, at this point the answer is obvious. Many games can easily get away with only having an order of a hundred or so objects active at a time, which we have clearly shown can be handled easily. The fact that the algorithm is trivial to implement and maintain makes it a no-brainer for a large number of games.

On the other hand, if you know you will need to have hundreds of objects, you will need another solution. You have two options: optimize this algorithm, or find a new one. Anyone who is experienced with code optimization will see several obvious ways to make both the algorithm and its implementation more efficient.

For starters, most games don’t actually need to test every object against every other object. Taking the Space Invasion game as an example, I don’t need to test invaders for collision with other invaders. In fact, it is almost crazy to do so.

Another obvious optimization is that the Sprite class’s BoundingBox property is adding the sprite’s current screen position to its internal BoundingRectangle every time TestCollision is called, this despite the fact that the position changes only once or twice per frame. TestCollision, on the other hand, is called once for every other sprite in the game.

In addition, the Sprite’s TestCollision code is computing the actual intersection rectangle even though we are not using it here. We could easily save some time by not computing it. However, we give ourselves more flexibility by going ahead and doing it. Remember that this is supposed to be a generic Sprite class that can be used for many games.

These suggestions don’t even get into implementation optimizations, such as always passing our BoundingBoxes by reference instead of value; and providing direct access to member variables instead of accessing them through properties. These are exactly the types of optimizations suggested by many efficiency proponents in the XNA forums. However, these also make the code less readable, harder to debug, and harder to maintain.

As Space Invasion never has more than around 60 objects on the screen at a time, the unoptimized brute force approach works just fine. In addition, that is undoubtedly true for many other games as well. However, what if your game does need more than 100 collidable objects? Should you not make those optimizations so you can handle them?

The answer is… maybe. By making some of these optimizations, we can get this same brute force algorithm to handle 500 objects at a far more reasonable 6.4 milliseconds per frame.

[code]
300 updates took 6682522 ticks (1866 milliseconds).
Each update took 6.22 milliseconds.
300 updates took 7038462 ticks (1966 milliseconds).
Each update took 6.553333 milliseconds.
300 updates took 7023610 ticks (1962 milliseconds).
Each update took 6.54 milliseconds.
300 updates took 6718281 ticks (1876 milliseconds).
Each update took 6.253334 milliseconds.
300 updates took 7136208 ticks (1993 milliseconds).
Each update took 6.643333 milliseconds.
[/code]

That is an impressive improvement and shows how significantly performance can be optimized through these techniques. However, the disadvantages mentioned earlier less maintainable and less flexible code should not be ignored. In addition, even if you do these sorts of implementation optimizations, keep in mind that this algorithm will still degrade exponentially as you add more objects. You may be able to move up from 100 to 500 objects, but it won’t get you to 1000. At some point, you need to recognize that you need a different algorithm to efficiently handle more objects, such as the one that partitions your game space, like quad trees.

Finally, remember that 6.4 milliseconds is still 40 percent of your entire frame time. If you are maintaining on the order of a thousand or more objects at a time, other parts of your code are almost certainly also going to be difficult to manage at a reasonable frame rate. Is optimizing your collision detection the best use of your time? How do you know in advance which ones to optimize? Optimizing all of them as you go will surely take you longer to write, not to mention make your code more difficult to debug and maintain.

If benchmarking shows your algorithm has problems without implementation optimizations, you are probably better off with a different algorithm.

Using the EQATEC Profiler to profile your game’s running time

Profiling your game performance is a significant part of the whole game development process. No matter how efficient the used algorithms are, or how powerful the hardware is, you still need to get sufficiently accurate CPU running time charts for different functions calling in different hardware conditions. Choosing a good profiling tool will help you to find the hot spots which consume most CPU game resources, and lead you to create the most efficient optimization. For Windows Phone, EQATEC is a good choice and in this recipe, you will learn how to use the EQATEC Profiler to profile your Window Phone game.

Getting ready

You can download the EQATEC Profiler from the official company website located at the following URL:

[code]
http://www.eqatec.com/Profiler/
[/code]

The following screenshot shows what website looks like:

EQATEC Profiler from the official company

After clicking on the Download EQATEC Profiler, a new page will let you choose the profiler version; the free version is fine for our needs. After filling out some basic information, the website will send a URL for downloading the profiler to your e-mail address. When you have installed the downloaded profiler, you are ready for profiling your Windows Phone 7 game.

How to do it…

Carry out the following steps:

  1. Run the EQATEC Profiler through the Start menu or through the root directory where the profiler binary application is located. If the profiler runs correctly, you should get the following screen:
    EQATEC Profiler through the Start menu
  2. The Browse button lets you locate the root directory of your Windows Phone 7 XAP file for profiling. When the directory is set, you will choose the XAP file which will be listed in the list box under the App path textbox. The testing XAP file and source code can be found in the bundle file of this chapter. After that, you should click on the Browse button for building the profile description file that processed the number of methods in the designated application and some application metadata.
  3. Then, after selecting the application you want to profile, click on the Run button to start the profiling. When the Run button is clicked, a prompt will pop up asking you about the device to be used for profiling, Windows Phone 7 Device or Windows Phone 7 Emulator. In the example, we chose the emulator as shown in the following screenshot:
    Windows Phone 7 Device or Windows Phone 7 Emulator
  4. Under the Run tab, if you are sure the Windows Phone 7 application is ready, it is time for profiling. The window should look similar to the following screenshot:
    Run tab, if you are sure the Windows Phone 7
  5. Now, click on the yellow Run app button. The profiler will automatically start a Windows Phone 7 emulator and connect to the emulator. Next, it will install the profiled Windows Phone 7 XAP file on the emulator. When this step is done, profiler will start to track and profile the designated application. At this moment, if you want to know the actual time of every method in your application costs, you need to click on the Take snapshot button with a timer symbol under the information list box, and a new snapshot report which includes the running time of every method will be generated. Then, click on the yellow View button once you have chosen the report you want to review.
  6. In the snapshot view window, you will find how many milliseconds every method takes. The windows will look similar to the following screenshot:
    many milliseconds every method

How it works…

The time of every method is listed in the list box:

  • Initialize() method: 693 MS
  • LoadContent() method: 671 MS
  • Draw() method: 122 MS
  • DrawModel() method: 50 MS
  • Update() method: 43 MS

You can find more details in the Details of Individual methods panel. This panel will tell you the percentage of called method costs on time of the caller. In this example, the LoadContent() method consumes 671 MS which occupies 97percent of the Initialize() method total time.

Reducing the game contents’ loading time

As you know, most of the time, before playing a game, a screen for loading game contents will show up with a running progress bar. Without this, you may feel the game is stuck and not responding. If you know that the game is loading and can see its progress, you know that all you have to do is wait. Usually, no one wants to wait too long for playing games, however. It is wasting time and can cause frustration to the user. For better user experiences, the following sections will tell you how to reduce the loading time of game contents.

Making good loading decisions

Often, the first step in reducing loading times is to understand where the current greatest expenses are. Highlighting the frequency and timing of content loading is an effective way to evaluate and adjust loading times, as well as validate that the right content (no more and no less) is being loaded in a given scenario. Consider instrumenting the following:

  • The time required to load an asset; the System.Diagnostics.Stopwatch object can be used for this
  • The frequency with which each asset has been loaded over multiple game levels, across sessions, and so on
  • The frequency with which each asset is freed
  • The average lifetime

Using the XNA content pipeline to reduce file size

Compressing the game contents into an XNB file will make a great file size reduction in building time.

For the XNA framework, assets are shared between PC, Xbox, and Windows Phone 7 platforms, and you can reuse the textures, models, and so on. If a texture is consistently scaled down for display on a Windows Phone 7, consider performing that scaling offline, rather than taking the processing penalty bandwidth and memory overhead when loading the content.

Developers may also want to exploit other texture types, such as PNG, where doing so would not contribute to already compressed assets. For sparse textures, PNG on Windows Phone will typically demonstrate superior compression to a DXT-compressed content that is brought through the XNA content pipeline. In order to use other texture types, the source files must be copied to the output directory and not compiled in the content pipeline.

Note that, while DXT-compressed assets can be used natively by Windows Phone 7 GPUs, many formats including PNG need to be expanded at runtime to a raw format of 32 bits per pixel. This expansion can lead to increased memory overhead compared to DXT compression.

In order to balance the runtime memory footprint of DXT with the loading time footprint of more aggressive compression formats, developers may choose to apply custom compression and runtime decompression to the DXT content (as built by the XNA pipeline into .xnb files), which can lead to a significant reduction in loading times. Developers should balance the loading time considerations with CPU requirements to decode their custom-encoded content, as well as with memory requirements to handle and manipulate the decompressed data. The offline custom compression and runtime title-managed decompression of the DXT content can offer a good balance of reduced size (and thus, reduced loading time) without large runtime memory costs.

Developers can also pack multiple images into a single texture, as demonstrated by the content processor in the spritesheet. We have already discussed in Chapter 4, Heads Up Display (HUD)—Your Phone Game User Interface, that spritesheets avoid DXT power-of-two restrictions imposed by the XNA content processor, and optimize the file loading (replacing many small files with one larger one).

In the realm of sound, if native audio assets from a console title are 48 kHz, consider down sampling them to 44.1 kHz (prior to applying the XNA pipeline’s own compression) for use on the phone. This will realize an immediate 8 percent savings (approximately) on storage and reading bandwidth, as well as mild CPU savings for running at the native sampling rate of the Windows Phone device (44.1 kHz).

Beyond compression, decreasing loading times can focus on data organization that focuses on the efforts of loading the content that is needed to drive to an initial interactive state, rather than preparing all possible loaded data. This is particularly important in avoiding the watchdog timer; a title that loads data for too long prior to drawing to the screen risks being terminated by the system. Developers should also give similar attention to the in-game content loading. Remember that returning to gameplay from interruptions (SMS, phone, app purchase, and so on) invalidates all the previously loaded content.

Evaluating the asynchronous background loading

Even if the game takes a substantial set-up time, there are numerous techniques to getting the user into some kind of interactive state sooner. Anything from a simplified arcade-style loading screen to cut-scenes, trivia, “did you know” facts, and other low-CPU-impact techniques can be leveraged to help smooth the setup and transition from loading to gameplay.

Loading to an initial menu state or a cut-scene, and then continuing to load additional assets in the background would seem to be appropriate strategies for masking loading times from the consumer. However, LoadContent() performs byte copies of each loaded texture asset that uses the XNA content pipeline, generating garbage. Moreover, LoadContent(), overall, will trigger the garbage collection at each megabyte of loaded data. Depending on the actual interactivity of foreground scenes, the potential CPU cost taken by garbage collection may be acceptable; playback of pre-rendered video cut-scenes takes advantage of purpose-built hardware, so the CPU utilization is typically negligible. Similarly, static or intermittently animated menu systems would likely have more success here than attempting to generate the CPU-intensive content rendered in-engine during the background loading.

Considering the custom serialization

Microsoft’s .NET framework provides an easy to use method for serializing data onto disks, using types present in the System.Xml.Serialization namespace. Simplicity always comes with tradeoffs, however; in this case, the tradeoff is the file size. The default serialization schema is verbose. The behavior of the XmlSerializer is trivially easy to change, however, and can result in significant savings in file sizes.

As an example, let’s consider the following class definition:

[code]
public class TestClass
{
public int count;
public float size;
public bool enabled;
public string
LongNameOfAMinorFieldThatDoesntNeedALongNameInTheFile = “test”;
}
[/code]

The preceding class definition, when serialized with the default XmlSerializer, produces the following XML:

[code]
<?xml version=”1.0″?>
<TestClass xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
xmlns:xsd=”http://www.w3.org/2001/XMLSchema”>
<count>0</count>
<size>0</size>
<enabled>false</enabled>
<LongNameOfAMinorFieldThatDoesntNeedALongNameInTheFile>test
</LongNameOfAMinorFieldThatDoesntNeedALongNameInTheFile>
</TestClass>
[/code]

The default behavior of XmlSerializer is to treat each public field or property as an XML element. This generates quite a bit of extra data in the file; this XML file uses 332 bytes on the disk to serialize four fields. With a few simple changes, we can get significantly smaller files from XmlSerializer. Consider the following class declaration:

[code]
public class TestClass2
{
[XmlAttribute(AttributeName=”count”)]
public int count;
[XmlAttribute(AttributeName=”size”)]
public float size;
[XmlAttribute(AttributeName=”enable”)]
public bool enabled;
[XmlAttribute(AttributeName = “longName”)]
public string
LongNameOfAMinorFieldThatDoesntNeedALongNameInTheFile = “test”;
}
[/code]

With XmlAttribute added to properties, the XmlSerializer treats the field as attributes rather than elements, and gives the attributes alternative names. The resulting XML is the following:
[code]
<?xml version=”1.0″?>
<TestClass2 xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:xsd=”http://www.w3.org/2001/XMLSchema” count =”0″ size =”0″
enable =”false” longName =”test” />
[/code]

The serialized file has significantly less wasted text. The file size also shrank to 167 bytes. This is a saving of roughly 50 percent, and a more reasonable file size to serialize four fields. Modifying your serialization code to prefer the XML attributes to XML elements will often result in similar savings. Even if you don’t perform renaming, as we did in this example, you will generally get close to a 50 percent reduction, as every XmlElement has to have a closing tag, while attributes don’t.

Avoid using XmlAttribute for complex types, or for collections of types. The space savings are minimal in these cases, and the resulting file is considerably more difficult to read. For larger amounts of data, consider writing a custom binary serialization code. In all cases, ensure that you time any new code to confirm any realized performance gains over the default Serializer settings.

Improving game performance with garbage collection

Discussing the garbage collector (GC) that runs on Windows Phone 7 devices is helpful for the Windows Phone 7 game developer. Anyone who has programmed in XNA for Windows or Xbox 360 before knows the GC well.

Value types versus reference types

One of the first things you must understand is the difference between value types and reference types. Value types such as int, float, Vector3, Matrix, and struct (this includes nullable types; a nullable type such as BOOL is just a special struct) live on the stack. The GC does not care about the stack. Well, technically, it cares slightly, but only to the extent that the system begins to run low on memory, and you would have to be trying very hard to get enough items on the stack to cause the system to run low on memory. So don’t worry about calling “new Vector3()” or “Matrix.CreateTranslation()” in your methods that run regularly (such as Update and Draw) it is just a stack allocation and it won’t anger the GC.

Classes are an entirely different matter. Classes, arrays (including arrays of value types, for example, int[ ]), collections (List<>, Dictionary<>, and so on.), and strings (yes, strings) are all reference types and they live on the heap. The heap is the GC’s caring. It pays attention to everything that shows up on the heap and to everything that no longer has any business there, but is still hanging around.

Defining a true value checking method

Take a look at the following code listing:

[code]
void CheckForTrue(bool value)
{
string trueText = “The value is true.”;
string falseText = “The value is false.”;
if (value == true)
{
Console.WriteLine(trueText);
}
else
{
Console.WriteLine(falseText);
}
return;
}
[/code]

Every time this method runs, trueText and falseText will both be allocated on the heap and will “go out of scope” when the the method is run. In other words, “gone out of scope” simply means that there are no more references to an object. A string declared with const never goes out of scope, and thus does not matter to GC for all practical purposes. This is also true for any object declared as static readonly, as once it is created it exists forever. However, the same is not true for a normal static, though many might mistakenly assume so. A static object without the readonly keyword applied to it will generally exist for the life of a program. However, if it is ever set to null, then unless there is some other reference to it, it goes out of scope and is subject to garbage collection.

Technically, the GC runs for every 1 MB of heap allocation. Whenever the GC is running, it takes time to comb through the heap and destroy any objects that are no longer in scope. Depending on how many references you have and how complex nesting of objects is, this can take a bit of time. In XNA, the clock is on a fixed time-step by default and in Windows Phone 7, the default frame rate is 30 FPS. This means that there are 33.3333333 milliseconds available for Update() and Draw() methods to finish their CPU-side tasks. Draw prepares things on the CPU-side, then hands over the actual drawing to the GPU which, being a separate processor, does not usually affect the Update/Draw side of things, except for stalls, but those are beyond the scope of this book and most people will never run into them anyway. If they finish ahead of time, the CPU hangs out and waits until it is time to run Update() again. If not, then the system takes notice that it is running behind and will skip as many draws as necessary to catch back up.

This is where the GC comes in. Normally, your code will complete just fine within 33.33 milliseconds, thereby maintaining a nice even 30 FPS (if your code does not normally complete within that time, you will see serious constant performance problems that may even cause your game to crash after a little while if XNA gets so far behind that it throws up its hands and surrenders). However, when the GC runs, it eats into that time. If you have kept the heap nice and simple, the GC will run nice and fast and this likely won’t matter. However, keeping a simple heap that the GC can run through quickly is a difficult programming task that requires a lot of planning and/or rewriting, and even then is not fool proof (sometimes, you just have a lot of stuff on the heap in a complex game with many assets). A much simpler option assuming you can do it is to limit or even eliminate all allocations during gameplay. You will obviously be allocating heap memory when you first start the game (for example, when loading assets in the LoadContent() method), and you will be allocating memory when loading levels, if you have a game with levels and decide to load each one in an interstitial screen. You will also be allocating memory when changing game screens. However, a small stutter from a couple of dropped frames in between levels or while switching screens is not a big concern the player is not going to accidentally fall off a cliff or get hit by an enemy projectile or anything when those things are happening. In fact, sometimes it makes a lot of sense to intentionally trigger the GC right before the game is going to (re)start. Triggering the GC resets the 1 MB counter and can prevent situations where the counter is at .94 MB when the level begins, such that even a small number of minimal allocations that would otherwise be perfectly acceptable, can cause problems.

Therefore, the goal is to minimize heap allocations. How do we do that? Well, the biggest contributors are needlessly creating new objects in your Update or Draw cycle and boxing value types. First, a quick note on boxing; the simplest example of boxing is casting a value type like int or enum to object in order to pass it as a state. Boxing is a great feature of .NET, but not recommended for game programming because of the heap allocations that can trigger the GC. So keep an eye out for it and try not to do it.

Another big contributor is creating new reference types. Every new instance of an object causes a heap allocation and increases that counter ever so slightly. There are several coding practices that will help you to eliminate needless heap allocation and increase performance for your game.

Using StringBuilder for string operations

Make any strings that never change into const strings.

Where you need strings that change, consider using System.Text.StringBuilder (visit http://msdn.microsoft.com/en-us/library/system.text. stringbuilder.aspx for more information on StringBuilder). All XNA methods that take a string (for example, SpriteBatch.DrawString) will also take a StringBuilder object. Make sure to use one of the constructors which take a default capacity and set it to a value high enough to hold as many characters as you plan, plus a few extra for good measure. If the internal array is large, it will never have to resize itself, and thus will never generate any heap allocations after it is created!

Drawing integer in string without garbage

If you need to draw an int value, such as a score or the number of lives a player has, consider using the following block of code (thanks to Stephen Styrchak):

[code]
public static class SpriteBatchExtensions
{
private static string[] digits = { “0”, “1”, “2”, “3”, “4”, “5”,
“6”, “7”, “8”, “9” };
private static string[] charBuffer = new string[10];
private static float[] xposBuffer = new float[10];
private static readonly string minValue =
Int32.MinValue.ToString(CultureInfo.InvariantCulture);
// Extension method for SpriteBatch that draws an integer
// without allocating any memory. This function avoids garbage
// collections that are normally caused by calling
// Int32.ToString or String.Format. the equivalent of calling
// spriteFont.MeasureString on
// value.ToString(CultureInfo.InvariantCulture).
public static Vector2 DrawInt32(this SpriteBatch spriteBatch,
SpriteFont spriteFont, int value,
Vector2 position, Color color)
{
Vector2 nextPosition = position;
if (value == Int32.MinValue)
{
nextPosition.X = nextPosition.X +
spriteFont.MeasureString(minValue).X;
spriteBatch.DrawString(spriteFont, minValue, position,
color);
position = nextPosition;
}
else
{
if (value < 0)
{
nextPosition.X = nextPosition.X +
spriteFont.MeasureString(“-“).X;
spriteBatch.DrawString(spriteFont, “-“, position,
color);
value = -value;
position = nextPosition;
}
int index = 0;
do
{
int modulus = value % 10;
value = value / 10;
charBuffer[index] = digits[modulus];
xposBuffer[index] = spriteFont.MeasureString
(digits[modulus]).X;
index += 1;
}
while (value > 0);
for (int i = index – 1; i >= 0; –i)
{
nextPosition.X = nextPosition.X + xposBuffer[i];
spriteBatch.DrawString(spriteFont, charBuffer[i],
position, color);
position = nextPosition;
}
}
return position;
}
}
[/code]

Taking advantage of the list for sprites

If you have a Sprites class, for example, create an object pool to reuse it rather than letting it fall out of scope and creating a new one each time one ceases to exist in the game and each time you need a new one. As an example, create a generic List<> of your Sprites class (refer http://msdn.microsoft.com/en-us/library/6sh2ey19. aspx for more information on lists). Use the List<> constructor overload that takes a default capacity and make sure to set it to a value high enough to contain all the objects of that sort and will exist at one time in your game (for example, 300). Then, use a for loop to go through and create all of the objects in the list up to the capacity. Add a public bool IsAlive { get; set; } property to your class to keep track of which ones are being used at any particular time. When you need a new one, loop through the list until you find one, where IsAlive is false. Take that one, set IsAlive to true, set the other properties (such as its position, direction, and so on.) to their appropriate values, and continue. When doing collision detection, loop through using a for or a foreach loop and process only the objects for which IsAlive is true. The same approach should be followed for updating and drawing them. Whenever one is no longer needed (for example, when it collides with something or it goes off screen), simply set its IsAlive to false and it will now be available for reuse without any memory allocation. If you want to be creative, you can expand on this further in several different ways. You could keep a count of the number of live objects, so that once you have processed that number in your update and draw methods, you can use the break keyword to get out of the loop early, rather than go all the way to the end. Alternatively, you could keep two lists: one for storing live objects and one for dead objects, and move objects between the two lists as appropriate.

Preferring struct rather than class when just an instance is needed

If you do want to create something, you can just create a new “instance” of each Update() or Draw() method. Try creating a struct instead of a class. Structures can perform most of the things that classes can (the major limitation being that they cannot inherit from another structure, a class, or anything else, but they can implement interfaces). Moreover, structures live on the stack and not on the heap, so unless you have a reference type, like a string or a class, as a field or property of the structure, you will not generate any trash using a structure. Remember, though, that an array of structures is a reference type (as are all arrays), and thus lives on the heap and counts towards the GC trigger limit whenever created.

Avoiding use of LINQ in game developing

Don’t use LINQ. It looks cool. It makes your code shorter, simpler, and perhaps even easier to read. However, LINQ queries can easily become a big source of trash. They are fine in your startup code, as you are going to generate trash there anyway, just by loading assets and preparing game resources. However, don’t use it in Update(), Draw(), or any other method that gets called during the gameplay.

Minimizing the use of ToString()

Minimize use of ToString(). At a minimum, it creates a string, which lives on the heap (refer to the Drawing integer in string without garbage section discussed earlier in this chapter). If you do need to use ToString(), try to limit how often it is called. If the string only changes every level, then generate it only once at the beginning of the level. If it only changes when a certain value changes, then generate it only when that value changes. Any limits you can set are worth it. The amount of time it takes to check a Boolean condition is so small as to be almost non-existent. You could probably fit tens and even hundreds of thousands of true/false checks for the time it takes the GC to run on a complex heap.

Windwos Phone Content Processing

The architecture and flow of content processing

Content processing is a special module in Windows Phone 7 Game Programming. It provides the flexibility for different types of game content including images, effects, and models. Besides this, the most powerful aspect of content processing is the extension to customize your game with different types of contents. In a commercial game, the customized game contents are especially relevant. Mastering the technique of dealing with any kind of game content is the key ability to master in your Windows Phone 7 Game Programming life. In this section, you will learn the basics of XNA Content Processing and, for example, we will process the text as game contents.

As a key part of the XNA game framework, the content processing pipeline transforms the original art or information file to a format that can be read and used in the Windows Phone 7 XNA framework. Actually, you don’t need to rewrite the importer for some common game content file formats, such as .FBX, .PNG, .fx, and so on. The XNA team has done all the hard work for you, saving you time on learning and researching these formats. Usually, all the built-in content processing components are enough for you to write games. However, if you want to use a new file format that XNA does not support, the best method is to develop your own importer and processor to get the file data. Before doing the job, it is better to have a thorough understanding of how the Window Phone 7 XNA Content Pipeline works, which will help you to fix some weird and unexpected content processing bugs. Believe me, it is true!

The content processing pipeline generally has the following two phases:

  • The reading phase: In this phase, the asset file is read from the local disk, the file is then processed, and the process data is stored in a binary form as exported file (.xnb).
  • The loading phase: In this phase, the exported data is loaded from the .xnb binary file into the XNA game application memory. This loading operation is completed manually by the user.

By putting the importing and processing operations in the compile phase, the game contents loading time is reduced. When the actual game reads the exported file at runtime, it only needs to get the processed data from the binary file. Another convenience is that the exported file could be used on different platforms, such as Windows, Xbox, and Windows Phone 7.

The following diagram shows the main content processing flow in XNA:

main content processing flow in XNAThe reading phase

The importer converts the game assets into objects that the standard content processor can recognize, or other specific customized processor could deal with. An importer typically converts the content into managed objects and includes strong typing for such assets as meshes, vertices, and materials. In addition, a custom importer may produce custom objects for a particular custom content processor to consume.

The job of the content processor is to transform the imported game assets and compile them into a managed code object that can be loaded and used by XNA games on Windows, Xbox, or Windows Phone. The managed code object is the .xnb file. Different processors deal with different game asset types.

If the imported file format is recognized by the Windows Phone 7 XNA built-in importers, the corresponding processor will be assigned to it automatically. Otherwise, if the content is not recognized properly, you should manually set the specific importer and processor in the file property panel (don’t worry about how to do this just now, as you will learn this later in the chapter). After that, when you build your game, the assigned importer and processor for each asset will build the recognized assets to binary XNB files which can be loaded at runtime.

The loading phase

In the reading phase, the game contents are serialized in a binary file, they are ready for use in your game, and then in the loading phase, you can load the processed file in your game by the content loader. When the game needs the game assets, it must call the ContentManager.Load() method to invoke the content loader, specifying the expected object type. The content loader then locates and loads the asset data from the .xnb file into the game memory.

Content Document Object Model

When importing the game asset to your Windows Phone 7 XNA game project, the XNA framework will transform the data to a built-in type system named Content Document Object Model (DOM) which represents the set of classes for standard content processing. Additionally, if the importer and processor are customized, the importer will return ContentItem with the information you specified, or a predefined data type. The following diagram is the main route of the model content processing pipeline specified by Microsoft:

Content Document Object Model (DOM)

This is how it works:

  1. The first step is to import the original model game asset into the game project, where
    each corresponding content importer converts the input model data into an XNA
    Document Object Model (DOM) format.
  2. The output of the model importers is a root NodeContent object, which describes a graphics type that has its own coordinate system and can have children. The classes: BoneContent, MeshContent, GeometryContent, VertexContent, and IndexCollection are all inherited from the NodeContent class. As a result, the root NodeContent object output from a model importer might have some NodeContent, MeshContent, and BoneContent children. Moreover, the NodeContent.Children property represents the hierarchy of the model.
  3. After the models have been imported, they can be processed by their corresponding model processors, or also by your processor. This separation allows you to define two importers for importing X and FBX files, but use the same model processor to process their output. ModelProcessor receives the root NodeContent object as a parameter, generated by the model importer, and returns a ModelContent object.
  4. The exported ModelContent model data has a Tag property which is an object, containing vertex and bone data, but no animation data.
  5. At the end of the reading phase, this processed data needs to be stored in an XNB binary file. To be able to store the ModelContent object in an XNB file, ModelContent and each object inside it must have its own ContentTypeWriter. ContentTypeWriter defines how the data of each object is written into the binary XNB file.
  6. During the reading phase, at runtime, ContentManager reads the binary XNB file and uses the correct ContentTypeReader for each object it finds in the XNB file.

    As the XNA Content Pipeline does not have full support for models with skeletal animation, you need to extend the content pipeline, adding support for skeletal animation.

Creating a custom importer and processor for your text

Most of the time, the standard content importers and processors are enough for Windows Phone Game Development. However, sometimes you need to process a different type of file that the XNA framework does not support. At present, you will need, a custom importer and processor. In this recipe, you will learn how to write your own importer and processor for text in Windows Phone 7 XNA.

Getting ready

In order to read the specific game asset, first you should develop a corresponding importer and processor. Then, write the processed data to an XNB file with the override Write() method of ContentTypeWriter class in the content pipeline project. After that, override the Read() method of ContentTypeWriter class in a separate class in the main game project. In this example, you will write a text importer and read the data from the compiled text in an XNB file into your game.

How to do it…

Carry out the following steps to create a custom importer and processor:

  1. Create a Windows Phone Game project named TextContentReader and a Content Pipeline Extension Library project named TextImporterProcessor with a clicking sequence: File | New | Project | XNA Game Studio 4.0 | Content Pipeline Extension Library. When the content pipeline project is created, we add five new files into it: Text.cs, TextImporter.cs, TextOutput.cs, TextProcessor.cs, and TextWriter.cs.
  2. In this step, we define the TextInput class and the TextOutput class. The TextInput class in the TextInput.cs file looks as follows:
    [code]
    // TextInput class for importer and processor
    public class TextInput
    {
    // Text string
    string text;
    // Constructor
    public TextInput(string text)
    {
    this.text = text;
    }
    // Text property
    public string Text
    {
    get
    {
    return text;
    }
    }
    }
    [/code]
  3. The other class TextOutput in the TextOutput.cs file is:
    [code]
    // TextOutput class for processor and ContentTypeWriter and
    // ContentTypeReader
    public class TextOutput
    {
    // Text byte array
    byte[] textOutput;
    public TextOutput(byte[] textOutput)
    {
    this.textOutput = textOutput;
    }
    // Compiled text property
    public byte[] CompiledText
    {
    get
    {
    return textOutput;
    }
    }
    }
    [/code]
  4. Create the TextImporter class in the TextImporter.cs file as follows:
    [code]
    // Text importer
    [ContentImporter(“.txt”, DefaultProcessor = “TextImporter”,
    DisplayName = “TextImporter”)]
    public class TextImporter : ContentImporter<TextInput>
    {
    // Override the Import() method
    public override TextInput Import
    (string filename, ContentImporterContext context)
    {
    // Read the text data from .txt file
    string text = System.IO.File.ReadAllText(filename);
    // Return a new TextInput object
    return new TextInput(text);
    }
    }
    [/code]
  5. Create a TextProcessor class that uses TextInput from TextImporter as an input and outputs a TextOutput object. Define the class in the TextProcessor. cs file as follows:
    [code]
    // Text content processor
    [ContentProcessor(DisplayName = “TextProcessor”)]
    public class TextProcessor :
    ContentProcessor<TextInput, TextOutput>
    {
    // Override the Process() method
    public override TextOutput Process
    (TextInput input, ContentProcessorContext context)
    {
    // Return the TextOutput object
    return new
    TextOutput(Encoding.UTF8.GetBytes(input.Text));
    }
    }
    [/code]
  6. The last step in the loading phase is to save the TextOutput data into an XNB file. In this step, we will create the TextWriter class in TextWriter.cs to define the behavior of how to write the TextOutput data into an XNB fie. The implementation of TextWriter is as follows:
    [code]
    // TextWriter for writing the text information into XNB
    // file
    [ContentTypeWriter]
    public class TextWriter : ContentTypeWriter<TextOutput>
    {
    // Override the Write() method
    protected override void Write
    (ContentWriter output, TextOutput value)
    {
    // Write the text length information to XNB file
    output.Write(value.CompiledText.Length);
    // Write the text string into XNB file
    output.Write(value.CompiledText);
    }
    public override string GetRuntimeType
    (TargetPlatform targetPlatform)
    {
    // Get the run time type of TextOutput
    return typeof(TextOutput).AssemblyQualifiedName;
    }
    public override string GetRuntimeReader
    (TargetPlatform targetPlatform)
    {
    // Get the TextOutput assembly information
    return “TextContentReader.TextReader,
    TextContentReader,” +
    ” Version=1.0.0.0, Culture=neutral”;
    }
    }
    [/code]
  7. In the loading phase, the first step is to define the output type of ContentTypeReader. For TextReader, the output type is the TextOutput class that came with it in the TextImporterProcessor project. Now we add TextOutput.cs and TextReader.cs into the main game project TextContentReader. For TextOutput.cs, we should change the namespace from TextImporterProcessor to TextContentReader, and the rest remains the same. Then, we define the TextReader class in TextReader.cs as follows:
    [code]
    public class TextReader : ContentTypeReader<TextOutput>
    {
    // Read the TextOutput data from xnb file
    protected override TextOutput Read
    (ContentReader input, TextOutput existingInstance)
    {
    // Read the text length
    int size = input.ReadInt32();
    // Read the text content
    byte[] bytes = input.ReadBytes(size);
    // Generate a TextOutput object with the already
    // read text.
    return new TextOutput(bytes);
    }
    }
    [/code]
  8. Load the text of the XNB file into the main game and render the content on the screen. We add gameSprite.spritefont into the content project and insert the TextImporterProcessor DLL into the content project reference. In the content project, right-click on the TextContent.txt file and choose the Properties from the pop-up menu. We then choose the text corresponding to the importer and processor in the property panel, as shown in the following screenshot:
    the TextContent.txt file
  9. Now, insert the following lines into the TextContentReader class field, as follows:
    [code]
    SpriteFont font;
    string str;
    [/code]
  10. Then, add the following code into the LoadContent() method as follows:
    [code]
    font = Content.Load<SpriteFont>(“gameFont”);
    TextOutput text = Content.Load<TextOutput>(“TextContent”);
    str = System.Text.Encoding.UTF8.GetString(
    text.CompiledText, 0, text.CompiledText.Length);
    [/code]
  11. The last part is to render the text content on the screen, so add the following code in the Draw() method as follows:
    [code]
    spriteBatch.Begin();
    spriteBatch.DrawString(font, str, new Vector2(0, 0), Color.
    White);
    spriteBatch.End();
    [/code]
  12. Now build and run the Windows Phone 7 XNA game, making sure that the Copy to Output Directory in the Property panel of the TextContent.txt text file in the content project has been set to Copy always. The application will run as shown in the following screenshot:
    Copy to Output Directory in the Property panel of the TextContent.txt

How it works…

In step 2, the TextInput class will be used to load the text data from TextImporter and send the data to TextProcessor as the input.

In step 3, the TextOutput class stores the text information compiled by TextProcessor as the output text data. All the string data is stored in TextOutput as byte array.

In step 4, before the TextImporter class, we add a ContentImporter attribute to provide some properties for the importer. The first parameter defines the file extension that the importer could recognize when the game asset is imported; the DefaultProcessor argument instructs the XNA system which processor will be the default processor corresponding to the importer. The last argument DisplayName specifies the display name when you have chosen the importer for your asset. The following is the ContentImporter attribute that TextImporter must inherit from the ContentImporter class and override the Import() method. The importer reads a text file containing characters and generates the original file to TextInput object. From XNA SDK:

When the game is built, the ContentImporter.Import function is called once for each XNA content item in the current project.

When invoked against an input file in the appropriate format, a custom importer is expected to parse the file and produce as output one or more content objects of appropriate types. Since an importer’s output is passed directly to a Content Pipeline processor, each type that an importer generates must have at least one processor available that can accept it as input.

In step 5, for the TextProcessor class, we declare another class attribute named ContentProcessor. DisplayName shows the display name when it is chosen for a specific game asset. As the Process() method is the default method in ContentProcessor, as a subclass, TextProcessor should override the Process() method to handle the TextInput data from TextImporter. Here, we use the Encoding. UTF8.GetBytes() to transform the text string into a byte array and generate the TextOutput object. The generated TextOutput object is the intermediate data for ContentTypeWriter to write to an XNB file.

In step 6, the TypeWriter class is derived from the ContentTypeWriter class and overrides the Write(), GetRuntimeType(), and GetRuntimeReader() methods. The Write() method performs the key operation of writing the text information into an XNB file. Notice that the text information writing order must comply with the data when read by TextReader at runtime. The GetRuntimeType() method identifies the type of an object your game should load from the XNB file written by the writer object. In this instance, the XNB file contains the binary array from your custom TextOutput type. The GetRuntimeReader() method specifies what reader should be invoked to load the XNB file in your game. It returns the namespace and name of the reader class, followed by the name of the assembly in which that class is physically located. In general, the assembly string of ContentTypeReader should be:

[code]
GameNamespace.ContentReaderClassName, ContentReaderClassName,
Version=x.x.x.x, Culture=neutral
[/code]

In step 7, TextReader is the subclass of ContentTypeReader and overrides the Read() method to read the XNB file generated in the reading phase. The reading order is the same as the data writing order. Essentially, what we are going to do here is load all the serialized bits of the XNB file. Finally, the method returns a new TextOutput object to the ContentManager.Load() method.

Processing XML files

In game development, the XML file often plays the role of describing the whole game world, configuring the basic game settings or defining the object properties in the game scenario. For processing an XML file, the .NET framework supports several methods. In Windows Phone 7 XNA programming, if XML is a part of the game content, you can process the file in a way to suit your requirements. At present, the technique of writing the XML file importer and processor will help you. In the following recipe, you will learn this useful technique, and it is easy to extend for your own game.

How to do it…

The following steps will lead you to create the content importer and process for XML files:

  1. Create a Windows Phone Game project named XMLReader. Change Game1.cs to XMLReaderGame.cs. Then, add a Content Pipeline Extension Library project in the solution named XMLImporterProcessor. Add Person.cs, PersonWriter.cs, PersonXMLInput.cs, XMLImporter.cs, and XMLProcessor.cs files into the project. In the content project, add the PersonInfo.xml file. The XML file looks as follows:

    [code]
    <?xml version=”1.0″ encoding=”utf-8″ ?>
    <Person>
    <Name>Steven</Name>
    <Age>24</Age>
    <Gender>Male</Gender>
    </Person>
    [/code]

  2. Create the PersonXMLInput class in PersonXMLInput.cs as follows:
    [code]
    [XmlRoot(“Person”)]
    public class PersonXMLInput
    {
    [XmlElement(“Name”)]
    public string Name;
    [XmlElement(“Age”)]
    public int Age;
    [XmlElement(“Gender”)]
    public string Gender;
    }
    [/code]
  3. Then, build the Person class in Person.cs as follows:
    [code]
    public class Person
    {
    public string Name;
    public int Age;
    public string Gender;
    public Person(string name, int age, string gender)
    {
    this.Name = name;
    this.Age = age;
    this.Gender = gender;
    }
    }
    [/code]
  4. Create the XMLImporter class in XMLImporter.cs as follows:
    [code]
    [ContentImporter(“.xml”, DisplayName=”XMLImporter”,
    DefaultProcessor=”XMLProcessor”)]
    public class XMLImporter : ContentImporter<PersonXMLInput>
    {
    public override PersonXMLInput Import
    (string filename, ContentImporterContext context)
    {
    PersonXMLInput personXMLInput = new PersonXMLInput();
    // Create an XML reader for XML file
    using (System.Xml.XmlReader reader =
    System.Xml.XmlReader.Create(filename))
    {
    // Create an XMLSerializer for the AnimationSet
    XmlSerializer serializer = new
    XmlSerializer(typeof(PersonXMLInput));
    // Deserialize the PersonXMLInput from the
    // XmlReader to the PersonXMLInput object
    personXMLInput =
    (PersonXMLInput)serializer.Deserialize(reader);
    }
    return personXMLInput;
    }
    }
    [/code]
  5. Implement XMLProcessor in XMLProcessor.cs as follows:
    [code]
    [ContentProcessor(DisplayName=”XMLProcessor”)]
    public class XMLProcessor :
    ContentProcessor<PersonXMLInput, Person>
    {
    public override Person Process(PersonXMLInput input,
    ContentProcessorContext context)
    {
    return new Person(input.Name, input.Age, input.Gender);
    }
    }
    [/code]
  6. Define the PersonWriter class for writing the importer XML person information into an XNB file:
    [code]
    [ContentTypeWriter]
    class PersonWriter : ContentTypeWriter<Person>
    {
    // Override the Write() method
    protected override void Write(ContentWriter output,
    Person value)
    {
    output.Write(value.Name);
    output.Write(value.Age);
    output.Write(value.Gender);
    }
    public override string GetRuntimeType
    (TargetPlatform targetPlatform)
    {
    // Get the run time type of TextOutput
    return typeof(Person).AssemblyQualifiedName;
    }
    public override string GetRuntimeReader
    (TargetPlatform targetPlatform)
    {
    // Get the PersonReader assembly information
    return “XMLReader.PersonReader, XMLReader,” +
    ” Version=1.0.0.0, Culture=neutral”;
    }
    }
    [/code]
  7. Load the person information from the XNB file. We should add PersonReader.cs and Person.cs in the main game project. The only difference between Person.cs here and in the XMLImporterProcessor project is that the namespace here is XMLReader. The Person class will be the storage for the person information from the XNB file when loaded by the content manager. The PersonReader should be:
    [code]
    public class PersonReader : ContentTypeReader<Person>
    {
    protected override Person Read
    (ContentReader input, Person existingInstance)
    {
    return new Person(input.ReadString(),
    input.ReadInt32(), input.ReadString());
    }
    }
    [/code]
  8. Read the XML file in the game. First, we add the following lines into the class field:
    [code]
    SpriteFont font;
    Person person;
    string textPersonInfo;
    [/code]
  9. Then, load the PersonInfo.xml in LoadContent() as follows::
    [code]
    person = Content.Load<Person>(“PersonInfo”);
    font = Content.Load<SpriteFont>(“gameFont”);
    textPersonInfo = “Name: ” + person.Name + “n” + “Age: ” +
    person.Age + “n” + person.Gender;
    [/code]
  10. Draw the person information on the screen.
    [code]
    spriteBatch.Begin();
    spriteBatch.DrawString(font, textPersonInfo,
    new Vector2(0, 0), Color.White);
    spriteBatch.End();
    [/code]
  11. Now build and run the application, and the person information should appear as shown in the following screenshot:
    Processing XML files

How it works…

In step 2, the PersonXMLInput class defines how the person information maps to the PersonXMLInput from XML description file. XmlRoot(“Person”) stands for the root name <Person> in the XML file, the other three XmlElement attributes represent the XML element: <Name>, <Age>, and <Gender>.

In step 3, the Person class is the output type of XMLProcessor and the input type of XMLReader.

In step 4, we create an XMLReader object to read the content of the XML file. The following XMLSerializer object is used to read the PersonXMLInput information from the XML file. The new personXMLInput object will be passed to XMLProcessor as the input.

In step 5, the processor code is simple; it extracts information from the PersonXMLInput object input to generate a new Person class object as the output.

In step 6, the Person object is the input of PersonWriter. The ContentWriter writes the Name, Age, and Gender into the XNB file, one by one. The GetRuntimeType() method identifies the type of Person your game should load from the XNB file written by the PersonWriter object. The GetRuntimeReader() method specifies the XMLReader which will be invoked to load the XNB file in your game.

In step 7, the PersonReader reads the person information from the XNB file written by PersonWriter and builds a new Person object.

Manipulating the extracted information from an image in the content pipeline

Images are irreplaceable in games. They could be the sprites, the game world in 2D or the game user interface. In the 3D world, usually, images represent the appearance of 3D models. In the Windows Phone 7 XNA framework, the default texture or model importers and processors have already dealt with the images for you. Sometimes, you may want to handle the image information as per your needs when loading them from an XNB file. In this recipe, you will learn how to get and operate the image data in the XNA content pipeline.

How to do it…

Carry out the following steps:

  1. Create a Windows Phone Game project named ImageProcessorGame and change Game1.cs to ImageProcessorGame.cs. Add the BackgroundMaze.png (shown in the following screenshot) into the content project. Then, add a Content Pipeline Extension Library into the game solution named ImageProcessor, and then add ImageProcessor.cs into the project:
    Create a Windows Phone Game project
  2. Create the ImageProcessor class in the ImageProcessor content library project as follows:
    [code]
    [ContentProcessor(DisplayName = “Image Processor”)]
    public class ImageProcessor : TextureProcessor
    {
    public override TextureContent Process
    (TextureContent input, ContentProcessorContext context)
    {
    // Random object to generate random color
    Random random = new Random();
    Color color = Color.White;
    // PixelBitmapContent maintain pixel value in a 2D
    //array
    PixelBitmapContent<Color> image = null;
    // TextureContent object get the image content
    TextureContent texContent = base.Process(input,
    context);
    // Convert the texture content into PixelBitmapContent
    texContent.ConvertBitmapType(typeof
    (PixelBitmapContent<Color>));
    // Get the pixel color from the image content
    for (int face = 0; face < texContent.Faces.Count;
    face++)
    {
    MipmapChain mipChain = texContent.Faces[face];
    for (int mipLevel = 0;
    mipLevel < mipChain.Count; mipLevel++)
    {
    image = (PixelBitmapContent<Color>)
    input.Faces[face][mipLevel];
    }
    }
    int RowSpan = 0;
    // Generate the random color strip line by line
    for (int i = 0; i < image.Height; i++)
    {
    if (RowSpan++ < 20)
    {
    for (int j = 0; j < image.Width; j++)
    {
    // If the pixel color is black,
    // replace it with a random color
    if (image.GetPixel(j, i) == Color.Black)
    {
    image.SetPixel(j, i, color);
    }
    }
    }
    else
    {
    // Begin a new line and generate another random
    //color
    RowSpan = 0;
    color.R = (Byte)random.Next(0, 255);
    color.G = (Byte)random.Next(0, 255);
    color.B = (Byte)random.Next(0, 255);
    }
    }
    return texContent;
    }
    }
    [/code]
  3. Now, build the ImageProcessor project and you will get the ImageProcessor. dll library file. Add the ImageProcessor.dll into the reference list of the content project. Right-click on the BackgroundMaze.png to change the corresponding processor, as shown in the following screenshot:
    BackgroundMaze.png
  4. In this step, we will draw the processed image on the screen. We add the following code as the field of the ImageProcessorGame class:
    [code]
    // The Texture2D object
    Texture2D image;
    [/code]
  5. Then, load the image in LoadContent() by using the following line:
    [code]
    image = Content.Load<Texture2D>(“BackgroundMaze”);
    [/code]
  6. Draw the processed image on screen, paste the code in the Draw() method:
    [code]
    spriteBatch.Begin();
    spriteBatch.Draw(image, new Vector2(0,0), Color.White);
    spriteBatch.End();
    [/code]
  7. Now build and run the application. It runs as shown in the following screenshot:
    content pipeline

How it works…

In step 2, the ImageProcessor inherits from TextureProcessor for dealing with the image information imported by the built-in importer which supports .bmp, .dds, .dib, .hdr, .jpg, .pfm, .png, .ppm, and .tga image formats. If the importer does not support the image format, such as .gif, then you should implement the corresponding one.

As the input of ImageProcessor, TextureContent contains basic methods to manipulate the image content. The random and color objects will be used to generate random colors. Image is an object of PixelBitmapContent<Color> and the PixelBitmapContent is a subclass of BitmapContent. The BitmapContent class represents a single two-dimensional image with a fixed size and fixed format. Various pixel encodings are supported by subclasses of the bitmap type. Higher-level constructs, such as textures, that may contain multiple mipmap images or cube map faces are stored as collections of multiple bitmaps. The image object declares the type to which the texContent will be converted. The following methods base.Process() and TexContent.ConvertBitmapType change the texContent to a designated format. Here we change the contents to colors. By default, the texContent will be converted to TextureContent2D, but could also be TextureContent3D. This means that the image can have multiple faces and mipmaps. For a simple image, it has only one face and one mipmap. Our job here is to iterate the mipmaps in every face in an image. After the first for loop, you will get the image color set. The second for loop is easy; we go through all of the colors pixel by pixel in the image. If the pixel color is black, we use the image.SetPixel() method to set the color to a randomly generated color.

Extracting BoundingSphere and BoundingBox information from models

Extracting the BoundingSphere and BoundingBox information often helps you in detecting collisions in a 3D game. The BoundingSphere and BoundingBox come from the model vertices. For BoundingSphere, you should know the center and radius, whereas for BoundingBox, you need the min and max points. In this recipe, you will learn how to build and get the BoundingSphere and BoundingBox from 3D models produced by 3D modeling tool such as 3DS MAX or Maya. The default format in XNA is FBX, a compatible format shared between most 3D modeling tools in the content processing phase for Windows Phone 7 XNA game.

Getting ready

BoundingBox is actually specified by eight points, whereas BoundingSphere consists of a center point and the radius. BoundingBox is axis aligned, where each face is perpendicular to the axis.

The reason to use BoundingBox is for performance and ease. BoundingBox has the advantage that it fits the non-rotated rectangular objects very well. Mostly, game objects are static, so there is no need to perform accurate collision detection. Even if you want to make a more precise collision detection, BoundingBox can still help you to eliminate the non-collided object to gain better performance. The disadvantage of BoundingBox is that when it is applied to an object that is not axis aligned and rotating, you have to recreate the BoundingBox. This means performing the rotation matrix multiplication of every BoundingBox point, and this will slow down the game running speed.

As compared to BoundingBox, BoundingSphere needs less information: only the center point vector and the radius are required. If the object encompassed by BoundingBox is rotated, you need to recreate the BoundingBox. When the game objects are bounded by BoundingSphere, any collision detection between them will be fast. You only need to compute the distance of the center points of the two objects. As a suggestion, the BoundingSphere could be the first choice when you want to perform the basic collision detection. The disadvantage of BoundingSphere is that if the object is a long box, there will be a log space which will be wasted.

How to do it…

The following steps show you the best practice approach to extracting the BoundingBox and BoundingSphere from 3D models, which will help you to perform the collision detection in the real game:

  1. Create a Windows Phone Game project named ObjectBoundings. Change Game1. cs to ObjectBoundingsGame.cs. Add a Content Pipeline Extension Library named ModelBoundsProcessor and insert a new processor definition file named ModelBoundingBoxProcessor.cs. Then, in the content project, we add a box. fbx model file.
  2. Define ModelBoundingBoxProcessor. We add the following lines into the class field:
    [code]
    // The collection for MeshBoundingSpheres
    Dictionary<string, BoundingBox> MeshBoundingBoxes = new
    Dictionary<string, BoundingBox>();
    // The collection for MeshBoundingBoxes
    Dictionary<string, BoundingSphere> MeshBoundingSpheres = new
    Dictionary<string, BoundingSphere>();
    // The dictionary stores the BoundingSphere and BoundingBox
    // information of a model
    Dictionary<string, object> bounds = new Dictionary<string,
    object>();
    [/code]
  3. Next, we implement the Process() method for ModelBoundingBoxProcessor as follows:
    [code]
    public override ModelContent Process(NodeContent input,
    ContentProcessorContext context)
    {
    // Get the children NodeContents
    NodeContentCollection nodeContentCollection =
    input.Children;
    // If the input NodeContent does not have children mesh,
    // the input is the NodeContent for processing
    if (input.Children.Count == 0)
    {
    // Cast the input to MeshContent
    MeshContent meshContent = (MeshContent)input;
    // Get the points of the mesh
    PositionCollection vertices = new PositionCollection();
    // Get the world transformation of current meshcontent
    Matrix absoluteTransform =
    meshContent.AbsoluteTransform;
    // Translate the points’ positions from object
    //coordinates to world coordinates
    foreach (Vector3 vec in meshContent.Positions)
    {
    Vector3 vector =
    Vector3.Transform(vec, absoluteTransform);
    // Add the transformed vector to the vertice list
    vertices.Add(vector);
    }
    // Generate the BoundingBox of the mesh from the
    // transformed vertice list
    BoundingBox boundingbox =
    BoundingBox.CreateFromPoints(vertices);
    MeshBoundingBoxes.Add(input.Name, boundingbox);
    // Generate the BoundingSpere of the mesh from the
    // transformed vertice list
    BoundingSphere boundingSphere =
    BoundingSphere.CreateFromPoints(vertices);
    MeshBoundingSpheres.Add(input.Name, boundingSphere);
    }
    else
    {
    // If the root NodeContent has children, process them
    ParseChildren(nodeContentCollection);
    }
    // Deal with model in default
    ModelContent modelContent = base.Process(input, context);
    // Add the output BoundingBoxes and BoundingSpere to the
    //bounds
    bounds.Add(“BoundingBoxes”, MeshBoundingBoxes);
    bounds.Add(“BoundingSpheres”, MeshBoundingSpheres);
    // Assign the bounds to Tap for using in the game.
    modelContent.Tag = bounds;
    return modelContent;
    }
    [/code]
  4. Read BoundingSphere and BoundingSphere information of model in the main game project ObjectBoundings. Insert the following code as the class fields:
    [code]
    Model modelBox;
    Dictionary<string, object> bounds;
    Dictionary<string, BoundingSphere> boundingSpheres;
    Dictionary<string, BoundingBox> boundingBoxes;
    [/code]
  5. Then, add the following code into the LoadContent() method:
    [code]
    modelBox = Content.Load<Model>(“box”);
    // Read the the bounds dictionary object from Tag property
    bounds = (Dictionary<string, object>)modelBox.Tag;
    // Get the BoundingSphere dictionary
    boundingSpheres = (Dictionary<string,
    BoundingSphere>)bounds[“BoundingSpheres”];
    // Get the BoundingBox dictionary
    boundingBoxes = (Dictionary<string,
    BoundingBox>)bounds[“BoundingBoxes”];
    [/code]

How it works…

In step 2, MeshBoundingBoxes is the dictionary that holds the BoundingBox of every mesh of a model by name. MeshBoundingSpheres stores the BoundingSphere of every mesh of model by name. bounds is the final dictionary where both BoundingSphere and BoundingBox collections are saved.

In step 3, the input is a NodeContent object that represents the root of the model tree. The first thing is to get the children NodeContents of input to hold the submeshes. The following condition judgment checks the count of children NodeObjects of input. If the input does have children, we will deal with the input itself. Understand this condition as we transform the input to MeshContent, which represents the model mesh. The following vertex is a PositionCollection object that stores the collection of Vector3. Then, we use MeshContent.AbsoluteTransform to get the world transformation matrix of model. After that, we iterate every point in the mesh and transform them from object coordinate to world coordinate. Finally, we use the BoundingBox.CreateFromPoints() and BoundingSphere.CreateFromPoints() methods to generate the BoundingBox and BoundingSphere of the model. Otherwise, if the model has more than one submesh, we use ParseChildren() to work on. The only difference is that we should traverse all the submeshes, and the ParseChildren () method should be:

[code]
private void ParseChildren(NodeContentCollection
nodeContentCollection)
{
//Iterate every NodeContent in the children NodeContent collection
foreach (NodeContent nodeContent in nodeContentCollection)
{
// Cast the input to MeshContent
MeshContent meshContent = (MeshContent)nodeContent;
// Get the points of the mesh
PositionCollection vertices = new PositionCollection();
// Get the world transformation of current meshcontent
Matrix absoluteTransform = meshContent.AbsoluteTransform;
// Translate the points’ positions from object
// coordinates to world coordinates
foreach (Vector3 vec in meshContent.Positions)
{
Vector3 vector = Vector3.Transform(vec,
absoluteTransform);
// Add the transformed vector to the vertice list
vertices.Add(vector);
}
// Generate the BoundingBox of the mesh from the
// transformed vertice list
BoundingBox boundingbox = BoundingBox.
CreateFromPoints(vertices);
MeshBoundingBoxes.Add(nodeContent.Name, boundingbox);
// Generate the BoundingSpere of the mesh from the
// transformed vertice list
BoundingSphere boundingSphere =
BoundingSphere.CreateFromPoints(vertices);
MeshBoundingSpheres.Add(nodeContent.Name, boundingSphere);
}
}
[/code]

Now you have learned how to get the BoundingSphere and BoundingBox information from a model, you can use this information in your game for efficient collision detection.

 

Windows Phone Game User Interface, Heads Up Display (HUD) #Part 1

Scaling an image

Controlling the size of images is a basic technique in Windows Phone 7 XNA programming. Mastering this technique will help you implement many special visual effects in 2D. In this recipe, you will learn how to zoom an image in and out for a special visual effect. As an example, the image will jump up to you and go back. In the jumping phase, the image will fade out gradually. When the image becomes transparent, it will fall back with fading in.

How to do it…

The following mandatory steps will lead you to complete the recipe:

  1. Create a Windows Phone Game in Visual Studio 2010 named ImageZoomInOut, change Game1.cs to ImageZoomGame.cs. Then add the Next.png file from the code bundle to the content project. After the preparation work, insert the following code to the field of the ImageZoomGame class:
    [code]
    // Image texture object
    Texture2D texImage;
    // Image position
    Vector2 Position;
    // The scale factor
    float scale = 1;
    // The rotate factor
    float rotate = 0;
    // Alpha value for controlling the image transparency
    float alpha = 255;
    // The color of image
    Color color;
    // Timer object
    float timer;
    // Bool value for zooming out or in.
    bool ZoomOut = true;
    [/code]
  2. In the Initialize() method, we define the image centered in the middle of the screen and the color:
    [code]
    Position = new Vector2(GraphicsDevice.Viewport.Width / 2,
    GraphicsDevice.Viewport.Height / 2);
    color = Color.White;
    [/code]
  3. Load the Next.png file in the LoadContent() method with the following code:
    [code]
    texImage = Content.Load<Texture2D>(“Next”);
    [/code]
  4. Add the code to the Update() method to control the size transparency and rotation of the image:
    [code]
    // Accumulates the game elapsed time
    timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
    // Zoom out
    if (ZoomOut && alpha >= 0 && timer > 50)
    {
    // If alpha equals 0, zoom the image in
    if (alpha == 0.0f)
    {
    ZoomOut = false;
    }
    // Amplify the image
    scale += 0.1f;
    // Rotate the image in clockwise
    rotate += 0.1f;
    // Fade the image out
    if (alpha > 0)
    {
    alpha -= 5;
    }
    color.A = (byte)alpha;
    // Reset the timer to 0 for the next interval
    timer = 0;
    }
    // Zoom in
    else if (!ZoomOut && timer > 50)
    {
    // If alpha equals 255, zoom the image out
    if (alpha == 255)
    {
    ZoomOut = true;
    }
    // Scale down the image
    scale -= 0.1f;
    // Rotate the image counter-clockwise
    rotate -= 0.2f;
    // Fade the image in
    if (alpha < 255)
    {
    alpha += 5;
    }
    color.A = (byte)alpha;
    // Reset the timer to 0 for the next interval
    timer = 0;
    }
    [/code]
  5. To draw the image and effects on the Windows Phone 7 screen, add the code to the Draw() method:
    [code]
    spriteBatch.Begin(SpriteSortMode.Immediate,
    BlendState.NonPremultiplied);
    spriteBatch.Draw(texImage, Position, null, color, rotate, new
    Vector2(texImage.Width / 2, texImage.Height / 2), scale,
    SpriteEffects.None, 0f);
    spriteBatch.End();
    [/code]
  6. Now, build and run the application. The application runs as shown in the following screenshots:
    Scaling an image

How it works…

In step 1, texImage holds the image for controlling; the Position variable represents the image position; scale will be used to control the image size; as the rotation factor, the rotate variable will store the value for rotation; alpha in color controls the image transparency degree; timer accumulates the game elapse milliseconds; the last variable ZoomOut determines whether to zoom the image out.

In step 2, we define the Position of the image in the center of the screen and set the color to white.

In step 4, the accumulated timer is used to control the period of time between the two frames. The next part is to check the direction of scale. If ZoomOut is true, we will increase the image size, decrease the alpha value and rotate the image in a clockwise direction, then reset the timer. Otherwise, the behavior is opposite.

In step 5, we set the BlendState in SpritBatch.Begin() to NonPremultiplied because we change the alpha value of color linearly, then draw the images with scale and rotation factors around the image center.

Creating a Simple Sprite Sheet animation in a 2D game

In Windows Phone 7 game programming, it is an obvious performance cost if you wanted to render a number of images. You need to allocate the same quantity of texture objects for the images in your game. If this happens in the game initialization phase, the time could be very long. Considering these reasons, the talented game programmers in the early days of the game industry found the solution through the Sprite Sheet technique, using a big image to contain a set of smaller images. It is very convenient for 2D game programming, especially for the sprite animation. The big advantage of using the Sprite Sheet is the ability to create character animation, complex effects, explosions, and so on. Applying the technique will be performance sweet for game content loading.

The Sprite Sheet has two types:

  • Simple Sprite Sheet, where all the smaller images have the same dimensions
  • Complex Sprite Sheet, where all the images in the sheet have different dimensions

In this recipe, you will learn how to create the Simple Sprite Sheet and use it in your Windows Phone 7 game.

In Simple Sprite Sheet, every subimage has the same size, the dimension could be defined when the Sprite Sheet was initiated. To locate the designated sub-image, you should know the X and Y coordinates, along with the width and height, and the offset for the row and column. For Simple Sprite Sheet, the following equation will help you complete the work:

[code]Position = (X * Offset, Y * Offset)[/code]

For convenience, you can define the size of the subimage in digital image processing software, such as Adobe Photoshop, Microsoft Expression Design, Paint.Net, GIMP, and so on.

In this recipe, you will learn how to use the Sprite Sheet in your own Windows Phone 7 Game.

Getting ready

In the image processing tool, we create a Sprite Sheet as the next screenshot. In the image, every subimage is surrounded by a rectangle 50 pixels wide and 58 pixels high, as shown in the left-hand image in the following screenshot.

In a real Windows Phone Game, I am sure you do not want to see the border of the rectangle. As part of the exportation process, we will make the rectangles transparent, you just need to change the alpha value from 100 to 0 in code, and the latest Sprite Sheet should look similar to the right-hand image in the following screenshot:

SimpleSpriteSheet

We name the Sprite Sheet used in our example SimpleSpriteSheet.png.

Now the Sprite Sheet is ready, the next part is to animate the Sprite Sheet in our Windows Phone 7 game.

How to do it…

The following steps will give you a complete guidance to animate a Simple Sprite Sheet:

  1. Create a Windows Phone Game project in Visual Studio 2010 named SimpleSpriteSheetAnimation, change Game1.cs to SimpleSpirteSheetGame.cs, adding the lines to the field of the SimpleSpriteSheetGame class:
    [code]
    // Sprite Texture
    Texture2D sprite;
    // A Timer variable
    float timer = 0f;
    // The interval
    float interval = 200;
    // Frame Count
    int FrameCount = 4;
    // Animation Count
    int AnimationCount = 2;
    // Current frame holder
    int currentFrame = 0;
    // Width of a single sprite image, not the whole Sprite
    int spriteWidth = 50;
    // Height of a single sprite image, not the whole Sprite
    int spriteHeight = 58;
    // A rectangle to store which ‘frame’ is currently being
    Rectangle sourceRect;
    // The center of the current ‘frame’
    Vector2 origin;
    // Index of Row
    int row = 0;
    // Position Center
    Vector2 screenCenter;
    [/code]
  2. Load the Sprite Sheet image. Put the code into the LoadContent() method:
    [code]
    sprite = Content.Load<Texture2D>(“SpriteSheet”);
    screenCenter = new Vector2(GraphicsDevice.Viewport.Width / 2,
    GraphicsDevice.Viewport.Height / 2);
    [/code]
  3. This step is to animate the Simple Sprite Sheet. Insert the code in to the Update() method:
    [code]
    // Increase the timer by the number of milliseconds since
    // update was last called
    timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
    // Check the timer is more than the chosen interval
    if (timer > interval)
    {
    //Show the next frame
    currentFrame++;
    //Reset the timer
    timer = 0f;
    }
    // If reached the last frame, reset the current frame back to
    // the one before the first frame
    if (currentFrame == FrameCount)
    {
    currentFrame = 0;
    }
    // React to the tap gesture
    TouchCollection touches = TouchPanel.GetState();
    if (touches.Count > 0 && touches[0].State ==
    TouchLocationState.Pressed)
    {
    // Change the sprite sheet animation
    row = (row + 1) % AnimationCount;
    }
    // Compute which subimage will be rendered
    sourceRect = new Rectangle(currentFrame * spriteWidth,
    row * spriteHeight, spriteWidth, spriteHeight);
    // Compute the origin position for image rotation and scale.
    origin = new Vector2(sourceRect.Width / 2,
    sourceRect.Height / 2);
    [/code]
  4. Draw the Sprite Sheet on the screen. Add the code in the Draw() method:
    [code]
    spriteBatch.Begin();
    //Draw the sprite in the center of an 800×600 screen
    spriteBatch.Draw(sprite, screenCenter, sourceRect,
    Color.White, 0f, origin, 3.0f, SpriteEffects.None, 0);
    spriteBatch.End();
    [/code]
  5. Now, build and run the application. When you tap on the screen, the animation will change, as shown in the following screenshots:
    Simple Sprite Sheet animation in a 2D game

How it works…

In step 1, the sprite object will hold the Sprite Sheet image; the timer variable will accumulate the game elapsed time; the interval variable defines the period of time between two frames in milliseconds; AnimationCount shows the number of animations that will be played; currentFrame indicates the playing frame, and also presents the column in Sprite Sheet image; spriteWidth and spriteHeight define the width and height of the currently rendering subimage. In this example the width and height of the Sprite subimage is 50 and 58 pixels respectively; the sourceRectangle lets the SpriteBatch.Draw() know which part of the Sprite Sheet image will be drawn; the row variable shows which animation will be rendered, 0 means the animation starts from the first.

In step 3, we accumulate the game elapsed time. If the accumulated time on the timer is greater than the interval, the current frame goes to the next frame, and then sets the timer to 0 for the next interval. We then check whether the current frame is equal to the FrameCount. If yes, it means that the animation ended, then we set the currentFrame to 0 to replay the animation. Actually, the currentFrame represents the current column of the Sprite Sheet image. Next, we should make sure which animation we want to render, here, the react to the tap gesture to set the row value within the AnimationCount to change the animation. When the currentFrame for the column and the row are ready, you can use them to locate the sub-image with the rectangle object sourceRect; the last line in this code will compute the origin position for the image rotation and scale, here we set it to the center of the Sprite Sheet subimage.

In step 4, the Draw() method receives the texture parameter; position of texture drawn on screen; the rectangle for defining the part of texture that will be drawn; the color White means render the texture in its original color; rotation angle set to 0 stands for no rotation; the scale factor will let the rendering device know how big the texture will be, 3 here means the final texture size will be three times larger than the original; SpriteEffects means the effects will be applied to render the texture, SpriteEffects.None tells the application not to use any effect for the texture; the final parameter layerDepth illustrates the texture drawing order.

Creating a Complex Sprite Sheet animation in a 2D game

The Complex Sprite Sheet contains subimages with different sizes. Moreover, for every Complex Sprite Sheet, it has an additional description file. The description file defines the location and size of every subimage. It is the key difference between a Simple Sprite Sheet and a Complex Sprite Sheet. For a Simple Sprite Sheet, you can compute the location and size of the sub-image with the same width and height; however, for a Complex Sprite Sheet, it is harder, because the subimages in this kind of Sprite Sheet are often placed for the efficient use of space. To help identify the coordinates of the sprites in the Complex Sprite Sheet, the description file offers you the subimage location and size information. For the Sprite Sheet animation use, the description file also provides the animation name and attributes. The following screenshot shows an example of a Complex Sprite Sheet:

Complex Sprite Sheet animation

Getting ready

In the Sprite Sheet, you can see that the subimages have different sizes and locations. They are not placed in a regular grid, and that’s why we need the description file to control these images. You may ask how we can get the description file for a designated Complex Sprite Sheet. Here, we use the tool SpriteVortex, which you can download from Codeplex at http://spritevortex.codeplex.com/.

The following steps show you how to process a Complex Sprite Sheet using SpriteVortex:

  1. When you run SpriteVortex, click Import Spritesheet Image, at the top-left, as shown in the following screenshot:
    SpriteVortex,  Import Spritesheet Image
  2. After choosing MegaManSheet.png from the image folder of this chapter, click on Alpha Cut, at the top bar of the main working area. You will see the subimages in the Complex Sprite Sheet individually surrounded by yellow rectangles, as shown in the following screenshot:
    Alpha Cut
  3. Next, you could choose the subimages to create your own animation. In the left Browser panel, we change the animation name from Animation 0 to Fire.
  4. Then, we choose the sub-images from the Complex Sprite Sheet. After that, we click the button—Add Selected to Animation. The selected images will show up in the Animation Manager from the first image to the last one frame-by-frame.
  5. Similar to these steps, we add other two animations in the project named Changing and Jump.
  6. The final step is to export the animation definition as XML, the important XML will be used in our project to animate the subimages, and the entire process can be seen in the following screenshot:
    XML, the important XML

The exported animation XML file named SpriteDescription.xml can be found in the project content directory for this recipe.

In the XML file, you will find the XML element—Texture, which saves the Sprite Sheet path. The XML—Animation—includes the animation name and its frames, the frames contain the subimages’ location and size. Next, you will learn how to use the Complex Sprite Sheet and the XML description to animate the sprite.

How to do it…

The following steps will show you how to animate a Complex Sprite Sheet:

  1. Create a Windows Phone Game project named ComplexSpriteSheetAnimation, change Game1.cs to ComplexSpriteSheetAnimationGame.cs. Then add the exported Sprite Sheet description file SpriteDescription.xml to the content project, changing the Build Action property of the XML file from Compile to None because we customized the content processing code for XML format, and the Copy to Output Directory property to Copy Always. This will always copy the description file to the game content output directory of the application.
  2. The description file is an XML document; we need to parse the animation information when loading it to our game. Subsequently, we add some description parsing classes: Frame, Animation, SpriteTexture, AnimationSet, and SpriteAnimationManager in the SpriteAnimationManager.cs of the main project. Before coding the classes, one more reference System.Xml. Serialization must be added to the project reference list as we will use the XML Serialization technique to parse the animation. Now, lets define the basic classes:
    [code]
    // Animation frame class
    public class Frame
    {
    // Frame Number
    [XmlAttribute(“Num”)]
    public int Num;
    // Sub Image X positon in the Sprite Sheet
    [XmlAttribute(“X”)]
    public int X;
    // Sub Image Y positon in the Sprite Sheet
    [XmlAttribute(“Y”)]
    public int Y;
    // Sub Image Width
    [XmlAttribute(“Width”)]
    public int Width;
    // Sub Image Height
    [XmlAttribute(“Height”)]
    public int Height;
    // The X offset of sub image
    [XmlAttribute(“OffSetX”)]
    public int OffsetX;
    // The Y offset for subimage
    [XmlAttribute(“OffsetY”)]
    public int OffsetY;
    // The duration between two frames
    [XmlAttribute(“Duration”)]
    public float Duration;
    }
    // Animation class to hold the name and frames
    public class Animation
    {
    // Animation Name
    [XmlAttribute(“Name”)]
    public string Name;
    // Animation Frame Rate
    [XmlAttribute(“FrameRate”)]
    public int FrameRate;
    public bool Loop;
    public bool Pingpong;
    // The Frames array in an animation
    [XmlArray(“Frames”), XmlArrayItem(“Frame”, typeof(Frame))]
    public Frame[] Frames;
    }
    // The Sprite Texture stores the Sprite Sheet path
    public class SpriteTexture
    {
    // The Sprite Sheet texture file path
    [XmlAttribute(“Path”)]
    public string Path;
    }
    // Animation Set contains the Sprite Texture and animation.
    [XmlRoot(“Animations”)]
    public class AnimationSet
    {
    // The sprite texture object
    [XmlElement(“Texture”, typeof(SpriteTexture))]
    public SpriteTexture SpriteTexture;
    // The animation array in the Animation Set
    [XmlElement(“Animation”, typeof(Animation))]
    public Animation[] Animations;
    [/code]
  3. Next, we will extract the Animation information from the XML description file using the XML deserialization technique:
    [code]
    // Sprite Animation Manager class
    public static class SpriteAnimationManager
    {
    public static int AnimationCount;
    // Read the Sprite Sheet Description information from the
    // description xml file
    public static AnimationSet Read(string Filename)
    {
    AnimationSet animationSet = new AnimationSet() ;
    // Create an XML reader for the sprite sheet animation
    // description file
    using (System.Xml.XmlReader reader =
    System.Xml.XmlReader.Create(Filename))
    {
    // Create an XMLSerializer for the AnimationSet
    XmlSerializer serializer = new
    XmlSerializer(typeof(AnimationSet));
    // Deserialize the Animation Set from the
    // XmlReader to the animation set object
    animationSet =
    (AnimationSet)serializer.Deserialize(reader);
    }
    // Count the animations to Animation Count
    AnimationCount = animationSet.Animations.Length;
    return animationSet;
    }
    }
    [/code]
  4. Now, from this step, we will begin to use the parsed AnimationSet to animate the Complex Sprite Sheet and switch the animations. So, add the code to the field of ComplexSpriteSheetAnimationGame class:
    [code]
    // A Timer variable
    float timer;
    // The interval
    float interval = 200;
    // Animation Set stores the animations in the sprite sheet
    // description file
    AnimationSet animationSet;
    // Texture object loads and stores the Sprite Sheet image
    Texture2D texture;
    // The location of subimage
    int X = 0;
    int Y = 0;
    // The size of subimage
    int height = 0;
    int width = 0;
    // A rectangle to store which ‘frame’ is currently being shown
    Rectangle sourceRectangle;
    // The center of the current ‘frame’
    Vector2 origin;
    // Current frame holder
    int currentFrame = 0;
    // Current animation
    int currentAnimation = 0;
    [/code]
  5. Read the Complex Sprite Sheet to the AnimationSet object. Add the line to the Initialize() method:
    [code]
    animationSet =
    SpriteAnimationManager.Read(@”ContentSpriteDescription.xml”);
    [/code]
  6. In this step, we animate the Complex Sprite Sheet using the parsed animation set. Add the following lines to the Update() method:
    [code]
    // Change the animation when tap the Touch screen
    TouchCollection touches = TouchPanel.GetState();
    if (touches.Count > 0 && touches[0].State ==
    TouchLocationState.Pressed)
    {
    // make the animation index vary within the total
    // animation count
    currentAnimation = (currentAnimation + 1) %
    SpriteAnimationManager.AnimationCount;
    }
    // Accumulate the game elapsed time
    timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
    // If the current frame is equal to the length of the current
    // animation frames, reset the current frame to the beginning
    if (currentFrame ==
    animationSet.Animations[currentAnimation].Frames.Length – 1)
    {
    currentFrame = 0;
    }
    // Check the timer is more than the chosen interval
    if (timer > interval && currentFrame <=
    animationSet.Animations[currentAnimation].Frames.Length – 1)
    {
    // Get the location of current subimage
    height = animationSet.Animations[currentAnimation].
    Frames[currentFrame].Height;
    width = animationSet.Animations[currentAnimation].
    Frames[currentFrame].Width;
    // Get the size of the current subimage
    X = animationSet.Animations[currentAnimation].
    Frames[currentFrame].X;
    Y = animationSet.Animations[currentAnimation].
    Frames[currentFrame].Y;
    // Create the rectangle for drawing the part of the
    //sprite sheet on the screen
    sourceRectangle = new Rectangle(X, Y, width, height);
    // Show the next frame
    currentFrame++;
    // Reset the timer
    timer = 0f;
    }
    // Compute the origin position for image rotation and scale.
    origin = new Vector2(sourceRectangle.Width / 2,
    sourceRectangle.Height / 2);
    [/code]
  7. Draw the animation on the Windows Phone 7 Touch screen, add the code to the Draw() method:
    [code]
    spriteBatch.Begin();
    spriteBatch.Draw(texture, new Vector2(400, 240),
    sourceRectangle,
    Color.White, 0f, origin, 2.0f, SpriteEffects.None, 0);
    spriteBatch.End();
    [/code]
  8. Now, build and run the application. When you tap on the Touch screen, the animation will change as shown in the following screenshots:
    Complex Sprite Sheet animation in a 2D game

How it works…

In step 2, the Frame class corresponds to the XML element: Frame and its attributes in the XML file. The same applies for the Animation class, SpriteTexture class, and the AnimationSet class. The XmlSerializer can recognize the XmlElement and XmlAttribute for every member variable unless the XmlAttribute or XmlElement attribute is defined above it. If the class type is customized, we also need to use the typeof() method to let the serializer know the object size. For identifying the array member variables—Frames and Animations—we should use the XmlArray attribute with array root name in the XML file and the XmlArrayItem with its element name. Additionally, the XmlRoot will help the XmlSerializer to locate the root of the whole XML file, here, it is Animations.

In step 3, since the loading of this data occurs independently of the instance of the manager class (in this case it deserializes an instance of a class from XML), we can declare it static to simplify our code, as an instance of the manager is not necessary as it contains no instance data. The Read() method creates an XmlReader object to read the Sprite Sheet XML description file. The XmlReader class supports reading XML data from a stream or file. It allows you to read the contents of a node. When the XmlReader for the description file is created, we instance the XmlSerializer object to read the AnimationSet type data. We use the XmlSerializer.Deserialize() method to decode the description XML file to meet the XML attributes we defined for the AnimationSet class, then the XmlSerializer will recursively read the subclasses with the XML attributes. After the XML deserialization, the code will set the count of the parsed animations to the AnimationCount variable. We will use the AnimationCount to control the index of animations for animation playing in the game.

In step 4, the timer variable will accumulate the game elapsed time; the interval variable defines the period of time between two frames; the animationSet variable will store the animations from the Sprite Sheet description file; the texture object holds the Complex Sprite Sheet image, with X,Y for the location and height, width for the size. The sourceRectangle variable will read the location and size information of the to create the region for drawing the subimage on screen; currentFrame defines the current animation playing frame; currentAnimation indicates the currently playing animation.

In step 6, the first part of the code is to react to the tap gesture for changing the current playing animation. The second part is to increase the currently playing frame, if the current frame reaches the maximum frame count of current animation, we will replay the current animation. The last part will get the location and size of the current subimage according to the current frame and use the location and size information to build the region of the current subimage for playing. Then, increase the frame to next before resetting the timer to 0 for a new frame. This information is controlled by the XML file that was generated from the Codeplex tool we exported in step 1.

Creating a text animation in Adventure Genre (AVG) game

Adventure Genre (AVG) game uses the text or dialog to describe game plots. With different branches, you will experience different directions in game underplaying. In most AVG games, text presentation is the key part, if you have ever played one or more of the games, you might have found the text was rendered character-by-character or word-by-word. In this recipe, you will learn how to work with sprite font and how to manipulate the font-related methods to compute the font and properly adjust the character and word positions in real time.

How to do it…

The following steps will lead you to complete text animation effect in Windows Phone 7:

  1. Create a Windows Phone Game in Visual Studio 2010 named AVGText, change Game1.cs to AVGTextGame.cs, and add the following code to the AVGTextGame class as field:
    [code]
    // SpriteFont object
    SpriteFont font;
    // Game text
    string text = “”;
    // The text showed on the screen
    string showedText = “”;
    // The bound for the showedText
    const int TextBound = 20;
    // Game timer
    float timer = 0;
    // Interval time
    float interval = 100;
    // Index for interate the whole original text
    int index = 0;
    [/code]
  2. Initialize the original text and process it for wrapped showing. Add the initialization and processing code to the Initialize() method:
    [code]
    // The original text
    text = “This is an AVG game text, you will find the text is
    + “showed character by character, I hope “
    + “this recipe is useful for you.”;
    // Split the original string to a string array
    string[] strArray = text.Split(‘ ‘);
    // Declare the temp string for each row, aheadstring for
    // looking ahead one word in this row
    string tempStr = strArray[0];
    string aheadString = “”;
    // Declare the StringBuilder object for holding the sliced
    // line
    StringBuilder stringBuild = new StringBuilder();
    // Iterate the word array, i for current word, j for next word
    for (int i = 0 ,j = i ; j < strArray.Length; j++)
    {
    // i is before j
    i = j – 1;
    // Check the temp string length whether less than the
    // TextBound
    if (aheadString.Length <= TextBound)
    {
    // If yes, check the string looks ahead one more word
    // whether less than the TextBound
    if ((aheadString = tempStr + “ “ + strArray[j]).Length
    <= TextBound)
    {
    // If yes, set the look-ahead string to the temp
    // string
    tempStr = aheadString;
    }
    }
    else
    {
    // If not, add the temp string as a row
    // to the StringBuilder object
    stringBuild.Append(tempStr.Trim() + “n”);
    // Set the current word to the temp string
    tempStr = strArray[i];
    aheadString = tempStr;
    j = i;
    }
    }
    [/code]
  3. Load the SpriteFont content in the LoadContent() method:
    [code]
    font = Content.Load<SpriteFont>(“gameFont”);
    [/code]
  4. Update for drawing the showedText character-by-character. Add the lines to the Update() method:
    [code]
    // Accumulate the game elapsed
    timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
    // Show the text character by character
    if (timer > interval && index < text.Length)
    {
    // Every interval add the current index character to the
    // showedText.
    showedText += text.Substring(index, 1);
    // Increse the index
    index++;
    // Set the timer to 0 for the next interval
    timer = 0;
    }
    [/code]
  5. Draw the AVG text effect on the Windows Phone 7 Touch screen. Add the code to the Draw() method:
    [code]
    // Draw the string on screen
    spriteBatch.Begin();
    spriteBatch.DrawString(font, showedText, new Vector2(0, 0),
    Color.White);
    spriteBatch.End();
    [/code]
  6. Build and run the application. It should look similar to the following screenshots:
    text animation in Adventure Genre (AVG) game

How it works…

In step 1, the font object will be used to render the AVG game text; the text variable holds the original text for processing; the showedText stores the latest showing text; TextBound limits the bound for the showing text; the timer is used to count the game elapsed time; the interval variable represents the period of time between two frames; the index variable indicates the current position when showing the original text.

In step 2, we use the text.Split() method to split the original text to a word array, then declare three objects tempStr, aheadString, and stringBuild. tempStr stores the current row, composed of the words from the original text, aheadString saves one more word ahead of the tempStr for preventing the actual length of tempStr from becoming greater than the TextBound. In the for loop, we declare two iterating indices i and j: i is the index from the beginning of the original text, j goes after i. In the loop step, if the length of aheadString is less than the TextBound, one more word following the tempStr will be added to it. If the new look-ahead string length is still less than the Textbound, we will assign the aheadString to tempStr. On the other hand, if the length of aheadString is greater than Textbound, we will do line breaking. At this moment, the tempStr is appended to the stringBuild object as a row with a line breaking symbol—n. Then we set the current word to the tempStr and aheadString, and also set the current i to j for the next row.

In step 4, the first line accumulates the game elapsed milliseconds. If the elapsed time is greater than the interval, and the index is less than the processed text length, we will append the current character to showedText, after that, move the index pointer to the next character and set the timer to zero for the next interval.

Creating a text-based menu—the easiest menu to create

Menu plays the key role of a complete game; the player could use the menu to navigate to different parts of the game. As guidance to the game, the menu has a lot of appearances depending on the game type, animated or static, and so on. In this chapter, you will learn how to work with three kinds of menu, text-based, image-based, and model-based. As a beginning, this recipe shows you the text-based menu.

Getting ready

The text-based menu is made up of texts. Every menu item is an independent string. In this example, when you tap the item, the reaction will be triggered with the text color changing and text visual effects popping up. OK, let’s begin!

How to do it…

The following steps will show you how to create a simple text-based menu:

  1. Create a Windows Phone Game project named TextMenu, change Game1.cs to TextMenuGame.cs, and add gameFont.spriteFont to content project. Then add TextMenuItem.cs to the main project.
  2. Open TextMenuItem.cs file, in the field of the TextMenuItem class, add the following lines:
    [code]
    // SpriteBatch
    SpriteBatch spriteBatch;
    // Menu item text font
    SpriteFont font;
    // Menu item text
    public string Text;
    // Menu Item position
    public Vector2 Position;
    public Vector2 textOrigin;
    // Menu Item size
    public Vector2 Size;
    // Bool tap value shows whether tap on the screen
    public bool Tap;
    // Tap event handler
    public event EventHandler OnTap;
    // Timer object
    float timer = 0;
    // Alpha value of text color
    float alpha = 1;
    Color color;
    // The scale of text
    float scale = 1;
    [/code]
  3. Next, we define the Bound property of the text menu item:
    [code]
    // The Bound of menu item
    public Rectangle Bound
    {
    get
    {
    return new Rectangle((int)Position.X,
    (int)Position.Y, (int)Size.X, (int)Size.Y);
    }
    }
    [/code]
  4. Define the constructor of the TextMenuItem class:
    [code]
    // Text menu item constructor
    public TextMenuItem(Vector2 position, string text, SpriteFont
    font, SpriteBatch spriteBatch)
    {
    Position = position;
    Text = text;
    this.font = font;
    this.spriteBatch = spriteBatch;
    // Compute the text size
    Size = font.MeasureString(Text);
    textOrigin = new Vector2(Size.X / 2, Size.Y / 2);
    color = Color.White;
    }
    [/code]
  5. Then, we implement the Update() method:
    [code]
    // Text menu item update method, get the tapped position on
    screen
    public void Update(Vector2 tapPosition)
    {
    // if the tapped position within the text menu item bound,
    // set Tap to true and trigger
    // the OnTap event
    if (Bound.Contains((int)tapPosition.X,(int)tapPosition.Y))
    {
    Tap = true;
    OnTap(this, null);
    }
    else
    {
    Tap = false;
    }
    }
    [/code]
  6. The last method in the TextMenuItem class is the Draw() method, let’s add the code:
    [code]
    public void Draw(GameTime gameTime)
    {
    timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
    // Draw the text menu item
    if (Tap)
    {
    // Draw text visual effect
    if (alpha >= 0 && timer > 100)
    {
    // Decrease alpha value of effect text
    alpha -= 0.1f;
    color *= alpha;
    // Increase the effect text scale
    scale++;
    // Draw the first layer of effect text
    spriteBatch.DrawString(font, Text, Position,
    color, 0, new Vector2(Size.X / 2, Size.Y / 2),
    scale, SpriteEffects.None, 0);
    // Draw the second layer of effect text
    spriteBatch.DrawString(font, Text, Position,
    color, 0, new Vector2(Size.X / 2, Size.Y / 2),
    scale / 2, SpriteEffects.None, 0);
    // Reset the timer for the next interval
    timer = 0;
    }
    // Draw the original text
    spriteBatch.DrawString(font, Text, Position,
    Color.Red);
    }
    else
    {
    // Reset the scale, alpha and color value of original
    // text
    scale = 1;
    alpha = 1;
    color = Color.White;
    // Draw the original text
    spriteBatch.DrawString(font, Text, Position,
    Color.White);
    }
    }
    [/code]
  7. When the TextMenuItem class is done, the following work is about using the class in our game class. Add the code to the TextMenuGame field:
    [code]
    // SpriteFont object for text menu item
    SpriteFont font;
    // Menu collection of text menu item
    List<TextMenuItem> Menu;
    // Random color for background
    Random random;
    Color backgroundColor;
    [/code]
  8. This step is to initialize the variables—Menu, random, and backgroundColor; add the code to the Initialize() method:
    [code]
    Menu = new List<TextMenuItem>();
    random = new Random();
    backgroundColor = Color.CornflowerBlue;
    [/code]
  9. Load the game sprite font and text menu items in Menu, and add the following code to the LoadContent() method:
    [code]
    font = Content.Load<SpriteFont>(“gameFont”);
    // Initialize the text menu items in Menu
    int X = 100;
    int Y = 100;
    for (int i = 0; i < 5; i++)
    {
    TextMenuItem item = new TextMenuItem(
    new Vector2(X, Y + 60 * i), “TextMenuItem”, font,
    spriteBatch);
    item.OnTap += new EventHandler(item_OnTap);
    Menu.Add(item);
    }
    [/code]
  10. Define the text menu item event reaction method item_OnTap():
    [code]
    void item_OnTap(object sender, EventArgs e)
    {
    // Set random color for back in every valid tap
    backgroundColor.R = (byte)random.Next(0, 256);
    backgroundColor.G = (byte)random.Next(0, 256);
    backgroundColor.B = (byte)random.Next(0, 256);
    }
    [/code]
  11. Get the tapped position and pass it to the text menu items for valid tap checking. Insert the code to the Update() method:
    [code]
    // Get the tapped position
    Vector2 tapPosition = new Vector2();
    TouchCollection touches = TouchPanel.GetState();
    if (touches.Count > 0 && touches[0].State ==
    TouchLocationState.Pressed)
    {
    tapPosition = touches[0].Position;
    // Check the tapped positon whether inside one of the text
    // menu items
    foreach (TextMenuItem item in Menu)
    {
    item.Update(tapPosition);
    }
    }
    [/code]
  12. Draw the Menu, paste the code into the Draw() method:
    [code]
    // Replace the existing Clear code with this to
    // simulate the effect of the menu item selection
    GraphicsDevice.Clear(backgroundColor);
    // Draw the Menu
    spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.
    AlphaBlend);
    foreach (TextMenuItem item in Menu)
    {
    item.Draw(gameTime);
    }
    spriteBatch.End();
    [/code]
  13. Now, build and run the application, and tap the first text menu item. It should look similar to the following screenshot:
    easiest menu to create

How it works…

In step 2, the spriteBatch is the main object to draw the text; font object holds the SpriteFont text definition file; Text is the actual string shown in the text menu; Position indicates the location of the text menu item on screen; textOrigin defines the center of the text menu item for scale or rotation; the Size, Vector2 variable returns the width and height of Text through X and Y in Vector2; Tap represents whether the tap gesture takes place; the event handler OnTap listens to the occurrence of the tap gesture; timer object accumulates the game elapsed time for text visual effects; alpha value will be used to change the transparency of the text color; the last variable scale stores the scale factor of text menu item size.

In step 3, Bound returns a rectangle around the text menu item, and the property will be used to check whether the tapped position is inside the region of the text menu item.

In step 4, notice, we use the font.MeasureString() method to compute the size of the Text. Then set the origin position to the center of the Text.

In step 5, the Update() method receives the tapped position and checks whether it is inside the region of the text menu item, if yes, set the Boolean value Tap to true and trigger the OnTap event, otherwise, set Tap to false.

In step 6, the first line is to accumulate the game elapsed time. When the Tap is false—which means no tap on the text menu item—we set scale and alpha to 1, and the color to Color.White. Else, we will draw the text visual effects with two texts having the same text content of the text menu item; the size of the first layer is two times the second. As time goes by, the two layers will grow up and gradually disappear. After that, we draw the original text of the text menu item to Color.Red.

In step 7, the font object holds the game sprite font for rendering the Text of the text menu item; Menu is the collection of text menu items; the random variable will be used to generates random number for creating random backgroundColor.

In step 10, because the range for red, green, and blue factors of Color is from 0 to 255, the random numbers generated for them also meet the rule. With the random number, every valid text menu item tap will change the background color randomly.

Creating an image-based menu

The image-based menu is another menu-presentation approach. Unlike the text-based menu, the image-based menu uses 2D picture as the content of every menu item and makes the game navigation user interface more attractive and innovative. It gives graphic designers a much bigger space for conceiving ideas for a game. An image-based menu easily stimulates the designers’ and programmers’ creativities. The image menu item can swipe in and swipe out, jump in and jump out, or fade in and fade out. In this recipe, you will learn how to create an image-based menu system and use it in your own Windows Phone 7 game.

Getting ready

As an example, the image menu items are placed horizontally on the screen. When you tap one of them, it will grow up and the current item index shows at the top-left of the screen; once the tapped position is outside of its bound, the menu will restore to the initial state. Now, let’s build the application.

How to do it…

Follow these steps to create your own image-based menu:

  1. Create a Windows Phone Game project in Visual Studio 2010 named ImageMenu, change Game1.cs to ImageMenuGame.cs, and add the ImageMenuItem.cs in the main project. Then add Imageitem.png and gameFont.spriteFont to the content project.
  2. Create the ImageMenuItem class in the ImageMenuItem.cs file. First, add the code to the ImageMenuItem class field:
    [code]
    // SpriteBatch object
    SpriteBatch spriteBatch;
    // Menu item text font
    Texture2D texture;
    // Menu Item position
    public Vector2 Position;
    // Menu Item origin position for translation and rotation
    public Vector2 Origin;
    // Bool tap value shows whether tap on the screen
    public bool Tap;
    // Timer object
    float timer = 0;
    // The scale range from MinScale to MaxScale
    const float MinScale = 0.8f;
    const float MaxScale = 1;
    // The scale of text
    float scale = 0.8f;
    // Image menu item index
    public int Index = 0;
    [/code]
  3. Next, add the Bound property:
    [code]
    // The Bound of menu item
    public Rectangle Bound
    {
    get
    {
    return new Rectangle(
    (int)(Position.X – Origin.X * scale),
    (int)(Position.Y – Origin.Y * scale),
    (int)(texture.Width * scale),
    (int)(texture.Height * scale));
    }
    }
    [/code]
  4. Then, define the constructor of the ImageMenuItem class:
    [code]
    // Text menu item constructor
    public ImageMenuItem(Vector2 Location,Texture2D Texture,
    SpriteBatch SpriteBatch)
    {
    Position = Location;
    texture = Texture;
    spriteBatch = SpriteBatch;
    Origin = new Vector2(texture.Width / 2,
    texture.Height / 2);
    }
    [/code]
  5. The following method is the Update() method of the ImageMenuItem class, so let’s add its implementation code:
    [code]
    // Text menu item update method, get the tapped position on
    // screen
    public void Update(GameTime gameTime, Vector2 tapPosition)
    {
    // if the tapped position within the text menu item bound,
    // set Tap to true and trigger the OnTap event
    Tap = Bound.Contains((int)tapPosition.X,
    (int)tapPosition.Y);
    // Accumulate the game elapsed time
    timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
    }
    [/code]
  6. The last method of the ImageMenuItem class is the Draw() method, so let’s add its implementation code:
    [code]
    public void Draw(GameTime gameTime)
    {
    // Draw the text menu item
    if (Tap)
    {
    // if tap gesture is valid, gradually scale to
    // MaxScale in
    if (scale <= MaxScale && timer > 200)
    {
    scale += 0.1f;
    }
    spriteBatch.Draw(texture, Position, null, Color.Red,
    0f, Origin, scale, SpriteEffects.None, 0f);
    }
    else
    {
    // If no valid tap, gradually restore scale to
    // MinScale in every frame
    if (scale > MinScale && timer > 200)
    {
    scale -= 0.1f;
    }
    spriteBatch.Draw(texture, Position, null, Color.White,
    0f, Origin, scale, SpriteEffects.None, 0f);
    }
    }
    [/code]
  7. So far, we have seen the ImageMenuItem class. Our next job is to use the class in the main class. Add the code to the ImageMenuGame class:
    [code]
    // SpriteFont object for the current index value
    SpriteFont font;
    // The image menu item texture
    Texture2D texImageMenuItem;
    // The collection of image menu items
    List<ImageMenuItem> Menu;
    // The count of image menu items
    int TotalMenuItems = 4;
    // The index for every image menu item
    int index = 0;
    // Current index of tapped image menu item
    int currentIndex;
    [/code]
  8. Initialize the Menu object in the Initialize() method:
    [code]
    // Initialize Menu
    Menu = new List<ImageMenuItem>();
    [/code]
  9. Load the SpriteFont and ImageMenuItem texture, and initialize the ImageMenuItem in Menu. Next, insert the code to the LoadContent() method:
    [code]
    texImageMenuItem = Content.Load<Texture2D>(“ImageItem”);
    font = Content.Load<SpriteFont>(“gameFont”);
    // Initialize the image menu items
    int X = 150;
    int Y = 240;
    // Instance the image menu items horizontally
    for (int i = 0; i < TotalMenuItems; i++)
    {
    ImageMenuItem item = new ImageMenuItem(
    new Vector2(
    X + i * (texImageMenuItem.Width + 20), Y),
    texImageMenuItem, spriteBatch);
    item.Index = index++;
    Menu.Add(item);
    }
    [/code]
  10. In this step, we will check the valid tapped position and get the index of the tapped image menu item, paste the code to the Update() method:
    [code]
    // Get the tapped position
    Vector2 tapPosition = new Vector2();
    TouchCollection touches = TouchPanel.GetState();
    if (touches.Count > 0 && touches[0].State ==
    TouchLocationState.Pressed)
    {
    tapPosition = touches[0].Position;
    // Check the tapped positon whether inside one of the
    // image menu items
    foreach (ImageMenuItem item in Menu)
    {
    item.Update(gameTime, tapPosition);
    // Get the current index of tapped image menu item
    if (item.Tap)
    {
    currentIndex = item.Index;
    }
    }
    }
    [/code]
  11. Draw the menu and the current index value on the screen, and insert the lines to the Draw() method:
    [code]
    spriteBatch.Begin();
    // Draw the Menu
    foreach (ImageMenuItem item in Menu)
    {
    item.Draw(gameTime);
    }
    // Draw the current index on the top-left of screen
    spriteBatch.DrawString(font, “Current Index: “ +
    currentIndex.ToString(), new Vector2(0, 0), Color.White);
    spriteBatch.End();
    [/code]
  12. Now, build and run the application. Tap the second image menu item, and it should look similar to the following screenshot:
    image-based menu

How it works…

In step 2, spriteBatch renders the image of the menu item on screen; texture loads the graphic content of the image menu item; Position represents the position of every image menu item; Origin defines the center for menu item rotation and translation; Tap is the mark for valid menu item tapping; the timer variable accumulates the game elapsed time for changing the scale of the image menu item. The following two objects MinScale and MaxScale limit the range of scale changing; the scale variable indicates the current scale value of the menu item; index holds the sequential position of a menu.

In step 3, the Bound property returns a rectangle around the image of the menu item according to the menu item position and the image size.

In step 6, we will draw the visual effect for the menu item with image zoom in and zoom out. The first line accumulates the game elapsed time for changing the menu item scale. If the tapped position is inside the image menu item, the item will grow up gradually along with the increasing scale value until MaxScale; otherwise, it will restore to the initial state.

In step 7, the font object will render the current index of the image menu item on the top-left of the screen; texImageMenuItem holds the image of the menu item; Menu is the collection of ImageMenuItem; TotalMenuItems declares the total number of image menu items in Menu; index is used to mark the index of every menu item in Menu; the currentValue variable saves the index of the tapped image menu item.

In step 9, we instance the image menu items horizontally and define the gap between each of the items at about 20 pixels wide.

In step 10, after calling the ImageMenuItem.Update() method, you can get the current index of the menu item when its Tap value is true.

Creating a 3D model-based menu

Text- or image-based menus are very common in games. As you know, they are both in 2D, but sometimes you want some exceptional effects for menus in 3D, such as rotating. 3D menus render the model as a menu item for any 3D transformation. They offer a way to implement your own innovative menu presentation in 3D. This recipe will specify the interesting technique in Windows Phone 7.

Getting ready

Programming the 3D model-based menu is an amazing adventure. You can use the 3D model rendering and transformation techniques to control the menu items and control the camera at different positions, getting closer or looking like a bird. As a demo, the model menu item of menu will pop up when selected. I hope this recipe will impress you. Let’s look into the code.

How to do it…

The following steps will lead you to build an impressive 3D model-based menu:

  1. Create the Windows Phone Game project named ModelMenu3D, change Game1. cs to ModelMenuGame.cs. Add a ModelMenuItem.cs to the project; gameFont. spriteFont and ModelMenuItem3D.FBX to the content project.
  2. Create the ModelMenuItem class in ModelMenuItem.cs. Add the code to its field:
    [code]
    // Model of menu item
    Model modelItem;
    // Translation of model menu item
    public Vector3 Translation;
    // The view and projection of camera for model view item
    public Matrix View;
    public Matrix Projection;
    // The index of model menu item
    public int Index;
    // The mark for selection
    public bool Selected;
    // The offset from menu item original position when selected
    public int Offset;
  3. Next, define the constructor of the ModelMenuItem class and set the default offset of the selected model menu item.
    [code]
    // Constructor
    public ModelMenuItem(Model model, Matrix view, Matrix
    projection)
    {
    modelItem = model;
    View = view;
    Projection = projection;
    Offset = 5;
    }
    [/code]
  4. This step is to give the definition of the Draw() method of the ModelMenuItem class:
    [code]
    // Draw the model menu item
    public void Draw()
    {
    Matrix[] modelTransforms = new
    Matrix[modelItem.Bones.Count];
    modelItem.CopyAbsoluteBoneTransformsTo(modelTransforms);
    foreach (ModelMesh mesh in modelItem.Meshes)
    {
    foreach (BasicEffect effect in mesh.Effects)
    {
    // Enable lighting
    effect.EnableDefaultLighting();
    // Set the ambient light color to white
    effect.AmbientLightColor = Color.White.ToVector3();
    if (Selected)
    {
    // If the item is not selected, it restores
    // to the original state
    effect.World =
    modelTransforms[mesh.ParentBone.Index]
    * Matrix.CreateTranslation(Translation +
    new Vector3(0,0, Offset));
    }
    else
    {
    // If the item is selected, it stands out
    effect.World =
    modelTransforms[mesh.ParentBone.Index]
    * Matrix.CreateTranslation(Translation);
    }
    effect.View = View;
    effect.Projection = Projection;
    }
    mesh.Draw();
    }
    }
    [/code]
  5. Use the ModelMenuItem class in our game. Add the code to the field of the ModelMenuGame class:
    [code]
    // Sprite font object
    SpriteFont font;
    // Model of menu Item
    Model menuItemModel;
    // Camera position
    Vector3 cameraPositon;
    // Camera view and projection matrices
    Matrix view;
    Matrix projection;
    // The collection of Model Menu items
    List<ModelMenuItem> Menu;
    // The count of model menu items in Menu
    int TotalMenuItems = 4;
    // The left and right hit regions for menu item selection
    Rectangle LeftRegion;
    Rectangle RightRegion;
    // Current index of model menu item in Menu
    int currentIndex = 0;
    // Event handler of hit regions
    public event EventHandler OnTap;
    [/code]
  6. Initialize the camera, menu, and hit regions. Insert the code to the Initialize() method:
    [code]
    // Define the camera position
    cameraPositon = new Vector3(-40, 10, 40);
    // Define the camera view and projection matrices
    view = Matrix.CreateLookAt(cameraPositon, Vector3.Zero,
    Vector3.Up);
    projection =
    Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
    GraphicsDevice.Viewport.AspectRatio, 1.0f, 1000.0f);
    // Initialize the Menu object
    Menu = new List<ModelMenuItem>();
    // Left hit region occupies the left half of screen
    LeftRegion = new Rectangle(
    0, 0,
    GraphicsDevice.Viewport.Width / 2,
    GraphicsDevice.Viewport.Height);
    // Right hit region occupies the right half of screen
    RightRegion = new Rectangle(GraphicsDevice.Viewport.Width / 2,
    0, GraphicsDevice.Viewport.Width / 2,
    GraphicsDevice.Viewport.Height);
    // Define the event handler OnTap with the delegate method
    OnTap = new EventHandler(item_OnTap);
    [/code]
  7. Next, define the reaction method item_OnTap of the OnTap event:
    [code]
    // Make the current index value change within the range of
    // total menu items
    currentIndex = currentIndex % TotalMenuItems;
    // If the current index is less than 0, set it to the last item
    if (currentIndex < 0)
    {
    // From the last item
    currentIndex = TotalMenuItems – 1;
    }
    // if the current index is greater than the last index, set it
    to the
    // first item
    else if (currentIndex > TotalMenuItems – 1)
    {
    // From the beginning item;
    currentIndex = 0;
    }
    // Select the menu item, of which the index equals the
    // current index
    foreach (ModelMenuItem item in Menu)
    {
    if (item.Index == currentIndex)
    {
    item.Selected = true;
    }
    else
    {
    item.Selected = false;
    }
    }
    [/code]
  8. Load the game content and initialize the menu items of Menu. Insert the code in the Initialize() method:
    [code]
    // Load and initialize the model and font objects
    menuItemModel = Content.Load<Model>(“ModelMenuItem3D”);
    font = Content.Load<SpriteFont>(“gameFont”);
    // Initialize the model menu items in Menu horizontally
    for (int i = 0; i < TotalMenuItems; i++)
    {
    int X = -20;
    ModelMenuItem item = new ModelMenuItem(
    menuItemModel, view,
    projection);
    item.Translation = new Vector3(X + (i * 20), 0, 0);
    // Set the index of menu item
    item.Index = i;
    Menu.Add(item);
    }
    // Setting the first menu item to be selected by default
    Menu[0].Selected = true;
    [/code]
  9. In this step, we make the current index value react to the tap on the hit regions. Paste the code to the Update() method:
    [code]
    // Get the tapped position
    Vector2 tapPosition = new Vector2();
    TouchCollection touches = TouchPanel.GetState();
    if (touches.Count > 0 && touches[0].State ==
    TouchLocationState.Pressed)
    {
    tapPosition = touches[0].Position;
    Point point = new Point((int)tapPosition.X,
    (int)tapPosition.Y);
    // Check the tapped position whether in the left region
    if (LeftRegion.Contains(point))
    {
    // If yes, decrease the current index
    –currentIndex;
    OnTap(this, null);
    }
    // Check the tapped position whether in the right region
    else if (RightRegion.Contains(point))
    {
    // If yes, increase the current index
    ++currentIndex;
    OnTap(this, null);
    }
    }
    [/code]
  10. The last step is to draw the menu on screen. Insert the code to the Draw() method:
    [code]
    // The following three lines are to ensure that the models are
    // drawn correctly
    GraphicsDevice.DepthStencilState = DepthStencilState.Default;
    GraphicsDevice.BlendState = BlendState.AlphaBlend;
    // Draw the Menu
    foreach (ModelMenuItem item in Menu)
    {
    item.Draw();
    }
    // Draw the current index on the top-left of screen
    spriteBatch.Begin();
    spriteBatch.DrawString(font, “Current Index: “ +
    currentIndex.ToString(), new Vector2(0, 0), Color.White);
    spriteBatch.End();
    [/code]
  11. Now, build and run the application. When you tap the right region, the second model menu will pop up, as shown in the following screenshot:
    3D model-based

How it works…

In step 2, the modelitem holds the model object of the menu item; Translation stores the world position of the model menu item; View and Projection stand for the view and projection matrices of the camera respectively; index saves the index of the menu item in the menu; Selected indicates the selection state of the model menu item; Offset is the offset value from the model menu item’s original position.

In step 4 and in the iteration of Mesh.Effects, we enable the light through calling the Effect.EnableDefaultLighting() method and set the Effect.AmbientLightColor to Color.White.ToVector3(). Notice, for popping up the model menu item, we create the translation matrix with positive 5 units offset at the Z-axis from the original position. If a menu is selected, it will pop up, otherwise, it will go back or remain in the initial state.

In step 5, font object will be used to draw the current index value on the top-left on screen; menuModel stores the model object for model menu item; cameraPosition defines the position of the camera; view and projection are the matrices for camera view and projection respectively; Menu is the collection of model menu items; TotalMenuItems indicates the total number of menu items; LeftRegion and RightRegion are the areas for item choosing.

In step 6, the first part of the code before is to define the camera; the second part is about initializing the left and right hit regions. LeftRegion takes up the left half of the screen; RightRegion occupies the other half.

In step 7, the first line is responsible for making the currentIndex value not less than -1 and greater than TotalMenuItems. Next, if the current index is less than 0, the last menu item will be selected; otherwise, if the current index is greater than TotalMenuItems minus 1, the first item will pop up. The following foreach loop checks which item is currently selected when its index is equal to the current index.

In step 8, the first two lines are to load the menu item model and the font for presenting the current index. The following for loop initializes the menu items of Menu horizontally and assigns i value to the item index. The last line sets the first selected item.

In step 9, this block of code first gets the tapped position on screen. If the tapped position is in the left region, the current index decreases by 1, else, current index increases by 1. Any valid tap on the regions will trigger the OnTap event.

Paint (Ink Presenter)

Paint is a classic finger-painting app, but with several powerful options:

  • You can paint on top of any color canvas or a photo from your pictures library and paint with multiple fingers simultaneously (naturally).
  • In addition to using the rich color picker shared by many apps in this book, Paint provides many options for customizing the brush strokes.
  • You can undo and redo your strokes to get them just right.
  • A “stroke straightening” feature can help you create more precise artwork, either by straightening your diagonal lines or by snapping your lines to be completely vertical/horizontal.
  • Save your masterpieces to your phone’s pictures library.

Paint uses multi-touch the same way as the preceding chapter’s Musical Robot app, but it applies the data to an interesting element worth knowing about—ink presenter.

An ink presenter holds a collection of objects known as strokes that are meant to represent handwriting. Each stroke contains a collection of points that are connected to form each one. Each stroke also exposes a DrawingAttributes object with four properties: Color, OutlineColor, Width, and Height. Therefore, this app’s main drawing surface is simply an ink presenter whose strokes are added based on the touch data and settings chosen for the DrawingAttributes object.

Paint has two pages (in addition to standard instructions and about pages not shown in this chapter)—the main page containing the drawing surface and a palette page for adjusting the brush settings. This chapter begins by examining the palette page first, as it uses an ink presenter in a simpler fashion.

The Palette Page

 The palette page exposes a way to change each of the four properties on DrawingAttributes.
FIGURE 39.1 The palette page exposes a way to change each of the four properties on DrawingAttributes.

Paint’s palette page, pictured in Figure 39.1, enables changing each of the properties on the DrawingAttributes object. It links to this book’s shared color picker for the main color as well as the optional outline color, and exposes two sliders for independently controlling a stroke’s width and height.

The page has a hard-coded stroke that demonstrates how the different settings affect the resulting strokes as the user changes them. This is especially helpful for visualizing width and height changes, as shown in Figure 39.2.

Demonstrating every combination of the minimum and maximum brush sizes.
FIGURE 39.2 Demonstrating every combination of the minimum and maximum brush sizes.

The User Interface

Listing 39.1 contains the XAML for the palette page.

LISTING 39.1 PalettePage.xaml—The User Interface for Paint’s Palette Page

[code]

<phone:PhoneApplicationPage x:Class=”WindowsPhoneApp.PalettePage”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:phone=”clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone”
xmlns:shell=”clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone”
xmlns:local=”clr-namespace:WindowsPhoneApp”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”Portrait” shell:SystemTray.IsVisible=”True”>
<Canvas>
<!– The standard header –>
<StackPanel Style=”{StaticResource PhoneTitlePanelStyle}”>
<TextBlock Text=”PAINT” Style=”{StaticResource PhoneTextTitle0Style}”/>
<TextBlock Text=”palette” Style=”{StaticResource PhoneTextTitle1Style}”/>
</StackPanel>
<!– The translucent foreground-colored palette image –>
<Rectangle Canvas.Left=”6” Width=”474” Height=”632” Opacity=”.6”
Fill=”{StaticResource PhoneForegroundBrush}”>
<Rectangle.OpacityMask>
<ImageBrush ImageSource=”Images/paletteBackground.png”/>
</Rectangle.OpacityMask>
</Rectangle>
<!– The InkPresenter with a single 5-point stroke –>
<InkPresenter x:Name=”PreviewInkPresenter” Canvas.Left=”236” Canvas.Top=”220”>
<InkPresenter.Strokes>
<Stroke>
<Stroke.StylusPoints>
<StylusPoint X=”100” Y=”0”/>
<StylusPoint X=”0” Y=”0”/>
<StylusPoint X=”80” Y=”80”/>
<StylusPoint X=”0” Y=”120”/>
<StylusPoint X=”0” Y=”170”/>
</Stroke.StylusPoints>
</Stroke>
</InkPresenter.Strokes>
</InkPresenter>
<!– Paint color –>
<TextBlock Text=”Paint color” Canvas.Left=”84” Canvas.Top=”431” FontSize=”23”
Foreground=”{StaticResource PhoneBackgroundBrush}”/>
<Ellipse x:Name=”PaintColorEllipse” Canvas.Left=”78” Canvas.Top=”305”
Width=”120” Height=”120” Stroke=”{StaticResource PhoneBackgroundBrush}”
StrokeThickness=”10” local:Tilt.IsEnabled=”True”
MouseLeftButtonUp=”PaintColorEllipse_MouseLeftButtonUp”/>
<!– Outline color –>
<CheckBox x:Name=”OutlineCheckBox” Canvas.Left=”210” Canvas.Top=”521”
Foreground=”{StaticResource PhoneBackgroundBrush}”
local:Tilt.IsEnabled=”True” Checked=”OutlineCheckBox_IsCheckedChanged”
Unchecked=”OutlineCheckBox_IsCheckedChanged”>
<TextBlock FontSize=”23” Foreground=”{StaticResource PhoneBackgroundBrush}”>
Outline<LineBreak/>color
</TextBlock>
</CheckBox>
<Ellipse x:Name=”OutlineColorEllipse” Canvas.Left=”213” Canvas.Top=”414”
Width=”120” Height=”120” Stroke=”{StaticResource PhoneBackgroundBrush}”
StrokeThickness=”10” local:Tilt.IsEnabled=”True”
MouseLeftButtonUp=”OutlineColorEllipse_MouseLeftButtonUp”/>
<!– Brush width –>
<TextBlock Text=”Brush width” Canvas.Left=”35” Canvas.Top=”660”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
<Slider x:Name=”BrushWidthSlider” Canvas.Left=”24” Canvas.Top=”680”
Minimum=”2” Maximum=”55” Width=”203”
ValueChanged=”BrushWidthSlider_ValueChanged”/>
<!– Brush height –>
<TextBlock Text=”Brush height” Canvas.Left=”263” Canvas.Top=”660”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
<Slider x:Name=”BrushHeightSlider” Canvas.Left=”252” Canvas.Top=”680”
Minimum=”2” Maximum=”55” Width=”203”
ValueChanged=”BrushHeightSlider_ValueChanged”/>
</Canvas>
</phone:PhoneApplicationPage>

[/code]

  • The ink presenter’s collection of strokes contains just one 5-point stroke. The stroke doesn’t specify any explicit drawing attributes because those are set in code-behind. By default, a stroke is given a width and height of 3, a color of black, and an outline color of transparent.
  • The StylusPoint objects that must be used to define a stroke are just like Point objects, but they have one additional property—PressureFactor—that unfortunately has no effect on Windows Phone.
  • The slider-enforced minimum and maximum width/height values of 2 and 55, respectively, are arbitrary. The corresponding properties on DrawingAttributes can be set to any nonnegative double.

Can I change the thickness of a stroke’s outline?

No, it is always a thin, approximately 1-pixel border.However, you could mimic a thicker border by rendering a duplicate stroke with a larger width and height behind each “real” stroke. It turns out that this is exactly how the outline color is rendered anyway, which you can see by giving the stroke a translucent or transparent color.This is demonstrated in Figure 39.3, which uses a translucent white stroke color and a green outline color.

A stroke outline is really just a slightly larger stroke underneath, which can be seen when a translucent paint color is used.
FIGURE 39.3 A stroke outline is really just a slightly larger stroke underneath, which can be seen when a translucent paint color is used.

What’s the difference between an ink presenter with strokes and a path with polylines?

Either of these two elements can be used for an app like Paint, as the differences between them are subtle.The main reason to prefer an ink presenter is that it performs better for the large number of points that are generated by finger movement. It’s also slightly easier to serialize its strokes so they can be saved and then later restored.

A path is more powerful because it can express mathematical curves between any two points, whereas each stroke’s stylus points are connected with straight lines. (There are just so many points when plotting finger movement that you don’t normally notice the connections.) It also supports arbitrary brushes (like gradient or image brushes instead of a solid color), and you can leverage its fill and stroke properties to either fill in a closed shape or to provide a true border of any thickness.

An ink presenter is more powerful because it can contain arbitrary UI elements in addition to strokes. (That’s because InkPresenter derives from Canvas.) It also holds the promise of easily enabling pressure-sensitive painting, as each stylus point exposes a PressureFactor property that can be set to a value from 0 to 1. However, given that setting this property currently has no effect on Windows Phone, and touch points never report how hard a finger is pressing the screen, this advantage is only a theoretical one for the future.

The Code-Behind

Listing 39.2 contains the code-behind for the palette page.

LISTING 39.2 PalettePage.xaml.cs—The Code-Behind for Paint’s Palette Page

[code]

using System;
using System.Windows;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
namespace WindowsPhoneApp
{
public partial class PalettePage : PhoneApplicationPage
{
public PalettePage()
{
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Respect the saved settings
this.PaintColorEllipse.Fill =
new SolidColorBrush(Settings.PaintColor.Value);
this.OutlineColorEllipse.Fill =
new SolidColorBrush(Settings.OutlineColor.Value);
this.OutlineCheckBox.IsChecked = Settings.HasOutline.Value;
this.BrushWidthSlider.Value = Settings.BrushWidth.Value;
this.BrushHeightSlider.Value = Settings.BrushHeight.Value;
// Update the ink presenter with the current settings
DrawingAttributes attributes =
this.PreviewInkPresenter.Strokes[0].DrawingAttributes;
attributes.Color = Settings.PaintColor.Value;
attributes.Width = Settings.BrushWidth.Value;
attributes.Height = Settings.BrushHeight.Value;
if (Settings.HasOutline.Value)
attributes.OutlineColor = Settings.OutlineColor.Value;
else
attributes.OutlineColor = Colors.Transparent; // Hide the outline
}
void PaintColorEllipse_MouseLeftButtonUp(object sender,
MouseButtonEventArgs e)
{
// Get a string representation of the colors we need to pass to the color
// picker, without the leading #
string currentColorString =
Settings.PaintColor.Value.ToString().Substring(1);
string defaultColorString =
Settings.PaintColor.DefaultValue.ToString().Substring(1);
// The color picker works with the same isolated storage value that the
// Setting works with, but we have to clear its cached value to pick up
// the value chosen in the color picker
Settings.PaintColor.ForceRefresh();
// Navigate to the color picker
this.NavigationService.Navigate(new Uri(
“/Shared/Color Picker/ColorPickerPage.xaml?”
+ “&currentColor=” + currentColorString
+ “&defaultColor=” + defaultColorString
+ “&settingName=PaintColor”, UriKind.Relative));
}
void OutlineColorEllipse_MouseLeftButtonUp(object sender,
MouseButtonEventArgs e)
{
// Get a string representation of the colors, without the leading #
string currentColorString =
Settings.OutlineColor.Value.ToString().Substring(1);
string defaultColorString =
Settings.OutlineColor.DefaultValue.ToString().Substring(1);
// The color picker works with the same isolated storage value that the
// Setting works with, but we have to clear its cached value to pick up
// the value chosen in the color picker
Settings.OutlineColor.ForceRefresh();
// Navigate to the color picker
this.NavigationService.Navigate(new Uri(
“/Shared/Color Picker/ColorPickerPage.xaml?”
+ “showOpacity=true”
+ “&currentColor=” + currentColorString
+ “&defaultColor=” + defaultColorString
+ “&settingName=OutlineColor”, UriKind.Relative));
}
void OutlineCheckBox_IsCheckedChanged(object sender, RoutedEventArgs e)
{
// Toggle the outline
Settings.HasOutline.Value = this.OutlineCheckBox.IsChecked.Value;
if (Settings.HasOutline.Value)
this.PreviewInkPresenter.Strokes[0].DrawingAttributes.OutlineColor =
Settings.OutlineColor.Value;
else
this.PreviewInkPresenter.Strokes[0].DrawingAttributes.OutlineColor =
Colors.Transparent;
}
void BrushWidthSlider_ValueChanged(object sender,
RoutedPropertyChangedEventArgs<double> e)
{
if (this.BrushWidthSlider != null) // Ignore during XAML parsing
{
Settings.BrushWidth.Value = (int)this.BrushWidthSlider.Value;
this.PreviewInkPresenter.Strokes[0].DrawingAttributes.Width =
Settings.BrushWidth.Value;
}
}
void BrushHeightSlider_ValueChanged(object sender,
RoutedPropertyChangedEventArgs<double> e)
{
if (this.BrushHeightSlider != null) // Ignore during XAML parsing
{
Settings.BrushHeight.Value = (int)this.BrushHeightSlider.Value;
this.PreviewInkPresenter.Strokes[0].DrawingAttributes.Height =
Settings.BrushHeight.Value;
}
}
}
}

[/code]

  • This app uses the following settings defined in a separate Settings.cs file:
    [code]
    public static class Settings
    {
    // Drawing attributes for strokes
    public static readonly Setting<Color> PaintColor = new Setting<Color>(
    “PaintColor”, (Color)Application.Current.Resources[“PhoneAccentColor”]);
    public static readonly Setting<Color> OutlineColor =
    new Setting<Color>(“OutlineColor”, Colors.Black);
    public static readonly Setting<bool> HasOutline =
    new Setting<bool>(“HasOutline”, false);
    public static readonly Setting<int> BrushWidth =
    new Setting<int>(“BrushWidth”, 10);
    public static readonly Setting<int> BrushHeight =
    new Setting<int>(“BrushHeight”, 10);
    // Background color
    public static readonly Setting<Color> PageColor =
    new Setting<Color>(“PageColor”, Colors.White);
    }
    [/code]
    All but the last one are modified by this page.
  • To update the stroke with all the current values, this code simply retrieves the 0th element of the ink presenter’s Strokes collection.
  • When the user turns off the outline color (by unchecking the check box), the outline color is set to transparent. This is the only way to prevent the outline color from interfering with the size of the stroke and even the color of the stroke if the main color is translucent.

The Main Page

Paint’s main page is nothing more than a drawing surface and an application bar with several available actions. As demonstrated in Figure 39.4, although the application bar adjusts for the current orientation, the artwork remains fixed relative to the screen. Having the artwork rotate would be problematic, as the page size would effectively change. Having the application bar rotate, however, is a nice touch when doing landscape- oriented artwork.

The application bar rotates according to the current orientation, but the artwork does not (relative to the physical screen).
FIGURE 39.4 The application bar rotates according to the current orientation, but the artwork does not (relative to the physical screen).

When designing this app, I wanted the palette button on the application bar to be colored with the current paint color as a helpful visual aid. However, it’s not currently possible to emit dynamic images to be used by application bar buttons. Therefore, I decided to update the application bar’s background color with the current paint color as the next best thing. In Figure 39.4, the current paint color is a light, translucent blue.

The User Interface

Listing 39.3 contains the XAML for the main page.

LISTING 39.3 MainPage.xaml—The User Interface for Paint’s Main Page

[code]

<phone:PhoneApplicationPage x:Class=”WindowsPhoneApp.MainPage”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:phone=”clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone”
xmlns:shell=”clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone”
SupportedOrientations=”PortraitOrLandscape”>
<!– The application bar, and that’s it! –>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar>
<shell:ApplicationBarIconButton Text=”palette”
IconUri=”/Images/appbar.palette.png” Click=”PaletteButton_Click”/>
<shell:ApplicationBarIconButton Text=”undo”
IconUri=”/Shared/Images/appbar.undo.png” Click=”UndoButton_Click”/>
<shell:ApplicationBarIconButton Text=”redo”
IconUri=”/Shared/Images/appbar.redo.png” Click=”RedoButton_Click”/>
<shell:ApplicationBarIconButton Text=”straighten”
IconUri=”/Images/appbar.straighten1.png” Click=”StraightenButton_Click”/>
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text=”set background color”
Click=”SetBackgroundColorMenuItem_Click”/>
<shell:ApplicationBarMenuItem Text=”set background picture”
Click=”SetBackgroundPictureMenuItem_Click”/>
<shell:ApplicationBarMenuItem Text=”erase all strokes”
Click=”EraseMenuItem_Click”/>
<shell:ApplicationBarMenuItem Text=”save to pictures library”
Click=”SaveToPicturesLibraryMenuItem_Click”/>
<shell:ApplicationBarMenuItem Text=”instructions”
Click=”InstructionsMenuItem_Click”/>
<shell:ApplicationBarMenuItem Text=”about”
Click=”AboutMenuItem_Click”/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
</phone:PhoneApplicationPage>

[/code]

This XAML file has the distinction of being the only one in this book where the page has no content! It only sets the values of its SupportedOrientations and ApplicationBar properties. That’s because the content shown on main page is created from code-behind and placed in a frame-rooted popup. This is what enables the behavior demonstrated 39.4, in which the application bar rotates but the content does not.

The Code-Behind

Listing 39.4 contains the code-behind for the main page.

LISTING 39.4 MainPage.xaml.cs—The Code-Behind for Paint’s Main Page

[code]

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using Microsoft.Phone;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using Microsoft.Phone.Tasks;
namespace WindowsPhoneApp
{
public partial class MainPage : PhoneApplicationPage
{
// Undo and redo stacks
Stack<HistoryEntry> undoStack = new Stack<HistoryEntry>();
Stack<HistoryEntry> redoStack = new Stack<HistoryEntry>();
// The in-progress strokes, tracked separately for each unique finger
Dictionary<int, Stroke> fingerStrokes = new Dictionary<int, Stroke>();
// The popup and its contents
Popup popup = new Popup { IsOpen = true };
Grid grid = new Grid { Width = 480, Height = 800 };
InkPresenter inkPresenter = new InkPresenter();
Image backgroundImage = new Image {
Stretch = Stretch.Uniform, RenderTransformOrigin = new Point(.5, .5),
RenderTransform = new CompositeTransform()
};
// Application bar buttons and a menu item that are changed by code-behind
IApplicationBarIconButton undoButton;
IApplicationBarIconButton redoButton;
IApplicationBarIconButton straightenButton;
IApplicationBarMenuItem backgroundPictureMenuItem;
public MainPage()
{
InitializeComponent();
// Assign the application bar items
this.undoButton = this.ApplicationBar.Buttons[1]
as IApplicationBarIconButton;
this.redoButton = this.ApplicationBar.Buttons[2]
as IApplicationBarIconButton;
this.straightenButton = this.ApplicationBar.Buttons[3]
as IApplicationBarIconButton;
this.backgroundPictureMenuItem = this.ApplicationBar.MenuItems[1]
as IApplicationBarMenuItem;
// Restore the background image, if persisted previously
if (IsolatedStorageHelper.FileExists(“background.jpg”))
SetBackgroundImage(IsolatedStorageHelper.LoadImageFile(“background.jpg”));
// Restore the strokes, if persisted previously.
// These are stored in a file rather than isolated storage settings due to
// a problem with the default serializer.
StrokeCollection strokes =
IsolatedStorageHelper.LoadSerializedObjectFromFile(“strokes.xml”,
typeof(StrokeCollection)) as StrokeCollection;
if (strokes != null)
this.inkPresenter.Strokes = strokes;
// Refresh the app bar based on the presence of a background image & strokes
RefreshAppBarMenu();
RefreshAppBarButtons();
// Attach the UI to the popup, which is already showing (IsOpen=true)
this.grid.Children.Add(this.backgroundImage);
this.grid.Children.Add(this.inkPresenter);
this.popup.Child = this.grid;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Need to hide the popup so the other page can be shown!
this.popup.IsOpen = false;
// Unsubscribe from this application-wide event
Touch.FrameReported -= Touch_FrameReported;
// Persist the current strokes.
// These are stored in a file rather than isolated storage settings due to
// a problem with the default serializer.
IsolatedStorageHelper.SaveFile(“strokes.xml”, this.inkPresenter.Strokes);
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Ensure the popup is shown, as it gets hidden when navigating away
this.popup.IsOpen = true;
// Reapply the background color, in case we just returned
// from the color picker page
this.grid.Background = new SolidColorBrush(Settings.PageColor.Value);
// Apply the current paint color as the app bar background color
Color paintColor = Settings.PaintColor.Value;
// Prevent the background from getting too transparent,
// potentialy making the buttons and menu items unreadable
if (paintColor.A < 60)
paintColor.A = 60;
this.ApplicationBar.BackgroundColor = paintColor;
// Choose a foreground color that will be visible over the background color
if (IsLight(Settings.PaintColor.Value))
this.ApplicationBar.ForegroundColor = Colors.Black;
else
this.ApplicationBar.ForegroundColor = Colors.White;
// Subscribe to the touch/multi-touch event.
// This is application-wide, so only do this when on this page.
Touch.FrameReported += Touch_FrameReported;
}
void Touch_FrameReported(object sender, TouchFrameEventArgs e)
{
// Get all touch points
TouchPointCollection points = e.GetTouchPoints(this.inkPresenter);
// Process each touch point individually
foreach (TouchPoint point in points)
{
// The “touch device” is each finger, and it has a unique ID
int fingerId = point.TouchDevice.Id;
if (point.Action == TouchAction.Down)
{
// Start a new stroke
Stroke stroke = new Stroke();
// Apply all the current settings
stroke.DrawingAttributes.Color = Settings.PaintColor.Value;
stroke.DrawingAttributes.Width = Settings.BrushWidth.Value;
stroke.DrawingAttributes.Height = Settings.BrushHeight.Value;
if (Settings.HasOutline.Value)
stroke.DrawingAttributes.OutlineColor = Settings.OutlineColor.Value;
else
stroke.DrawingAttributes.OutlineColor = Colors.Transparent;
// The first point of this stroke is the current finger position
stroke.StylusPoints.Add(
new StylusPoint(point.Position.X, point.Position.Y));
// Track which finger this stroke belongs to
this.fingerStrokes[fingerId] = stroke;
// Add it to the ink presenter’s collection of strokes
this.inkPresenter.Strokes.Add(stroke);
}
else if (point.Action == TouchAction.Move)
{
// Keep adding new points to the stroke
if (this.fingerStrokes.ContainsKey(fingerId))
this.fingerStrokes[fingerId].StylusPoints.Add(
new StylusPoint(point.Position.X, point.Position.Y));
}
else // TouchAction.Up
{
// The stroke is finished
if (this.fingerStrokes.ContainsKey(fingerId))
{
// Enable this action to be undone
this.undoStack.Push(
new HistoryEntry { StrokeAdded = this.fingerStrokes[fingerId] });
this.redoStack.Clear();
// Stop tracking this stroke
this.fingerStrokes.Remove(fingerId);
// Refresh the state of the undo/redo/straighten buttons
RefreshAppBarButtons();
}
}
}
}
bool IsLight(Color color)
{
return ((color.R + color.G + color.B) / 3 > 127.5);
}
void SetBackgroundImage(ImageSource source)
{
this.backgroundImage.Source = source;
// The ImageOpened event doesn’t get raised after this, but the values for
// ActualWidth and ActualHeight aren’t correct yet. The BeginInvoke enables
// us to retrieve the values.
this.Dispatcher.BeginInvoke(delegate()
{
// Rotate the image based on whether it’s landscape or portrait
if (this.backgroundImage.ActualWidth > this.backgroundImage.ActualHeight)
{
this.backgroundImage.Width = 800;
this.backgroundImage.Margin = new Thickness((480 – 800) / 2, 0, 0, 0);
(this.backgroundImage.RenderTransform as CompositeTransform).Rotation =
90;
}
else
{
this.backgroundImage.Width = 480;
this.backgroundImage.Margin = new Thickness(0, 0, 0, 0);
(this.backgroundImage.RenderTransform as CompositeTransform).Rotation =
0;
}
});
}
// Update the state of the application bar menu
void RefreshAppBarMenu()
{
if (IsolatedStorageHelper.FileExists(“background.jpg”))
this.backgroundPictureMenuItem.Text = “remove background picture”;
else
this.backgroundPictureMenuItem.Text = “set background picture”;
}
// Update the state of the application bar buttons
void RefreshAppBarButtons()
{
this.undoButton.IsEnabled = (this.undoStack.Count > 0);
this.redoButton.IsEnabled = (this.redoStack.Count > 0);
this.straightenButton.IsEnabled = (this.inkPresenter.Strokes.Count > 0);
// Customize the straighten button icon based on the last stroke’s shape
if (this.inkPresenter.Strokes.Count > 0)
{
Stroke lastStroke =
this.inkPresenter.Strokes[this.inkPresenter.Strokes.Count – 1];
if (lastStroke.StylusPoints.Count > 2)
this.straightenButton.IconUri =
new Uri(“/Images/appbar.straighten1.png”, UriKind.Relative);
else
this.straightenButton.IconUri =
new Uri(“/Images/appbar.straighten2.png”, UriKind.Relative);
}
}
// Application bar button handlers
void PaletteButton_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(
new Uri(“/PalettePage.xaml”, UriKind.Relative));
}
void UndoButton_Click(object sender, EventArgs e)
{
if (this.undoStack.Count == 0)
return;
// Get the previous action
HistoryEntry entry = this.undoStack.Pop();
// If a stroke was added, remove it
if (entry.StrokeAdded != null)
this.inkPresenter.Strokes.Remove(entry.StrokeAdded);
// If strokes were removed, add them back
if (entry.StrokesRemoved != null)
foreach (Stroke s in entry.StrokesRemoved)
this.inkPresenter.Strokes.Add(s);
// Enable the undo to be undone
this.redoStack.Push(entry);
// Update the state of the undo/redo/straighten buttons
RefreshAppBarButtons();
}
void RedoButton_Click(object sender, EventArgs e)
{
if (this.redoStack.Count == 0)
return;
// Get the action that was just undone
HistoryEntry entry = this.redoStack.Pop();
// If a stroke was added, add it back
if (entry.StrokeAdded != null)
this.inkPresenter.Strokes.Add(entry.StrokeAdded);
// If strokes were removed, remove them again
if (entry.StrokesRemoved != null)
foreach (Stroke s in entry.StrokesRemoved)
this.inkPresenter.Strokes.Remove(s);
// Enable this action to be undone
this.undoStack.Push(entry);
// Update the state of the undo/redo/straighten buttons
RefreshAppBarButtons();
}
void StraightenButton_Click(object sender, EventArgs e)
{
if (this.inkPresenter.Strokes.Count == 0)
return;
bool straightened = false;
Stroke lastStroke =
this.inkPresenter.Strokes[this.inkPresenter.Strokes.Count – 1];
// Clone the stroke before changing it, simply so the original stroke
// can be placed in the undo stack.
// The DrawingAttributes instance is shared by both, but we don’t change it.
Stroke newStroke = new Stroke { DrawingAttributes =
lastStroke.DrawingAttributes };
foreach (StylusPoint point in lastStroke.StylusPoints)
newStroke.StylusPoints.Add(point);
if (newStroke.StylusPoints.Count > 2)
{
// This is a raw stroke, so do the first round of straightening simply
// by removing every point except its two endpoints
while (newStroke.StylusPoints.Count > 2)
newStroke.StylusPoints.RemoveAt(1);
straightened = true;
}
else if (newStroke.StylusPoints.Count == 2)
{
// This is already a straight line, so make it completely horizontal or
// completely vertical depending on which is closer
double deltaX = newStroke.StylusPoints[0].X – newStroke.StylusPoints[1].X;
double deltaY = newStroke.StylusPoints[0].Y – newStroke.StylusPoints[1].Y;
if (Math.Abs(deltaX) > Math.Abs(deltaY))
{
// The line is more horizontal than vertical
if (newStroke.StylusPoints[0].Y != newStroke.StylusPoints[1].Y)
{
// Give the horizontal line the average Y value
double newY = (newStroke.StylusPoints[0].Y +
newStroke.StylusPoints[1].Y) / 2;
newStroke.StylusPoints[0] =
new StylusPoint(newStroke.StylusPoints[0].X, newY);
newStroke.StylusPoints[1] =
new StylusPoint(newStroke.StylusPoints[1].X, newY);
straightened = true;
}
}
else
{
// The line is more vertical than horizontal
if (newStroke.StylusPoints[0].X != newStroke.StylusPoints[1].X)
{
// Give the vertical line the average X value
double newX = (newStroke.StylusPoints[0].X +
newStroke.StylusPoints[1].X) / 2;
newStroke.StylusPoints[0] =
new StylusPoint(newX, newStroke.StylusPoints[0].Y);
newStroke.StylusPoints[1] =
new StylusPoint(newX, newStroke.StylusPoints[1].Y);
straightened = true;
}
}
}
if (straightened)
{
// Remove the old stroke and swap in the cloned and modified stroke
this.inkPresenter.Strokes.Remove(lastStroke);
this.inkPresenter.Strokes.Add(newStroke);
// Update the undo/redo stacks
HistoryEntry entry = new HistoryEntry { StrokeAdded = newStroke };
entry.StrokesRemoved = new Stroke[] { lastStroke };
this.undoStack.Push(entry);
this.redoStack.Clear();
// Update the state of the undo/redo/straighten buttons
RefreshAppBarButtons();
}
}
// Application bar menu handlers
void SetBackgroundColorMenuItem_Click(object sender, EventArgs e)
{
// Get a string representation of the colors, without the leading #
string currentColorString = Settings.PageColor.Value.ToString().Substring(1);
string defaultColorString =
Settings.PageColor.DefaultValue.ToString().Substring(1);
// The color picker works with the same isolated storage value that the
// Setting works with, but we have to clear its cached value to pick up
// the value chosen in the color picker
Settings.PageColor.ForceRefresh();
// Navigate to the color picker
this.NavigationService.Navigate(new Uri(
“/Shared/Color Picker/ColorPickerPage.xaml?”
+ “showOpacity=false”
+ “&currentColor=” + currentColorString
+ “&defaultColor=” + defaultColorString
+ “&settingName=PageColor”, UriKind.Relative));
}
void SetBackgroundPictureMenuItem_Click(object sender, EventArgs e)
{
if (IsolatedStorageHelper.FileExists(“background.jpg”))
{
// “remove background picture” was tapped
IsolatedStorageHelper.DeleteFile(“background.jpg”);
this.backgroundImage.Source = null;
RefreshAppBarMenu();
return;
}
// “set background picture” was tapped
PhotoChooserTask task = new PhotoChooserTask();
task.ShowCamera = true;
task.Completed += delegate(object s, PhotoResult args)
{
if (args.TaskResult == TaskResult.OK)
{
// Apply the image to the background
SetBackgroundImage(PictureDecoder.DecodeJpeg(args.ChosenPhoto));
// Seek back to the beginning of the stream again
args.ChosenPhoto.Seek(0, SeekOrigin.Begin);
// Save the file to isolated storage.
// This overwrites the file if it already exists.
IsolatedStorageHelper.SaveFile(“background.jpg”, args.ChosenPhoto);
RefreshAppBarMenu();
}
};
task.Show();
}
void EraseMenuItem_Click(object sender, EventArgs e)
{
// Allow this to be undone by storing all the current strokes
HistoryEntry entry = new HistoryEntry();
entry.StrokesRemoved = this.inkPresenter.Strokes.ToArray();
this.undoStack.Push(entry);
this.redoStack.Clear();
// Erase them all
this.inkPresenter.Strokes.Clear();
// Update the state of the undo/redo buttons
RefreshAppBarButtons();
}
void SaveToPicturesLibraryMenuItem_Click(object sender, EventArgs e)
{
// Create a new bitmap with the page’s dimensions
WriteableBitmap bitmap = new WriteableBitmap((int)this.grid.ActualWidth,
(int)this.grid.ActualHeight);
// Render the contents to the bitmap
bitmap.Render(grid, null);
// We must explicitly tell the bitmap to draw its new contents
bitmap.Invalidate();
using (MemoryStream stream = new MemoryStream())
{
// Fill the stream with a JPEG representation of this bitmap
bitmap.SaveJpeg(stream, (int)this.grid.ActualWidth,
(int)this.grid.ActualHeight,
0 /* orientation */, 100 /* quality */);
// Seek back to the beginning of the stream
stream.Seek(0, SeekOrigin.Begin);
// Save the image
try
{
new Microsoft.Xna.Framework.Media.MediaLibrary().SavePicture(
“paint.jpg”, stream);
}
catch
{
MessageBox.Show(“To do this, please disconnect your phone from Zune.”,
“Please Disconnect”, MessageBoxButton.OK);
return;
}
}
MessageBox.Show(
“Your artwork has been saved. Go to your phone’s Pictures hub to view it.”,
“Success”, MessageBoxButton.OK);
}
void InstructionsMenuItem_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(“/InstructionsPage.xaml”,
UriKind.Relative));
}
void AboutMenuItem_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(
new Uri(“/Shared/About/AboutPage.xaml?appName=Paint”, UriKind.Relative));
}
}
}

[/code]

  • The undo/redo feature is implemented as two stacks of the following simple data type:
    [code]
    public struct HistoryEntry
    {
    public Stroke StrokeAdded { get; set; }
    public IList<Stroke> StrokesRemoved { get; set; }
    }
    [/code]
    The undo feature supports not just the removal of newly added strokes, but undoingthe straightening of a stroke and undoing the erasure of all strokes simultaneously. If it weren’t for these two extra undo cases, this app wouldn’t need an undo stack at all—it could just treat the ink presenter’s Strokes collection as the stack and remove each stroke from the end of the list.
  • The fingerStrokes dictionary is used just like the fingerSounds dictionary from the preceding chapter, tracking each in-progress stroke while associating it with the correct finger. The hack to work around missing Up actions is not done here, however, because the only bad effect caused by this is extra entries left behind in the dictionary.
  • Although ink presenters can contain arbitrary elements with canvas-style layout, this page uses an image behind the ink presenter—placing both in a one-cell grid—to take advantage of grid’s automatic layout.
  • Because the application bar’s background is set to whatever the user has chosen as the paint color (inside OnNavigatedTo), we must ensure that the buttons and text are visible on top of this color no matter what. A simple IsLight method is used, defined toward the end of the listing, to make the foreground white if the background is dark or to make the foreground black if the background is light. The code in OnNavigatedTo also prevents the application bar background from becoming too transparent, as that could cause the buttons and text to become unreadable based on whatever the artwork happened to contain underneath.
  • Touch_FrameReported contains the code at the heart of this app. When a finger touches down, a new stroke is created and given drawing attributes that match all of the current settings. A stylus point is then added to its StylusPoints collection that matches the finger’s current location, and then it is added to the ink presenter’s Strokes collection. When a finger moves, the correct stroke is retrieved based on the finger ID, and then a new stylus point is added to it based on the current location. When a finger breaks contact with the screen, no further changes need to be made to the stroke, but the undo/redo stacks are adjusted appropriately and the application bar is refreshed.
  • The stroke-straightening feature works in two phases. The first time it is tapped, all stylus points on the most recent stroke are removed except for the starting and ending points. This causes it to form a straight but likely diagonal line. The second time it is tapped, the location of both points is adjusted to make the line horizontal or vertical, whichever is a closer match. The stroke is cloned and the copy is modified, but that’s only done so that the original stroke can be placed in the undo stack. If straightening did not need to be undone, the changes to the stroke’s points could be done directly to the instance already in the ink presenter.
  • The straightening process is demonstrated in Figure 39.5 for two different strokes. Notice that the straighten button’s icon changes to indicate which phase the most recent stroke is currently in.
The sequence of straightening two strokes.
FIGURE 39.5 The sequence of straightening two strokes.
  • XNA’s MediaLibrary.SavePicture method is called to save the artwork to the pictures library. Similar to the Local FM Radio and Subservient Cat apps, it checks for a failure case caused when the phone is connected to Zune on a PC. There’s one more failure case caused by the Zune connection: Calling Show on PhotoChooserTask causes the Completed event to be raised with the event-args TaskResult property set to TaskResult.Cancel. Because this isn’t easily distinguishable from the user cancelling the task, this case is left alone. It’s more likely to cause confusion for developers than users.

Manual Serialization and Deserialization

Although an ink presenter’s Strokes collection is serializable, attempting to assign such a collection to an isolated storage application setting (or a page state item) does not work. The automatic serialization process throws an exception. Therefore, rather than using a Setting object to persist and retrieve the ink presenter’s strokes, Listing 39.4 uses two methods in the project’s IsolatedStorageHelper class implemented as follows:

[code]

public static void SaveFile(string filename, object serializableObject)
{
using (IsolatedStorageFile userStore =
IsolatedStorageFile.GetUserStoreForApplication())
using (IsolatedStorageFileStream stream = userStore.CreateFile(filename))
using (StreamWriter writer = new StreamWriter(stream))
{
// Serialize the object to XML and write it to the file
XmlSerializer serializer = new XmlSerializer(serializableObject.GetType());
serializer.Serialize(writer, serializableObject);
}
}
public static object LoadSerializedObjectFromFile(string filename, Type type)
{
using (IsolatedStorageFile userStore =
IsolatedStorageFile.GetUserStoreForApplication())
{
if (userStore.FileExists(filename))
{
using (IsolatedStorageFileStream stream =
userStore.OpenFile(filename, FileMode.Open))
using (StreamReader reader = new StreamReader(stream))
{
// Deserialize the object from the XML in the file
XmlSerializer serializer = new XmlSerializer(type);
return serializer.Deserialize(reader);
}
}
}
return null;
}

[/code]

Manual serialization and deserialization is done with System.Runtime.Serialization.XmlSerializer from the System.Xml.Serialization assembly. The serialized XML it produces looks like the following for a one-point, onestroke collection:

[code]

<?xml version=”1.0” encoding=”utf-16”?>
<ArrayOfStroke xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:xsd=”http://www.w3.org/2001/XMLSchema”>
<Stroke>
<StylusPoints>
<StylusPoint>
<X>100</X>
<Y>0</Y>
<PressureFactor>0.5</PressureFactor>
</StylusPoint>
</StylusPoints>
<DrawingAttributes>
<Color>
<A>255</A>
<R>27</R>
<G>161</G>
<B>226</B>
</Color>
<OutlineColor>
<A>0</A>
<R>255</R>
<G>255</G>
<B>255</B>
</OutlineColor>
<Width>10</Width>
<Height>10</Height>
</DrawingAttributes>
</Stroke>
</ArrayOfStroke>

[/code]

XmlSerializer isn’t the only available option, however. Silverlight for Windows Phone also ships with System.Runtime.Serialization.DataContractSerializer (in the System.Runtime.Serialization assembly) and System.Runtime.Serialization .Json.DataContractJsonSerializer (in the System.Servicemodel.Web assembly).

DataContractSerializer serializes objects to XML, but in a different way than XmlSerializer. It also happens to support the serialization of a broader set of types and properties. The serialized XML it produces looks like the following for the same stroke collection:

[code]

<ArrayOfStroke xmlns:i=”http://www.w3.org/2001/XMLSchema-instance” xmlns=”http://s
chemas.datacontract.org/2004/07/System.Windows.Ink”><Stroke><DrawingAttributes><Co
lor xmlns:d4p1=”http://schemas.datacontract.org/2004/07/System.Windows.Media”><d4p
1:A>255</d4p1:A><d4p1:B>226</d4p1:B><d4p1:G>161</d4p1:G><d4p1:R>27</d4p1:R></Color
><Height>10</Height><OutlineColor xmlns:d4p1=”http://schemas.datacontract.org/2004
/07/System.Windows.Media”><d4p1:A>0</d4p1:A><d4p1:B>255</d4p1:B><d4p1:G>255</d4p1:
G><d4p1:R>255</d4p1:R></OutlineColor><Width>10</Width></DrawingAttributes><StylusP
oints xmlns:d3p1=”http://schemas.datacontract.org/2004/07/System.Windows.Input”><d
3p1:StylusPoint><d3p1:PressureFactor>0.5</d3p1:PressureFactor><d3p1:X>100</d3p1:X>
<d3p1:Y>0</d3p1:Y></d3p1:StylusPoint></StylusPoints></Stroke></ArrayOfStroke>

[/code]

Rather than pretty-printing the XML, which is not needed in this case, it produces one big line.

DataContractJsonSerializer serializes objects to JavaScript Object Notation (JSON), the popular format that is usually much more compact than XML. Here is the serialized JSON for the same stroke collection, which again is produced as one big line:

[code]

[{“DrawingAttributes”:{“Color”:{“A”:255,”B”:226,”G”:161,”R”:27},”Height”:10,”Outli neColor”:{“A”:0,”B”:255,”G”:255,”R”:255},”Width”:10},”StylusPoints”:[{“PressureFac tor”:0.5,”X”:100,”Y”:0}]}]

[/code]

The Finished Product

Paint (Ink Presenter)

 

 

 

Diagnostics Tools

You can monitor performance and memory using diagnostics tools. Let’s look at a few of them.

Hi-Res-Stats

The Hi-Res-Stats class, from mrdoob, calculates the frame rate, the time to render each frame, the amount of memory used per frame, and the maximum frame rate and memory consumption. Import the library and add a new instance of Stats as a displayOb ject. This is simple and convenient to use (see https://github.com/bigfish):

[code]

import net.hires.debug.*;
var myStats:Stats = new Stats();
addChild(myStats);

[/code]

Because it needs to be added to the displayList and draws its progress visually, as shown in Figure 19-4, this tool may impact rendering slightly, or get in the way of other graphics. A trick I use is to toggle its visibility when pressing the native search key on my device:

[code]

import flash.ui.Keyboard;
import flash.events.KeyboardEvent;
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKey);
function onKey(e:KeyboardEvent):void {
switch (e.keyCode) {
case Keyboard.SEARCH:
event.preventDefault();
myStats.visible = !myStats.visible;
break;
}
}

[/code]

Figure 19-4. Hi-Res-Stats display
Figure 19-4. Hi-Res-Stats display

Flash Builder Profiler

The premium version of Flash Builder comes with Flash Builder Profiler, which watches live data and samples your application at small, regular intervals and over time. It is well documented. Figure 19-5 shows the Configure Profiler screen.

Figure 19-5. The Configure Profiler screen in Flash Builder Profiler
Figure 19-5. The Configure Profiler screen in Flash Builder Profiler

When “Enable memory profiling” is selected, the profiler collects memory data and memory usage. This is helpful for detecting memory leaks or the creation of large objects. It shows how many instances of an object are used.

When “Watch live memory data” is selected, the profiler displays memory usage data for live objects. When “Generate object allocation stack traces” is selected, every new creation of an object is recorded.

When “Enable performance profiling” is selected, the profiler collects stack trace data at time intervals. You can use this information to determine where your application spends its execution time. It shows how much time is spent on a function or a process.

You can also take memory snapshots and performance profiles on demand and compare them to previous ones. When doing so, the garbage collector is first run implicitly. Garbage collection can also be monitored.

Flash Preload Profiler

The Flash Preload Profiler is an open source multipurpose profiler created by Jean- Philippe Auclair. This tool features a simple interface and provides data regarding frame rate history and memory history, both current and maximum.

Other more unusual and helpful features are the overdraw graph, mouse listener graph, internal events graph, displayObject life cycle graph, full sampler recording dump, memory allocation/collection dump, function performance dump, and run on debug/ release SWFs capability. More information on the Flash Preload Profiler is available at http://jpauclair.net/2010/12/23/complete-flash-profiler-its-getting-serious/.

Grant Skinner’s PerformanceTest

Grant’s PerformanceTest class is a tool for doing unit testing and formal test suites. Some of its core features are the ability to track time and memory usage for functions, and the ability to test rendering time for display objects. The class performs multiple iterations to get minimum, maximum, and deviation values, and it runs tests synchronously or queued asynchronously.

The class returns a MethodTest report as a text document or XML file. It can perform comparisons between different versions of Flash Player and different versions of the same code base. More information on this class is available at http://gskinner.com/blog/ archives/2010/02/performancetest.html.

Native Tools

The Android Debug Bridge (ADB) logcat command grabs information from the device and dumps it onto a log screen via USB. A lot of information is provided. Some basic knowledge of the Android framework will help you understand it better.

Memory

Let’s say it one more time: the memory on mobile devices is limited. If you exceed it, Android will terminate your application.

Creating Objects

Choose the appropriate object. For instance, use a Sprite instead of a MovieClip if you don’t need multiple frames. Use a Vector, which is more efficient than an Array, to store objects. Call the getSize() function to determine the memory footprint for a particular data type.

Allocating new blocks of memory is costly. Create your objects at an idle time, ideally when your application first initializes, and reuse them throughout your application.

Object pooling creates a pool to store objects and re-use them over time. In this example, we pop an object, or create one if none exists, and then push it back when done until the next use:

[code]

import flash.display.Sprite;
var pool:Vector.<Sprite>();
// get a new Sprite
var sprite:Sprite = popSprite();
sprite.init(); // set properties as needed
addChild(sprite);
function popSprite():Sprite {
var sprite:Sprite;
if (pool.length > 0) {
sprite = pool.pop(); } else {
sprite = new Sprite();
}
return sprite;
}

[/code]

When the object is no longer needed, return it:

[code]

removeChild(sprite);
pushSprite(sprite);
function pushSprite(sprite:Sprite):void {
pool.push(sprite);
}

[/code]

This approach is organic. The application creates objects as needed until it reaches a sufficient quantity to recycle them. Another method is to create all the objects initially, up to a maximum amount.

Removing Objects

Memory is dynamic. As objects are deleted, memory is released via a process called garbage collection.

Only primitive types, such as String, Number, and Boolean, are used directly. All other types are used using a reference, which must be removed to clear them from memory. This includes removing a child from a displayList, splicing an Array array or a Vector, stopping and deleting a Timer, removing an EventListener, or nulling a reference from another object.

Once this is all done, set the object to null to help the garbage collector work more quickly.

Use the disposeXML method for an XML object. It traverses the tree and sets all pointers between parents and children to null to make the object available for immediate garbage collection:

[code]

import flash.system.System;
function onXMLLoaded(event:Event):void {
var xml:XML = event.target.data;
// sudo code to parse and store data
var dataStorage = parseXML(XML);
System.disposeXML(xml);
xml = null;
}

[/code]

Use the dispose method for a BitmapData object:

[code]

var bitmapData:BitmapData = new BitmapData(480, 800);
bitmapData.dispose();
bitmapData = null;

[/code]

One of the principal reasons for memory leaks is lingering event listeners. A common recommendation today is to set weak event listeners as shown in the code below. Weak references are not counted by the garbage collector as references, a topic we will cover next:

[code]

var sprite:Sprite = new Sprite();
// strongly referenced listeners
sprite.addEventListener(MouseEvent.CLICK, onClick);
// weakly referenced listeners
// eventName, listener, capturePhase, priority, useWeakReference
sprite.addEventListener(MouseEvent.CLICK, onClick, false, 1, true);

[/code]

Use this approach with caution. Too many developers tend to rely on it instead of removing listeners in code. As a responsible coder, you should be diligent about doing the proper housekeeping.

If your code is large, or difficult to maintain, you can create an array to store listeners or a mechanic to automate their removal.

Create a destroy method to use in your classes to remove listeners and objects. To enforce the practice, use an IDestroy interface in which all classes need to have the destroy method but can implement it according to their individual needs:

[code]

// interface
public interface IDestroy {
function destroy():void;
}
// class A
public class ClassA implements IDestroy {
public function ClassA() {
}
public function destroy():void {
// remove listeners
// clear display list
while (numChildren > 0) {
removeChildAt(0);
}
}
}
// class B
public class ClassB implements IDestroy {
public function ClassB() {
}
public function destroy(event:):void {
// empty arrays
// set variables to null
// call garbage collection
System.gc();
}
}

[/code]

If one of your classes implements the interface but erroneously does not have a destroy method, you will see the following error:

[code]

Interface method destroy in namespace com:IDestroy not implemented by class
com.ClassC

[/code]

Garbage Collection

Garbage collection is an automatic mechanism that deallocates the memory used by objects when they are no longer referenced in the application. It is important because it releases memory for future use, and particularly so for mobile devices with limited memory.

The garbage collector is not predictable, but you can sometimes monitor it by watching your memory consumption using the System class. totalMemory refers to the portion of memory allocated to your code. privateMemory is the entire memory consumption of the application. All functions return bytes; divide by 1,024 to obtain kilobytes:

[code]

import flash.system.System;
System.privateMemory/1024;
System.totalMemory/1024;
System.totalMemoryNumber/1024 // returned as NUMBER for greater precision
System.freeMemory/1024; // memory unused

[/code]

You can call the garbage collector directly in AIR, but it requires significant memory and can result in a noticeable slowdown in the application. For this reason, reuse objects or clean them up quickly and frequently as described earlier:

[code]

System.gc();

[/code]

The AIR runtime uses two different methods for best results.

Reference count keeps track of the object and marks it for deletion when it has a value of zero if it has no reference:

[code]

var a:Object = new Object(); count is 1 for a;
var b:Object = a; 2 for b;
a = null; 1 for a;
b = null; 0 for b;

[/code]

This method fails when two objects reference each another:

[code]

var a:Object = new Object(); is 1 for a;
var b:Object = new Object(); is 1 for b;
a.b = b; 2 for b;
b.a = a; 2 for a;
a = null; 1 for a;
b = null; 1 for b;

[/code]

Mark sweeping handles this issue in a more accurate way but takes longer and does not happen as often. It traverses the entire application tree and marks an object that has a reference, marks its children, and so on, recursively. Objects that are not marked can be assumed to be eligible for collection.

In AIR for Android, the garbage collector is more iterative and objects are collected more frequently because memory is in short supply. For this reason, be attentive to making sensors and objects with listener member variables, not local variables.