Managing Lots of Sprites

Robot Trash Collectors

Our example in this hour is a simulation of robot trash collectors. The robots are represented as white circles, and the trash items as red squares. The robot cleaners will look for the closest trash item and move toward it, getting rid of the trash when it is touched. Figure 11.1 shows the simulation running with just one robot.

Simulation of robotic trash collectors with population control A.I.
FIGURE 11.1 Simulation of robotic trash collectors with population control A.I.

To make the simulation more interesting, it has the capability to automatically manage the amount of trash produced based on the number of cleaners present. A minus button at the bottom left removes cleaners, and its complementary plus button at the lower right adds new cleaners. As long as there is trash remaining, new trash is added rather slowly. But if the number of cleaners increases and the trash goes down, more trash is added more quickly to keep the cleaners busy. Figure 11.2 shows the simulation with five cleaners. Note the respawn time.

Five robot cleaners are present, which increases the trash rate.
FIGURE 11.2 Five robot cleaners are present, which increases the trash rate.

An algorithm of two trash items to one robot cleaner is used to adjust the speed. If there is fewer than twice the number of trash items compared to the cleaners, the speed is increased (by speeding up the respawn time). By watching the simulation run over time, you can see that a balance is maintained as long as the numbers are reasonable. Adding up to 100 or more robot cleaners causes the trash production to shift into high gear to keep up! See Figure 11.3 for an example.

The robot cleaner population is out of control!
FIGURE 11.3 The robot cleaner population is out of control!

Don’t let the primitive artwork dissuade you from studying the example in this hour. The code shared here will be extremely valuable in your own future game projects.

Building the Example

The simulation project this hour has a few asset requirements that you can source from the resource files for this hour, or just make yourself. The button image is just a 64×64 square. The robots are represented by a 32×32 white circle with alpha transparency around the edges. The trash is represented by a 32×32 red square. You are free to use different images if you want, perhaps even little robot and trash images! You can also change the theme of the art; how about insects and food?

Button Class

A helper class is needed to handle the buttons used to increase and decrease the number of robots. The Button class (see Listing 11.1) inherits from Sprite, so it will have all of Sprite’s capabilities and then some.

LISTING 11.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]

As you can see from the code, a constructor requires ContentManager and SpriteBatch parameters (which are just passed directly to the Sprite constructor), as well as a third parameter, SpriteFont, so that the button can print text on its own. Draw() calculates the size of the text and tries to center it. The font being used in this example is Wasco Sans Bold 36-point, but you are welcome to use a different font if you want. The Tapped() method receives as a parameter the position of a click/tap and checks to see whether it (the button) was tapped, returning true or false.

Main Source Code

Listing 11.2 contains the main source code for the Entity Grouping Demo program. We’ll go over the new helper methods after this.

LISTING 11.2 Source Code for the Entity Grouping Demo Program

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
TouchLocation oldTouch;
Random rand;
SpriteFont font, buttonFont;
Button plus, minus;
List<Sprite> cleaners;
Texture2D circleImage;
List<Sprite> trash;
Texture2D trashImage;
int lastTime = 0;
int target = -1;
int respawn = 500;
int score = 0;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
oldTouch = new TouchLocation();
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
rand = new Random();
spriteBatch = new SpriteBatch(GraphicsDevice);
font = Content.Load<SpriteFont>(“WascoSans”);
buttonFont = Content.Load<SpriteFont>(“ButtonFont”);
//create minus button
minus = new Button(Content, spriteBatch, buttonFont);
minus.position = new Vector2(32, 480-32);
minus.text = “-”;
//create plus button
plus = new Button(Content, spriteBatch, buttonFont);
plus.position = new Vector2(800 – 32, 480 – 32);
plus.text = “+”;
//create cleaners group
cleaners = new List<Sprite>();
circleImage = Content.Load<Texture2D>(“circle”);
AddCleaner();
//create trash group
trash = new List<Sprite>();
trashImage = Content.Load<Texture2D>(“trash”);
AddTrash();
}
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 &&
oldTouch.State == TouchLocationState.Released)
{
if (minus.Tapped(touch.Position))
RemoveCleaner();
if (plus.Tapped(touch.Position))
AddCleaner();
}
oldTouch = touch;
}
//add new trash item periodically
if ((int)gameTime.TotalGameTime.TotalMilliseconds >
lastTime + respawn)
{
lastTime = (int)gameTime.TotalGameTime.TotalMilliseconds;
AddTrash();
}
if (trash.Count > cleaners.Count * 2)
{
respawn += 1;
if (respawn > 1000)
respawn = 1000;
}
else
{
respawn -= 1;
if (respawn < 10)
respawn = 10;
}
//move the cleaners
for (int n = 0; n < cleaners.Count; n++)
{
//find trash to pick up
target = FindNearestTrash(cleaners[n].position);
if (target > -1)
{
float angle = TargetAngle(cleaners[n].position,
trash[target].position);
cleaners[n].velocityLinear = Velocity(angle, 2.0f);
cleaners[n].Move();
}
//look for collision with trash
CollectTrash(cleaners[n].position);
//look for collision with other cleaners
for (int c = 0; c < cleaners.Count; c++)
{
if (n != c)
{
while (cleaners[n].Boundary().Intersects(
cleaners[c].Boundary()))
{
cleaners[c].velocityLinear.X += 0.001f;
cleaners[c].velocityLinear.Y += 0.001f;
cleaners[c].Move();
}
}
}
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.DarkBlue);
spriteBatch.Begin();
minus.Draw();
plus.Draw();
foreach (Sprite spr in trash)
spr.Draw();
foreach (Sprite spr in cleaners)
spr.Draw();
spriteBatch.DrawString(font,
“Cleaners:”+cleaners.Count.ToString(),
Vector2.Zero, Color.White);
spriteBatch.DrawString(font, “Trash:”+trash.Count.ToString(),
new Vector2(0,25), Color.White);
spriteBatch.DrawString(font, “Score:” + score.ToString(),
new Vector2(0, 50), Color.White);
spriteBatch.DrawString(font, “Respawn:” + respawn.ToString(),
new Vector2(0, 75), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
[/code]

Main Simulation Functionality Code

The “meat and potatoes” code that drives the simulation is coming up next, in Listing 11.3. Here we find helper methods that look for the nearest trash items, move the robots toward the trash, add new robots, remove robots, and calculate distance and velocity. Some of these methods, like AddCleaner() and RemoveCleaner(), could have been just coded directly where they are called in the program, but this approach is much cleaner and reusable.

LISTING 11.3 Entity Grouping Demo Program (Continued)

[code]
void RemoveCleaner()
{
if (cleaners.Count > 0)
cleaners.RemoveAt(0);
}
void AddCleaner()
{
Sprite obj = new Sprite(Content, spriteBatch);
obj.image = circleImage;
obj.position = new Vector2((float)rand.Next(0, 760),
(float)rand.Next(0, 450));
cleaners.Add(obj);
}
void AddTrash()
{
Sprite obj = new Sprite(Content, spriteBatch);
obj.image = trashImage;
obj.position = new Vector2((float)rand.Next(0, 760),
(float)rand.Next(0, 450));
trash.Add(obj);
}
int FindNearestTrash(Vector2 pos)
{
int target = -1;
float closest = 9999;
if (trash.Count == 0) return -1;
for (int n = 0; n < trash.Count; n++)
{
float dist = Distance(pos, trash[n].position);
if (dist < closest)
{
closest = dist;
target = n;
}
}
return target;
}
void CollectTrash(Vector2 pos)
{
if (trash.Count == 0) return;
for (int n = 0; n < trash.Count; n++)
{
float dist = Distance(pos, trash[n].position);
if (dist < 8)
{
score++;
trash.RemoveAt(n);
break;
}
}
}
float Distance(Vector2 A, Vector2 B)
{
double diffX = A.X – B.X;
double diffY = A.Y – B.Y;
double dist = Math.Sqrt(Math.Pow(diffX, 2) + Math.Pow(diffY, 2));
return (float)dist;
}
float TargetAngle(Vector2 p1, Vector2 p2)
{
return TargetAngle(p1.X, p1.Y, p2.X, p2.Y);
}
float TargetAngle(double x1, double y1, double x2, double y2)
{
double deltaX = (x2 – x1);
double deltaY = (y2 – y1);
return (float)Math.Atan2(deltaY, deltaX);
}
Vector2 Velocity(float angle, float acceleration)
{
double x = Math.Cos(angle) * acceleration;
double y = Math.Sin(angle) * acceleration;
return new Vector2((float)x, (float)y);
}
}
[/code]

Simulating Group Dynamics

The key to this simulation example is a pair of linked lists called cleaners and trash. A linked list is a managed collection for a single type of data, like our Sprite class. In C#, the list is defined with the data type (Sprite) as a template in brackets:

[code]
List<Sprite> cleaners;
The list is created in LoadContent():
cleaners = new List<Sprite>();
[/code]

At this point, the list is ready to be filled with Sprite objects. Each object is a separate, complete Sprite object with its own properties and methods. A new object is added to the list with the Add() method. This code creates a new Sprite object and adds it to the list:

[code]
Sprite sprite = new Sprite(Content, spriteBatch);
sprite.position = new Vector2(0, 0);
cleaners.Add(sprite);
[/code]

Testing revealed that the simulation locks up when there are more than about 250 robots, at which point it is difficult to click the minus button to reduce the population.

You can add as many objects to the list as you want, using a for loop, or by reading data in from a level file, or by any other means, and the list will grow as needed to contain all the objects being added. When objects have been added to the list, they can be forgotten to a certain degree. It’s up to us to make sure we can identify each object inside the list later if we want to use a specific object. For instance, if all the objects for a game are stored in a list like this, and you want to highlight the player’s character, then there must be a way to uniquely identify that sprite among all the others. This can be done by adding an identifier to the Sprite class, such as an ID number or name string. We aren’t doing that in this simulation because every object has equal treatment.

Using List Objects

There will be at least two different points where all the objects in the list must be accessed—from Update() and Draw(). That is, again, the very least number of times, while you might need to access the list elsewhere to test for collisions or for other purposes. There are two ways to iterate the list, using a for loop or using a foreach iterator.

A property of List provides the number of items it contains, called Count. For example:

[code]
for (int n = 0; n < cleaners.Count; n++)
{
//reference cleaners[n]
}
[/code]

Another way to access a list is with a foreach loop such as this:

[code]
foreach (Sprite sprite in cleaners)
{
//reference sprite
}
[/code]

Creating and Using a List

A List is like an array, but it is much easier to use. Let’s learn how to create a List container for other objects.

  1. Define the new List variable in the program’s global variables section. In the brackets, you will define the type of data that the List will contain— which can be anything, such as Sprite or int or string.
    [code]
    List<string> groceries;
    [/code]
  2. Create or initialize the List variable, again with the data type in brackets. Plus, don’t forget the parentheses at the end! The parentheses mean this is a function—or rather, method—call, the constructor method to be precise.
    [code]
    groceries = new List<string>();
    [/code]
  3. Add items to the list like so:
    [code]
    groceries.Add(“butter”);
    groceries.Add(“milk”);
    groceries.Add(“bread”);
    [/code]
  4. Access the items in the list using either a for loop with the groceries.count property, or a foreach loop with the groceries object.
    [code]
    foreach (string item in groceries)
    {
    // … do something with the item here
    }
    [/code]

Iteration Techniques Compared

There are distinct advantages to both iteration mechanisms, depending on programming needs. The numeric for loop is helpful when you want to find the index position of a certain object in the list and then reference it later with indexers ([]). For instance, you might keep track of a “clicked” object with a number variable, and then just index inside the list with that variable, like so:

[code]
cleaners[clicked].Move();
[/code]

The foreach loop is easier to use if you just want to do a quick update of every object, and is commonly used to update the position or draw the object. Of course, there are many other purposes for a foreach iteration, and this is just one example. In the simulation project, all the robot cleaners are drawn with just two lines of code:

[code]
foreach (Sprite spr in cleaners)
spr.Draw();
[/code]

Very complex-appearing A.I. behaviors can seem to derive from surprisingly simple algorithms. When it comes down to analysis, simple rules determine human behavior in social environments too!

But in another part of the project, an index for loop is used to find the nearest trash. This snippet of code from the FindNearestTrash() method looks for the closest trash item by calculating the distance to every item in the trash list, keeping track of the index of the closest one with an int variable called closest. That specific trash item can then be found by indexing into trash with the variable.

[code]
for (int n = 0; n < trash.Count; n++)
{
float dist = Distance(pos, trash[n].position);
if (dist < closest)
{
closest = dist;
target = n;
}
}
[/code]

That’s all there is to it! In the final analysis, we use the distance calculation for many, many things in an ordinary video game!

Debugging IronPython Applications

Understanding IronPython Warnings

Warnings are simply indicators that something could be wrong with your application or might not work under all conditions. For example, if you use a deprecated (outdated) function, you might later find that the application refuses to work on all machines. You can use warnings for all kinds of purposes, including providing debugging messages for your application.

The main difference between a warning and an exception is that a warning won’t stop the application. When the interpreter encounters a warning, it outputs the warning information to the standard error device unless the interpreter is ignoring the warning. In some cases, you need to tell the interpreter to ignore a warning because the warning is due to a bug in someone else’s code, a known issue that you can’t fix, or simply something that is obscuring other potential errors in your code. A standard warning looks like this:

[code]
__main__:1: UserWarning: deprecated
[/code]

The elements are separated by colons (:) and each warning message contains the following elements (unless you change the message formatting to meet a specific need).

  • Function name (such as __main__)
  • Line number where the warning appears
  • Warning category
  • Message

You’ll discover more about these elements as the chapter progresses. In the meantime, it’s also important to know that you can issue warnings, filter them, change the message formatting, and perform other tasks using the warning-related functions shown in Table 12-1. You see these functions in action in the sections that follow.

Warning-Related Functions and Their Purpose
Table 12-1: Warning-Related Functions and Their Purpose
Warning-Related Functions and Their Purpose
Table 12-1: Warning-Related Functions and Their Purpose (Continue)

Working with Actions

Before you do too much with warnings, it’s important to know that warnings have an action associated with them. For example, you can choose to turn a particular warning into an exception or to ignore it completely. You can apply actions to warnings in a number of ways using either the filterwarnings() or simplefilter() function. Table 12-2 shows the list of standard warning actions.

Standard Warning Actions
Table 12-2: Standard Warning Actions
Table 12-2 (continued)
Table 12-2 (continued)

It’s important to work with a few warnings to see how filtering works because filters are exceptionally important. In order to use warnings, you import the warnings module. Figure 12-1 shows a typical instance of the default action. Notice that the first time the code issues the warnings .warn(“deprecated“, DeprecationWarning) warning, the interpreter displays a message. (Don’t worry too much about the specific arguments for the warnings.warn()function for right now; you see them explained in the “Working with Messages” and “Working with Categories” sections of the chapter.) However, the interpreter ignores the same warning the second time. If you change the message, however, the interpreter displays another message.

The default action displays each message just one time.
Figure 12-1: The default action displays each message just one time.

Of course, you could always associate a different action with the warnings .warn(“deprecated“, DeprecationWarning) warning. To make this change, you can use the simplefilter() function as shown in Figure 12-2. Now when you issue the warning, it appears every time.

You can set the warning to appear every time.
Figure 12-2: You can set the warning to appear every time.

Unfortunately, as shown in the figure, the change affects every message. Using the simplefilter() function affects every message in every module for a particular message category. Both the newmessage and deprecated messages always appear. Let’s say you want to make just the deprecated message always appear. To perform this task, you use the filterwarnings() function as shown in Figure 12-3 (after first resetting the category using the resetwarnings() function).

Use the filterwarnings() function when you need better control over filtering.
Figure 12-3: Use the filterwarnings() function when you need better control over filtering.

In this case, the warnings.warn(“deprecated“, DeprecationWarning) warning appears every time because its action is set to always. However, the warnings .warn(“newmessage“, DeprecationWarning) warning appears only once because it uses the default action.

You can also set an action at the command line using the –W command line switch. For example, to set the interpreter to always display warning messages, you’d use the –W always command line switch. The –W command line switch accepts an action, message, category, module, or line number (lineno) as input. You can include as many –W command line switches as needed on the command line to filter the warning messages.

The resetwarnings() function affects every warning category and every message in every module. You might not want to reset an entire filtering configuration by using the resetwarnings() function. In this case, simply use the filterwarnings() or simplefilter() function to set the warning back to the default action.

At this point, you might wonder how to obtain a list of the filters you’ve defined. For that matter, you don’t even know if there are default filters that the interpreter defines for you. Fortunately, the warnings class provides two attributes, default_action and filters, which provide this information to you. Listing 12-1 shows how to use these two attributes.

Listin g 12-1: Discovering the default action and installed filters

[code]
# Import the required modules.
import warnings
# Display the default action.
print ‘Default action:’, warnings.default_action
# Display the default filters.
print ‘nDefault Filters:’
for filter in warnings.filters:
print ‘Action:’, filter[0],
print ‘Msg:’, filter[1],
print ‘Cat:’, str(filter[2]).split(“‘“)[1].split(‘.’)[1],
print ‘Module:’, filter[3],
print ‘Line:’, filter[4]
# Add new filters.
warnings.filterwarnings(‘always’, message=’Test’, category=UserWarning)
warnings.filterwarnings(‘always’, message=’Test2’, category=UserWarning,
module=’Test’)
warnings.filterwarnings(‘always’, message=’Test3’, category=UserWarning,
module=’Test’, append=True)
# Display the updated filters.
print ‘nUpdated Filters:’
for filter in warnings.filters:
print ‘Action:’, filter[0],
try:
print ‘Msg:’, filter[1].pattern,
except AttributeError:
print ‘None’,
print ‘Cat:’, str(filter[2]).split(“‘“)[1].split(‘.’)[1],
try:
if len(filter[3].pattern) == 0:
print ‘Module: Undefined’,
else:
print ‘Module:’, filter[3].pattern,
except AttributeError:
print ‘Module: None’,
print ‘Line:’, filter[4]
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)
[/code]

The code begins by importing the warnings module. It then displays (using warnings.default_ action) the default action that the interpreter will take when it encounters a warning. As shown in Figure 12-4 and described in Table 12-2, the default action is ‘default‘.

The example shows the default actions and filters, along with the output of filter changes.
Figure 12-4: The example shows the default actions and filters, along with the output of filter changes.

The next step is to show the default filters that the interpreter provides for you. It may surprise you to know that the interpreter does include some default filters for the PendingDeprecationWarning, ImportWarning, and BytesWarning, as shown in Figure 12-4. These default filters make the interpreter easier and more enjoyable to use, but could also hide important bugs, so you need to be aware of them.

In order to show how actions and filters work, the example adds three filters using the warnings .filterwarnings() function. The first filter simply tells the interpreter to always display warnings about the Test message provided in the UserWarning category. The second filter specifies that the Test2 warning will appear in the Test module. The third filter specifies that the interpreter should append the warning filter to the end of the filter list, rather than add it to the front of the list as is traditional. You can see the result of all three filter additions in Figure 12-4.

The code used to display the filter information is different in this case because the simple display method used earlier won’t work. What you’ll see as output for the message and module information is something like

[code]
<RE_Pattern object at 0x000000000000002C>
[/code]

which isn’t particularly useful. In order to get information from the message and module elements, you must access the pattern attribute. Unfortunately, this attribute isn’t available with the default filters, so the solution is to create a try…except AttributeError structure, as shown in the code. When the code encounters a default filter entry, it simply prints None as it would have done in the past.

Working with modules presents a special problem. If you look at the first filter declaration, it doesn’t include the Module attribute. Unfortunately, the interpreter takes this omission to mean that you want to create a blank entry, not a null entry. Consequently, the module code also handles the empty entry scenario by saying the module is undefined. If you want to create a null module entry, you must use Module=None as part of your filter declaration.

Notice in Figure 12-4 that the first two filters appear at the front of the list and in reverse order. That’s because the interpreter always adds new filters to the beginning of the list unless you include the append=True attribute. Because the third filter includes this attribute, it appears at the end of the list.

Working with Messages

A message is simply the text that you want to appear as part of the warning. The message is specific information about the warning so that someone viewing the warning will know precisely why the warning is issued. For example, if you issue a DeprecationWarning category warning, the output will automatically tell the viewer that something is deprecated. As a result, your message doesn’t have to tell the viewer that something is deprecated, but it does have to tell the viewer what is deprecated. In many cases, this means supplying the name of the feature such as a method name, attribute, function, or even a class.

Simply telling someone that a feature is deprecated usually isn’t enough information. At a minimum, you must include information about an alternative. For example, you might want to suggest another class or a different function. Even if there is no alternative, you should at least tell the viewer that there isn’t an alternative. Otherwise, the viewer is going to spend hours looking for something that doesn’t exist.

You can’t always tell someone why something is deprecated, but you should when you can. For example, it would be helpful to know that an old function is unstable and that the new function fixes this problem. It’s a good idea to extend this information by saying that the old function is supplied for backward compatibility (assuming that this really is the case).

In some cases, you also need to provide some idea of when a feature is deprecated, especially if the action occurs in the future. Perhaps your organization knows that a function is unstable but hasn’t come up with a fix yet. The fix will appear in the next version of a module as a new function. Having this information will help organizations that rely on your module to plan ahead for required updates.

The point of messages is that they should provide robust information — everything that someone needs to make good decisions. Of course, you don’t want to provide too much information either (anything over three well-written sentences is too much). If you feel the viewer needs additional information, you can always provide it as part of the feature’s help. That way, people who are curious can always find more information. Make sure you note the availability of additional information as part of your message.

Message consistency is another consideration. Remember that filters work with messages as well as categories and other warning elements. If two modules require the same message, make sure you use the same message to ensure filtering works as anticipated. In fact, copying and pasting the message is encouraged to reduce the risk of typographical errors.

If you ever want to see how your message will appear to others, you can use the formatwarning() function to perform the task. Try it out now. Open a copy of the IronPython console and try the following code.

[code]
import warnings
warnings.formatwarning(‘Bad Input’, UserWarning, ‘My.py’, 5, ‘import warnings’)
[/code]

You’ll see results similar to those shown in Figure 12-5. Notice that the output contains linefeeds like this: ‘My.py:5: UserWarning: Bad Inputn import warningsn‘. When you work with the printed version, the warning appears on multiple lines, as shown near the bottom of Figure 12-5.

Use formatwarning() to see how your warning will appear.
Figure 12-5: Use formatwarning() to see how your warning will appear.

Of course, it’s handy to know the arguments for the formatwarning() function. The following list provides a brief description of each argument.

  • Message: The message you want to display to the user.
  • Category: The warning category you want to use.
  • Filename: The name of the file where the warning occurred (not necessarily the current file).
  • Line number: In most cases, this value contains the line at which the warning is detected, which isn’t always the line at which the warning occurs. For example, it’s possible for a warning to appear at the end of a structure, rather than at the appropriate line within the structure.
  • Line of code: An optional entry that shows the line of code at which the warning occurs. If you don’t supply this argument, the formatwarnings() function defaults to a value of None. The IronPython implementation differs from the standard in this regard. According to the standard, the interpreter is supposed to read the file, obtain the correct line of code, and display the specified line when you don’t provide the appropriate text.

Working with Categories

A warning category is a means of identifying a particular kind of warning. The category makes it possible to group like warnings together and reduces the risk that someone will misinterpret the meaning of a message. In short, a category is a way to pigeonhole a particular message so that others know what you intend. Of course, filtering considers the warning category, so you also need to use the correct category to ensure filtering works as expected. Table 12-3 contains a list of the warning message categories, including a general Warning class that you shouldn’t ever use because it’s too general.

Warning Message Categories
Table 12-3: Warning Message Categories
Warning Message Categories
Table 12-3: Warning Message Categories (Continue)
Warning Message Categories
Table 12-3: Warning Message Categories (Continue)

The warning categories are used with almost every warnings module function. For example, you supply a category when setting a filter or creating a new message. There is always an exception. The resetwarnings() function doesn’t require any input, not even a warning category, because it resets the entire warning environment to a default state.

Obtaining Error Information

Errors will happen in your application, even if you use good exception handling. The handlers you create only react to the errors you know about. Applications also encounter unknown errors. In this case, your application has to have a way to obtain error information and display it to the user (or at least record it in a log file).

It’s important to remember that you normally obtain error information in an application using the exception process described. This section of the chapter is more designed for those situations where you need to work with a generic exception or obtain more detailed information than the specific exceptions provide.

As with many things, IronPython provides a number of methods for obtaining error information. In fact, you might be surprised at how many ways you can retrieve information once you really start looking. The following sections discuss the most common methods for obtaining error information.

Using the sys Module

The sys module contains a wealth of useful functions and attributes you use to obtain, track, and manage error information. One of the first things you should know about the sys module is that it contains the sys.stderr attribute, which defines where the interpreter sends error output. Normally, the output goes to the console window, but you can redirect the error output to any object that has a write() method associated with it, such as a file. If you want to later reset the sys.stderr attribute to the console, the sys.__stderr__ attribute always contains the original output location, so using sys.stderr = sys.__stderr__ performs a reset.

Obtaining error information seems like it should be straightforward, but it’s harder than most developers initially think because obtaining error information often affects application execution in unforeseen ways. In addition, ensuring that the caller receives the right information in a multithreaded application is difficult. The caller could also make unfortunate changes to error information objects, such as the traceback object, creating problems with circular references that the garbage collector is unable to handle. Consequently, you find a lot of functions in sys that look like they should do something useful (and this section covers them), but the two functions you need to keep in mind when working with IronPython are.

  • sys.exc_info(): Returns a tuple containing three items:
    • type: The type of the error, such as ZeroDivisionError. You can find a list of all standard exception types in the exceptions module.
    • value: The human readable string that defines the error. For example, a ZeroDivisionError might provide ZeroDivisionError(‘Attempted to divide by zero.‘,) as a value.
    • traceback: An object that describes the stack trace for an exception. Normally, you won’t use this information directly unless you truly need to obtain the stack trace information, which can prove difficult. If you need stack trace information, consider using the traceback module features instead
  • sys.exc_clear(): Clears the existing exceptions from the current thread. After you call this function, sys.exc_info() returns None for all three elements in the tuple.

The sys.exc_info() function isn’t very hard to use, but you can’t really try it out by executing it directly in the IronPython console. You need to place it within a try…except structure instead. The following code shows a quick demonstration you can type directly into the console window.

[code]
try:
5/0
except:
type, value = sys.exc_info()[:2]
print type
print value
[/code]

The example uses a simple division by zero to create an exception. As previously noted, you normally need just the first two elements of the tuple, which you can obtain using sys.exc_info()[:2]. When you execute this code, you see the following output.

[code]
<type ‘exceptions.ZeroDivisionError’>
Attempted to divide by zero.
[/code]

Some IronPython sys module functions affect only the interactive thread (which means they’re safe to use in multithreaded applications because there is only one interactive thread in any given session). You could use these functions to determine the current type, value, and traceback for an exception, but only for the interactive session, which means these functions are completely useless for your application. In most cases, you avoid using these three functions.

  • sys.last_traceback()
  • sys.last_type()
  • sys.last_value()

You could run into problems when working with some functions in the sys module. For example, these three functions are global, which means they aren’t specific to the current thread and are therefore, unsafe to use in a multithreaded application.

  • sys.exc_type()
  • sys.exc_value()
  • sys.exc_traceback()

Interestingly enough, these three functions are also listed as deprecated (outdated) in most Python implementations (including IronPython). As with all IronPython modules, you also have access to low-level functions in the sys module. The following list is low-level modules you can use for special needs, but won’t normally use in your application.

  • sys.excepthook(type, value, traceback): The system calls this low-level function each time it generates an exception. To use this function, you supply the same tuple of values as you receive when you call sys.exc_info().
  • sys._getframe([depth]): The system calls this low-level function to display a frame object from the call stack. If the caller supplies a depth value, the frame object is at that call stack depth. The default depth value setting is 0. IronPython doesn’t appear to implement this function, but you may encounter it in other versions of Python, so it pays to know about this function.

If you want to control how much information the interpreter provides when you request a traceback, you can always set the sys.tracebacklimit attribute. The sys.tracebacklimit attribute defaults to 1,000. It doesn’t actually appear when you perform a dir() command. In fact, until you set it, printing the sys.tracebacklimit attribute returns an AttributeError. Use code like this

[code]
sys.tracebacklimit = 3
[/code]

to modify the traceback level. Now when you try to print the sys.tracebacklimit attribute, you get back the value you supplied.

Using the traceback Module

The traceback module adds to the capabilities of the sys module described in the “Using the sys Module” section of the chapter. In addition, it adds to the standard exception handling capabilities of IronPython by making it easier to obtain complex information about exceptions in general. The traceback module does focus on tracebacks, which are the IronPython equivalent of a call stack.

The most common call is traceback.print_exc(). Essentially, this call prints out the current exception information. You can use it in a try…except structure, much as you’d use the sys.exc_info() function, but with fewer limitations. Figure 12-6 shows a typical view of the traceback.print_exc() function in action.

Obtain traceback information with ease using the traceback.print_exc() function.
Figure 12-6: Obtain traceback information with ease using the traceback.print_exc() function.

You may find that you want a string that you can manipulate, rather than direct output. In this case, you use the traceback.format_exc() function and place its output in a variable. The information is the same as shown in Figure 12-6, but you have the full capability of string manipulation functions to output the information in any form desired.

All of the traceback output functions include a level argument that defines how many levels of trace information you want. The default setting provides 1,000 levels, which may be a little more information than you want. Many of the traceback output functions also include a file argument that accepts the name of a file you can use for output (such as application logging). If you don’t provide the file argument, it defaults to using the sys.stderr device (normally the console).

Some of the traceback functions are macros for longer function combinations. For example, when you type traceback.print_last(), what you’re really doing is executing print_exception(sys.last_ type, sys.last_value, sys.last_traceback, limit, file). Obviously, typing traceback .print_last() is a lot less work!

IronPython is missing some extremely important functionality when it comes to the traceback module. You can’t use traceback.print_stack(), traceback.extract_stack(), or traceback.format_ stack() to obtain current stack information. The code shown in Figure 12-7 is standard output when working with Python. Figure 12-8 shows what happens when you execute this code in IronPython. Instead of getting a nice stack trace you can use for debugging (see Figure 12-7), you get nothing at all (see Figure 12-8). This is a known issue (see the issue information at http://ironpython.codeplex .com/WorkItem/View.aspx?WorkItemId=25543).

Python provides full stack information you can use for debugging.
Figure 12-7: Python provides full stack information you can use for debugging.
IronPython lacks support for stack traces, making debugging significantly more difficult.
Figure 12-8: IronPython lacks support for stack traces, making debugging significantly more difficult.

The traceback module contains a number of interesting functions that you can use to debug your application. You can see these functions described at http://docs.python.org/library/traceback .html. Don’t assume that all of these functions work as they do in Python. There are currently a number of outstanding traceback module issues for IronPython.

Debugging with the Python Debugger

You might not know it, but Python and IronPython come with a debugger module, pdb (for Python debugger). Like any module, you have full access to the debugger source code and can modify it as needed. This section describes the default debugger performance.

It’s possible to use pdb with any Python file by invoking the debugger at the command line using the –m command line switch. Here’s how you’d invoke it for the example shown in Listing 12-1.

[code]
IPY -m pdb ShowFilters.py
[/code]

Unfortunately, using this command line format limits what you can do with the debugger. Although you can single step through code, you can’t work with variables easily and some other debugger commands may not work as anticipated.

The debugger works better if you configure your application to use a main() module. Most of the examples in this book don’t use a main() function for the sake of simplicity, but you should use one for any production code you create. The ShowFilters2.py file contains the modifications to provide a main() function. Essentially, you encase the code in Listing 12-1 in the main() function and then call it using the following code:

[code]
# Create an entry point for debugging.
if __name__ == “__main__“:
main()
[/code]

Using the debugger is very much like old-style DOS debuggers such as the Debug utility. You issue commands and the debugger responds without output based on the application environment and variable content. The lack of a visual display may prove troublesome to developers who have never used a character-mode debugger, but pdb is actually more effective than any of the graphical alternatives in helping you locate problems with your application — at least, in the Python code. Use these steps to start the pdb:

  1. Start the IronPython console by selecting it from the Start menu or typing IPY at the command line.
  2. Type import pdb and press Enter to import the Python debugger.
  3. Type import ApplicationName where ApplicationName is the name of the file that contains your application and press Enter. For example, if your application appears in ShowFilters2.py, then you’d type import ShowFilters2 (without the file extension) and press Enter.
  4. Type pdb.run(‘ApplicationName.FunctionName()‘) where ApplicationName is the name of the application and FunctionName is the name of the function you want to test, and press Enter. For example, if your application is named ShowFilters2 and the function you want to test is main(), you’d type pdb.run(‘ShowFilters2.main()‘) and press Enter. The standard console prompt changes to a pdb prompt, as shown in Figure 12-9.
The Python debugger uses a special pdb prompt where you can enter debugging commands.
Figure 12-9: The Python debugger uses a special pdb prompt where you can enter debugging commands.

Now that you have a debugger prompt, you can begin debugging your application. Here is a list of standard debugger commands you can issue:

  • a or args: Displays the list of arguments supplied to the current function. If there aren’t any arguments, the call simply returns without displaying anything.
  • alias: Creates an alias for a complex command. For example, you might need to use a for loop to drill down into a list to see its contents. You could use an alias to create a command to perform that task without having to write the complete code every time. An alias can include replaceable variables, just as you would use for a batch file.
  • b or break: Defines a breakpoint when you supply a line number or a function name. When you provide a function name, the breakpoint appears at the first executable line within the function. If an application spans multiple files, you can specify a filename, followed by a colon, followed by a line number (no function name allowed), such as ShowFilters2:1. A breakpoint can also include a condition. To add the condition, follow the breakpoint specification with a comma and the condition you want to use, such as ShowFilters2:2, Filter == None. If you type just b or break, the debugger shows the current breakpoints. Use the cl or clear command to clear breakpoints you create.
  • bt, w, or where: Prints a stack trace with the most current frame at the bottom of the list. You can use this feature to see how the application arrived at the current point of execution.
  • c, cont, or continue: Continues application execution until the application ends or the debugger encounters a breakpoint.
  • cl or clear: Clears one or more breakpoints. You can specify the breakpoint to clear by providing one or more breakpoint numbers separated by spaces. As an alternative, you can supply a line number or a filename and line number combination (where the filename and line number are separated by a colon).
  • commands: Defines one or more commands that execute when the debugger arrives at a line of code specified by a breakpoint. You include the optional breakpoint as part of the commands command. If you don’t supply a breakpoint, then the commands command refers to the last breakpoint you set. To stop adding commands to a breakpoint, simply type end. If you want to remove the commands for a breakpoint, type commands, press Enter, type end, and press Enter again. A command can consist of any interactive Python or debugger command. For example, if you want to automatically move to the next line of code, you’d simply add step as one of the commands.
  • condition: Adds a condition to a breakpoint. You must supply a breakpoint number and a Boolean statement (in string format) as arguments. The debugger doesn’t honor a breakpoint with a condition unless the condition evaluates to True. The condition command lets you add a condition to a breakpoint after defining the breakpoint, rather than as part of defining the breakpoint. If you use condition with a breakpoint, but no condition, then the debugger removes a condition from a breakpoint, rather than adding one.
  • d or down: Moves the frame pointer down one level in the stack trace to a new frame.
  • debug: Enters a recursive debugger that helps you debug complex statements.
  • disable: Disables one or more breakpoints so that they still exist, but the debugger ignores them. You can separate multiple breakpoint numbers with spaces to disable a group of breakpoints at once.
  • enable: Enables one or more breakpoints so that the debugger responds to them. You can separate multiple breakpoint numbers with spaces to enable a group of breakpoints at once. Enabling a breakpoint doesn’t override any conditions that are set on the breakpoint. The condition must still evaluate to True before the debugger reacts to the breakpoint.
  • EOF: Tells the debugger to handle the End of File (EOF) as a command. Normally, this means ending the debugger session once the debugger reaches EOF.
  • exit or q or quit: Ends the debugging session. Make sure you type exit, and not exit(), which still ends the IronPython console session.
  • h or help: Displays information about the debugger. If you don’t provide an argument, help displays a list of available debugging commands. Adding an argument shows information about the specific debugging command.
  • ignore: Creates a condition where the debugger ignores a breakpoint a specific number of times. For example, you might want to debug a loop with a breakpoint set at a specific line of code within the breakpoint. You could use the ignore command to ignore the first five times through the loop and stop at the sixth. You must supply a breakpoint number and a count to use this command. The debugger automatically ignores the breakpoint until the count is 0.
  • j or jump: Forces the debugger to jump to the line of code specified as an argument.
  • l or list: Displays the specified lines of code. If you don’t supply any arguments with the command, the debugger displays 11 lines of code starting with the current line. When you supply just a starting point (a code line number), the debugger displays 11 lines of code starting with the starting point you specify. To control the listing completely, supply both a starting and ending point.
  • n or next: Continues execution to the next line of code. If the current line of code is a function, the debugger executes all of the code within the function and stops at the next line of code in the current function. In sum, this command works much like a step over command in most other debuggers.
  • p: Prints the value of an expression as the debugger sees it. Don’t confuse this command with the IronPython print() function, which prints an expression based on how IronPython sees it.
  • pp: Performs a pretty print. Essentially, this command is the same as the p command, except that the debugger interprets any control characters within the output so that the output appears with line feeds, carriage returns, tabs, and other formatting in place.
  • r or return: Continues execution until the current function returns. This command works much like a step out command in most other debuggers
  • restart: Restarts the current application at the beginning so that you can retest it. The command lets you supply optional arguments that appear as part of the sys.argv attribute. This command preserves debugger history, breakpoints, actions, and options.
  • run: Starts the application when used within Python as demonstrated earlier in this section. However, this command is simply an alias for restart when used within the debugger environment.
  • s or step: Executes the current line of code and then moves to the next line of code, even if that line of code appears within another function. This command works much like a step into command in most other debuggers
  • tbreak: Performs precisely like a break command, except that the debugger removes the breakpoint when the debugger stops at it the first time. This is a useful command when you want to execute a breakpoint just one time.
  • u or up: Moves the frame pointer up one level in the stack trace to an old frame.
  • unalias: Removes the specified alias (see the alias command for additional details).
  • unt or until: Continues execution until such time as the line number is greater than the current line number or the current frame returns. This command works much like a combination of the step over and step out commands in most other debuggers (see next, return, and step for other stepping commands).
  • whatis: Displays the type of the argument that you supply.

Debugging with the CLR Debugger

The CLR debugger, CLRDbg.EXE, is part of the .NET Framework SDK. You find it in the GuiDebug folder of your .NET Framework installation or in the Program FilesMicrosoft.NETSDKv2.0 GuiDebug folder. However, if you installed Visual Studio without installing the SDK, you might not see a GuiDebug folder. In this case, you can download and install the .NET Framework SDK separately. You can obtain the .NET Framework SDK for various platforms at these locations.

  • .NET Framework 2.0: http://msdn.microsoft.com/en-us/netframework/aa731542.aspx
  • .NET Framework 3.0: http://msdn.microsoft.com/en-us/netframework/bb264589.aspx
  • .NET Framework 3.5: http://msdn.microsoft.com/en-us/netframework/cc378097.aspx
  • .NET Framework 3.5 SP1: http://msdn.microsoft.com/en-us/netframework/ aa569263.aspx

This section relies on the CLRDbg.EXE version found in the .NET Framework 2.0 SDK. However, the instructions work fine for every other version of the CLR debugger as well. The newer versions of the debugger may include a few additional features that you won’t likely use or need when working with IronPython. The following steps describe how to start the debugger.

  1. Start the CLR debugger. If you installed the .NET Framework SDK separately, choose Start ➪ Programs ➪ Microsoft .NET Framework SDK v2.0 ➪ Tools ➪ Microsoft CLR Debugger. It’s also possible to start the CLR debugger from the command line by typing CLRDbg and pressing Enter as long as the debugger’s location appears in the path. You see the Microsoft CLR Debugger window.

    Provide the information needed to debug your application.
    Figure 12-10: Provide the information needed to debug your application.
  2. Choose Debug ➪ Program to Debug. You see the Program to Debug dialog box shown in Figure 12-10. This dialog box is where you enter the IronPython executable and script information, along with any command line switches you want to use.
  3. Click the ellipsis (…) in the Program field and use the Find Program to Debug dialog box to locate the IPY.EXE file. Click Open to add the IPY.EXE information to the dialog box.
  4. Type –D NameOfScript.py in the Arguments field (the example uses –D ShowFilters2 .py). Type any additional command line arguments you want to use while working with the application.
  5. Click the ellipses in the Working Directory field and use the Browse for Working Directory dialog box to locate the script directory (not the IPY.EXE directory). Click Open to select the working directory.
  6. Click OK. The CLR debugger prepares the debugging environment. However, you don’t see any files opened. You must open any files you wish to interact with as a separate step.
  7. Choose File ➪ Open ➪ File. Locate the source files you want to debug (ShowFilters2.py for the example). Click Open. You see the source file opened in the Microsoft CLR Debugger window. Figure 12-11 shows an example of how your display should look when working with the example. (The figure shows the debugger in debugging mode.)

    Open the source files you want to debug.
    Figure 12-11: Open the source files you want to debug.

At this point, you can begin working with the script just as you would with the Visual Studio debugger. The next section, “Using Visual Studio for IronPython Debugging,” discusses this debugger in more detail.

Using Visual Studio for IronPython Debugging

When you click Start Debugging, the debugger stops at the line of code as you might expect. Now, create a watch for both filter and filters. As shown in Figure 12-12, you can drill down into a complex object and examine it. In many cases, you must look through the Non-Public Members to find what you want, but the data is there for you to peruse. In this case, you can see all five elements in filters and even see the pattern data. Notice that the Type column is truly helpful in showing you which types to use when interacting with the data.

Watches let you drill down into both Python and .NET data.
Figure 12-12: Watches let you drill down into both Python and .NET data.

Unfortunately, Figure 12-12 also shows the other side of the coin. You can’t access warnings .filters even though it should be available. The Visual Studio debugger often produces poor results when working with Python-specific objects. If you have a need for working with these objects.

As shown in Figure 12-13, you can use the Immediate window to query objects directly. However, you can’t drill down into an object as you might have in the past. Consequently, entering ? filter works just fine, but entering ? filter[0] doesn’t.

The Immediate window is only partially useful when working with IronPython.
Figure 12-13: The Immediate window is only partially useful when working with IronPython.

In general, you’ll find that using the Python debugger works better for some Python-specific applications. Even though the Visual Studio debugger does provide a nice visual display, the quality of information isn’t quite as good. Of course, the picture changes when your application mixes Python and .NET code. In this case, the Visual Studio debugger can be your best friend because it knows how to work with the .NET objects.

 Defining and Using Exceptions

Exceptions are an essential part of any application. In fact, most developers have no problem using them at all. Unfortunately, many developers also misuse exceptions. Instead of providing robust code that handles common problems, the developer simply raises an exception and hopes someone else does something about the issue. Exceptions are generally used to address conditions that you couldn’t anticipate.

IronPython provides access to both Python exception and .NET exceptions, so the developer actually has twice as many opportunities to catch errors before they become a problem. It’s important to use the correct kind of exception handling. If you’re working with .NET code, you’ll normally use a .NET exception. Python exceptions address anything that isn’t .NET-specific. The following sections provide additional information about exceptions.

Implementing Python Exceptions

Python provides a number of standard exceptions, just as the .NET Framework does. You find these exceptions in the exceptions module. To see the list of standard exceptions, import the exceptions module and perform a dir() command on it, as shown in Figure 12-14.

Python stores its list of standard exceptions in the exceptions module.
Figure 12-14: Python stores its list of standard exceptions in the exceptions module.

The various exceptions provide different amounts of information. For example, when working with an IOError, you can access the errno, filename, message, and strerror attributes. On the other hand, a ZeroDivisionError provides only the message attribute. You can use the dir(exceptions .ExceptionName) command to obtain information about each of the exception attributes.

As with .NET, you can create custom exceptions using Python. The documentation for creating a custom exception is a bit sketchy, but you can create a custom exception (usually with the word Error in the name by convention) for every need. Listing 12-2 shows all of the Python exception basics, including creating a relatively flexible custom exception.

Listin g 12-2: Discovering the default action and installed filters

[code]
# Import the required modules.
import exceptions
# Define a custom exception.
class MyError(exceptions.Exception):
errno = 0
message = ‘Nothing’
def __init__(self, errno=0, message=’Nothing’):
self.errno = errno
self.message = message
def __str__(self):
return repr(self.message)
# Display the Error exception list.
for Error in dir(exceptions):
if ‘Error’ in Error:
print Error
# Create a standard exception.
try:
5/0
except ZeroDivisionError as (errinfo):
print “nDivide by Zero error: {0}“.format(errinfo)
# Create a custom exception.
try:
raise MyError(5, ‘Hello from MyError’)
except MyError, Info:
print “Custom Error({0}): {1}“.format(Info.errno, Info.message)
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)
[/code]

The code begins by importing exceptions. The for loop lists all of the exceptions (the names of the types) found in exceptions, as shown in Figure 12-15. Notice how the code uses if ‘Error‘ in Error to locate just the exceptions in the module. This technique is useful for a lot of tasks in IronPython where you need to filter the output in some way.

The example shows basic exception handling and creation for Python.
Figure 12-15: The example shows basic exception handling and creation for Python.

The next bit of code raises a standard exception and then handles it. The output shows just a message. Notice that this exception relies on the as clause to access the error information.

It’s time to look at a custom exception, which begins with the MyError class definition. At a minimum, you should define both __init__() and __str__() or the exception won’t work as intended. Notice how __init__() assigns default values to both errno and message. You can’t depend on the caller to provide this information, so including default values is the best way to approach the problem. You can always assign other values later in the code based on the actual errors.

Make sure you create attributes for any amplifying information you want the caller to have. In this case, the example defines two attributes errno and message.

The __str__() method should return a human-readable message. You can return just the text portion of the exception or return some combination of exception attributes. The important thing is to return something that the developer will find useful should the exception occur. You can test this behavior out with the example by typing raise MyError. Here’s the output you’ll see.

[code]
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
__main__.MyError: ‘Nothing’
[/code]

Because you didn’t provide any arguments, the output shows the default values. Try various combinations to see how the output works. The example tries the exception in a try…except statement. Notice that a custom exception differs from a standard exception in that you don’t use the as clause and simply provide a comma with a variable (Info in this case) instead. You can then use the variable to access the exception attributes as shown. Figure 12-15 shows how the custom exception outputs information. Of course, your custom exception can provide any combination of values.

Implementing .NET Exceptions

In general, you want to avoid using .NET exceptions in your IronPython applications, except in those cases where you need to provide specific functionality for .NET code. The problem is that IronPython views such exceptions from a Python perspective. Consequently, trapping .NET exceptions can prove tricky unless you spend some time working with them in advance.

Many .NET exceptions are available in the System assembly so you need to import it before you can perform any serious work. After that, you can raise a .NET exception much as you do a Python exception. Handling the exception follows the same route as using a try…except statement. However, the problem is that the exception you get isn’t the exception you raised. Look at Figure 12-16 and you see that the ArgumentException becomes a ValueError and the ArithmeticException becomes an ArithmeticError.

Sloppy programming will cost you so much time as to make the programming experience a nightmare. Using a combination of warnings, error trapping, and exceptions will make your code significantly easier to debug. Of course, choosing the right debugging tool is also a requirement if you want to go home this weekend, rather than spending it in your office debugging your latest application.

 

 

 

When Objects Collide

Boundary Collision Detection

The first type of collision test we’ll examine uses the boundaries of two rectangles for the test condition. Also called “bounding box” collision detection, this technique is simple and fast, which makes it ideal in situations in which a lot of objects are interacting on the screen at once. Not as precise as the radial technique (coming up next), boundary collision detection does return adequate results for a high-speed arcade game. Interestingly, this is also useful in a graphical user interface (GUI), comparing the boundary of the mouse cursor with GUI objects for highlighting and selection purposes. On the WP7, however, we can’t actually track mouse movement because a “mouse” does not even exist on Windows Phone, only touch points.

XNA provides a useful struct called Rectangle. Among the many methods in this struct is Intersects(), which accepts a single parameter—another Rectangle. The return value of Intersects() is just a bool (true/false). The trick to using Rectangle.Intersects() effectively is creating a bounding rectangle around a sprite at its current location on the screen, taking into account the sprite’s width and height. When we get into animation a couple of hours from now, we’ll have to adapt this code to take into account the width and height of individual frames. In the meantime, we can use the width and height of the whole texture since we’re just working with simple static sprite images at this point. Figure 10.1 illustrates the boundary around a sprite image.

The image dimensions are used as the boundary.
FIGURE 10.1 The image dimensions are used as the boundary.

As you can see in this illustration, boundary collision does not require that the image have an equal width and height, since the bounding rectangle can handle a nonuniform aspect ratio. However, it is usually better to store game artwork in square images for best results.

Accounting for Pivot

Remember that Sprite.position references the pivot point of the object, also known as the origin. Normally, the origin is at the center of the sprite. If the origin is changed from the center of the sprite, the boundary will return an incorrect result! Therefore, if you change the origin, it’s up to you to take that into account when calculating boundaries. The Sprite class does not account for such changes. The calculation for the boundary might be done like so at this point:

[code]
int halfw = image.Width / 2;
int halfh = image.Height / 2;
return new Rectangle(
(int)position.X – halfw,
(int)position.Y – halfh,
image.Width,
image.Height);
[/code]

Accounting for Scaling

That should work nicely, all things being equal. But another important factor that must be considered is scaling. Has the scale been changed from 1.0, or full image size? If so, that will affect the boundary of the sprite, and we have to account for that or else false results will come back while the game is running, causing a sprite to collide when it clearly did not touch another sprite (due to scaling errors). When the boundary is calculated, not only the origin must be accounted for, but the scaling as well, which adds a bit of complexity to the Boundary() method. Not to worry, though—it’s calculated by the method for us. Here is a new version that takes into account the scaling factor:

[code]
int halfw = (int)( (float)(image.Width / 2) * scaleV.X );
int halfh = (int)( (float)(image.Height / 2) * scaleV.Y );
return new Rectangle(
(int)position.X – halfw,
(int)position.Y – halfh,
halfw * 2,
halfh * 2);
[/code]

What is happening in this code is that the width and height are each divided by two, and the scaling is multiplied by these halfw and halfh values to arrive at scaled dimensions, which are then multiplied by two to get the full width and height when returning the Rectangle.

Sprite Class Changes

To simplify the code, we can add a new method to the Sprite class that will return the boundary of a sprite at its current location on the screen. While we’re at it, it’s time to give Sprite a new home in its own source code file. The new file will be called Sprite.cs. The namespace is a consideration, because the class needs a home. I propose just calling it GameLibrary, and in any program that uses Sprite, a simple using statement will import it.

The new version of this class includes the Boundary() method discussed earlier. An additional new method is also needed for debugging purposes. Every class has the capability to override the ToString() method and return any string value. We can override ToString() and have it return information about the sprite. This string can be logged to a text file if desired, but it is easier to just print it on the screen. The code in ToString() is quite messy due to all the formatting codes used, but the result looks nice when printed. It can be very helpful to add a ToString() to a custom class for this purpose, for quick debugging. For example, it is helpful to see the position and boundary of a sprite to verify whether collision is working correctly. See the demo coming up shortly to see an example of this in action. Our Sprite class, the source code for which is shown in Listing 10.1, sure has grown since its humble beginning!

LISTING 10.1 Revised source code for the Sprite class.

[code]
public class Sprite
{
private ContentManager p_content;
private SpriteBatch p_spriteBatch;
public Texture2D image;
public Vector2 position;
public Vector2 velocityLinear;
public Color color;
public float rotation;
public float velocityAngular;
public Vector2 scaleV;
public Vector2 origin;
public bool alive;
public bool visible;
public Sprite(ContentManager content, SpriteBatch spriteBatch)
{
p_content = content;
p_spriteBatch = spriteBatch;
image = null;
position = Vector2.Zero;
velocityLinear = Vector2.Zero;
color = Color.White;
rotation = 0.0f;
velocityAngular = 0.0f;
scaleV = new Vector2(1.0f);
origin = Vector2.Zero;
alive = true;
visible = true;
}
public float scale
{
get { return scaleV.X; }
set
{
scaleV.X = value;
scaleV.Y = value;
}
}
public bool Load(string assetName)
{
try
{
image = p_content.Load<Texture2D>(assetName);
origin = new Vector2(image.Width / 2, image.Height / 2);
}
catch (Exception) { return false; }
return true;
}
public void Draw()
{
p_spriteBatch.Draw(image, position, null, color, rotation,
origin, scaleV, SpriteEffects.None, 0.0f);
}
public void Move()
{
position += velocityLinear;
}
public void Rotate()
{
rotation += velocityAngular;
if (rotation > Math.PI * 2)
rotation -= (float)Math.PI * 2;
else if (rotation < 0.0f)
rotation = (float)Math.PI * 2 – rotation;
}
public Rectangle Boundary()
{
int halfw = (int)( (float)(image.Width / 2) * scaleV.X );
int halfh = (int)( (float)(image.Height / 2) * scaleV.Y );
return new Rectangle(
(int)position.X – halfw,
(int)position.Y – halfh,
halfw * 2,
halfh * 2);
}
public override string ToString()
{
string s = “Texture {W:” + image.Width.ToString() +
“ H:” + image.Height.ToString() + “}n” +
“Position {X:” + position.X.ToString(“N2”) + “ Y:” +
position.Y.ToString(“N2”) + “}n” +
“Lin Vel “ + velocityLinear.ToString() + “n” +
“Ang Vel {“ + velocityAngular.ToString(“N2”) + “}n” +
“Scaling “ + scaleV.ToString() + “n” +
“Rotation “ + rotation.ToString() + “n” +
“Pivot “ + origin.ToString() + “n”;
Rectangle B = Boundary();
s += “Boundary {X:” + B.X.ToString() + “ Y:” +
B.Y.ToString() + “ W:” + B.Width.ToString() +
“ H:” + B.Height.ToString() + “}n”;
return s;
}
}
[/code]

Boundary Collision Demo

Let’s put the new Sprite method to use in an example. This example has four asteroids moving across the screen, and our spaceship pilot must cross the asteroid belt without getting hit by an asteroid. This example is automated—there’s no user input. So just watch it run this time. The source code is found within Listing 10.2, while Figure 10.2 shows a screenshot of the program running. When the ship collides with an asteroid, it simply stops until the asteroid goes on by. But to give the ship more survivability, additional intelligence code has to be added.

The ship tries to dodge asteroids as it crosses the asteroid field.
FIGURE 10.2 The ship tries to dodge asteroids as it crosses the asteroid field.

When the demo is completed later in the hour, it will also have dodging capability. If an asteroid is directly above or below the ship when a “grazing” collision occurs, the ship can simply stop to avoid being destroyed. But if an asteroid is coming from the left or right, destruction is imminent! When this happens, the pilot has to use emergency thrusters to quickly move forward to get out of the way of the asteroid. This is also automated, so again, just watch it run, as input is ignored. We’ll start by just having the ship stop when a collision occurs, and add the capability to speed up in the section coming up on collision response.

Note that the using statements are included here just to show that GameLibrary must be included (the namespace containing the Sprite class), but they will again be omitted in future listings.

LISTING 10.2 Source code for the boundary collision demo program.

[code]
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
using GameLibrary;
namespace Bounding_Box_Collision
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Random rand;
Viewport screen;
SpriteFont font;
Sprite[] asteroids;
Sprite ship;
const float SHIP_VEL = -1.5f;
int collisions = 0;
int escapes = 0;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
screen = GraphicsDevice.Viewport;
rand = new Random();
font = Content.Load<SpriteFont>(“WascoSans”);
//create asteroids
asteroids = new Sprite[4];
for (int n=0; n<4; n++)
{
asteroids[n] = new Sprite(Content, spriteBatch);
asteroids[n].Load(“asteroid”);
asteroids[n].position.Y = (n+1) * 90;
asteroids[n].position.X = (float)rand.Next(0, 760);
int factor = rand.Next(2, 12);
asteroids[n].velocityLinear.X = (float)
((double)factor * rand.NextDouble());
}
//create ship
ship = new Sprite(Content, spriteBatch);
ship.Load(“ship”);
ship.position = new Vector2(390, screen.Height);
ship.scale = 0.2f;
ship.velocityLinear.Y = SHIP_VEL;
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
//move asteroids
foreach (Sprite ast in asteroids)
{
ast.Move();
if (ast.position.X < 0 – ast.image.Width)
ast.position.X = screen.Width;
else if (ast.position.X > screen.Width)
ast.position.X = 0 – ast.image.Width;
}
//move ship
ship.Move();
if (ship.position.Y < 0)
{
ship.position.Y = screen.Height;
escapes++;
}
//look for collision
int hit = 0;
foreach (Sprite ast in asteroids)
{
if (BoundaryCollision(ship.Boundary(), ast.Boundary()))
{
//oh no, asteroid collision is imminent!
EvasiveManeuver(ast);
collisions++;
hit++;
break;
}
//if no collision, resume course
if (hit == 0)
ship.velocityLinear.Y = SHIP_VEL;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
foreach (Sprite ast in asteroids)
ast.Draw();
ship.Draw();
string text = “Collisions:” + collisions.ToString();
spriteBatch.DrawString(font, text, new Vector2(600, 0),
Color.White);
text = “Escapes:” + escapes.ToString();
spriteBatch.DrawString(font, text, new Vector2(600, 25),
Color.White);
spriteBatch.DrawString(font, ship.ToString(), new Vector2(0, 0),
Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
void EvasiveManeuver(Sprite ast)
{
//for now, just stop the ship
ship.velocityLinear.Y = 0.0f;
}
bool BoundaryCollision(Rectangle A, Rectangle B)
{
return A.Intersects(B);
}
}
}
[/code]

Radial Collision Detection

The term “radial” refers to rays or radii (plural for radius), implying that this form of collision detection uses the radii of objects. Another common term is “distance” or “spherical” collision testing. In some cases, a bounding sphere is used to perform 3D collision testing of meshes in a rendered scene, the 3D version of a bounding circle. (Likewise, a bounding cube represents the 3D version of rectangular boundary collision testing.) Although radial collision can be done with an image that has an unbalanced aspect ratio (of width to height), best results will be had when the image has uniform dimensions. The first figure here, Figure 10.3, shows how a bounding circle will not correspond to the image’s width and height correctly, and this radius would have to be set manually.

A bounding circle around a nonsquare image.
FIGURE 10.3 A bounding circle around a nonsquare image.

But if the image’s width and height are uniform, as illustrated in Figure 10.4, then the width or height of the image can be used for the radius, simplifying everything. The beauty of radial collision testing is that only a single float is needed—the radius, along with a method to calculate the distance between two objects. This illustration also shows the importance of reducing the amount of empty space in an image. The transparent background pixels in this illustration (the area within the box) should be as close to the edges of the visible pixels of the shuttle as possible to improve collision results. The radius and distance factors will never be perfect, so reducing the overall radii of both sprites is often necessary. I have found in testing that 80% produces good results. Due to the shape of some images, reducing the radius by 50% might even be helpful, especially in a high-speed arcade-style game in which the velocity would exceed the collision error anyway.

Here is a method that will be helpful when radial collision detection is being used:

[code]
bool RadialCollision(Vector2 A, Vector2 B, float radius1, float radius2)
{
double diffX = A.X – B.X;
double diffY = A.Y – B.Y;
double dist = Math.Sqrt(Math.Pow(diffX, 2) + Math.Pow(diffY, 2));
return (dist < radius1 + radius2);
}
[/code]

An overload with Sprite properties will make using the method even more useful in a game:

[code]
bool RadialCollision(Sprite A, Sprite B)
{
float radius1 = A.image.Width / 2;
float radius2 = B.image.Width / 2;
return RadialCollision(A.position, B.position, radius1, radius2);
}
[/code]

The bounding circle shows the radius at all points from the center.
FIGURE 10.4 The bounding circle shows the radius at all points from the center.

To test radial collision, just make a change to the Boundary Collision program so that RadialCollision() is called instead of BoundaryCollision() in the program’s Update() method.

Assessing the Damage

We now have two algorithms to test for sprite collisions: boundary and radial. Detecting collisions is the first half of the overall collision-handling code in a game. The second half involves responding to the collision event in a meaningful way.

There are as many ways to respond to the collision are there are games in the computer networks of the world—that is, there is no fixed “right” or “wrong” way to do this; it’s a matter of gameplay. I will share one rather generic way to respond to collision events by figuring out where the offending sprite is located in relation to the main sprite.

First, we already know that a collision occurred, so this is post-collision response, not predictive response. The question is not whether a collision occurred, but where the offending sprite is located. Remember that the center of a sprite represents its position. So if we create four virtual boxes around our main sprite, and then use those to test for collision with the offender, we can get a general idea of where it is located: above, below, left, or right, as Figure 10.5 shows.

Four collision response boxes are used to quickly determine colliding object position.
FIGURE 10.5 Four collision response boxes are used to quickly determine colliding object position.

There will be cases in which the offender is within two of these virtual boxes at the same time, but we aren’t concerned with perfect results, just a general result that is “good enough” for a game. If you are doing a slow-paced game in which pixel-perfect collision is important, it would be vital to write more precise collision response code, perhaps a combination of boundary and radial collision values hand-coded for each sprite’s unique artwork. It might even be helpful to separate appendages into separate sprites and move them in relation to the main body, then perform collision testing on each item of the overall “game object.” Figure 10.6 shows a screenshot of the new boundary collision example, which now has some collision-avoidance intelligence built in. The source code for the program is contained in Listing 10.3.

The ship now has a rudimentary intelligence to avoid collisions.
FIGURE 10.6 The ship now has a rudimentary intelligence to avoid collisions.

LISTING 10.3 Source code for the new boundary collision program with collision-avoidance A.I.

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Random rand;
Viewport screen;
SpriteFont font;
Sprite[] asteroids;
Sprite ship;
int collisions = 0;
int escapes = 0;
int kills = 0;
int hit = 0;
const float SHIP_VEL = 1.5f;
const float SHIP_ACCEL = 0.2f;
const float ESCAPE_VEL = 2.5f;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
screen = GraphicsDevice.Viewport;
rand = new Random();
font = Content.Load<SpriteFont>(“WascoSans”);
//create asteroids
asteroids = new Sprite[4];
for (int n=0; n<4; n++)
{
asteroids[n] = new Sprite(Content, spriteBatch);
asteroids[n].Load(“asteroid”);
asteroids[n].position.Y = (n+1) * 90;
asteroids[n].position.X = (float)rand.Next(0, 760);
int factor = rand.Next(2, 12);
asteroids[n].velocityLinear.X = (float)
((double)factor * rand.NextDouble());
}
//create ship
ship = new Sprite(Content, spriteBatch);
ship.Load(“ship”);
ship.position = new Vector2(390, screen.Height);
ship.scale = 0.2f;
ship.velocityLinear.Y = -SHIP_VEL;
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
//move asteroids
foreach (Sprite ast in asteroids)
{
ast.Move();
if (ast.position.X < 0 – ast.image.Width)
ast.position.X = screen.Width;
else if (ast.position.X > screen.Width)
ast.position.X = 0 – ast.image.Width;
}
//move ship
ship.Move();
if (ship.position.Y < 0)
{
ship.position.Y = screen.Height;
escapes++;
}
//look for collision
foreach (Sprite ast in asteroids)
{
if (RadialCollision(ship, ast))
{
//oh no, asteroid collision is imminent!
EvasiveManeuver(ast);
collisions++;
hit++;
}
}
//accelerate
if (ship.velocityLinear.Y >= -SHIP_VEL)
{
ship.velocityLinear.Y -= SHIP_ACCEL;
if (ship.velocityLinear.Y < -SHIP_VEL)
ship.velocityLinear.Y = -SHIP_VEL;
}
else if (ship.velocityLinear.Y < SHIP_VEL)
{
ship.velocityLinear.Y += SHIP_ACCEL;
if (ship.velocityLinear.Y > SHIP_VEL)
ship.velocityLinear.Y = SHIP_VEL;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
foreach (Sprite ast in asteroids)
{
ast.Draw();
ast.color = Color.White;
}
ship.Draw();
string text = “Intersections:” + collisions.ToString();
spriteBatch.DrawString(font, text, new Vector2(600, 0), Color.White);
text = “Kills:” + kills.ToString();
spriteBatch.DrawString(font, text, new Vector2(600, 25), Color.White);
text = “Escapes:” + escapes.ToString();
spriteBatch.DrawString(font, text, new Vector2(600, 50), Color.White);
float survival = 100.0f – (100.0f / ((float)collisions
/ (float)kills));
text = “Survival:” + survival.ToString(“N2”) + “%”;
spriteBatch.DrawString(font, text, new Vector2(600, 75), Color.White);
spriteBatch.DrawString(font, ship.ToString(), new Vector2(0, 0),
Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
void EvasiveManeuver(Sprite ast)
{
ast.color = Color.Red;
//shortcuts for ship
int SW = (int)(float)(ship.image.Width * ship.scale);
int SH = (int)(float)(ship.image.Height * ship.scale);
int SX = (int)ship.position.X – SW/2;
int SY = (int)ship.position.Y – SH/2;
//create boundary boxes around the ship
Rectangle[] boxes = new Rectangle[4];
boxes[0] = ship.Boundary(); //upper
boxes[0].Y -= SH;
boxes[1] = ship.Boundary(); //lower
boxes[1].Y += SH;
boxes[2] = ship.Boundary(); //left
boxes[2].X -= SW;
boxes[3] = ship.Boundary(); //right
boxes[3].X += SW;
if (boxes[0].Intersects(ast.Boundary()))
ship.velocityLinear.Y = SHIP_VEL * ESCAPE_VEL;
else if (boxes[1].Intersects(ast.Boundary()))
ship.velocityLinear.Y = -SHIP_VEL * ESCAPE_VEL;
else if (boxes[2].Intersects(ast.Boundary()))
ship.velocityLinear.Y = -SHIP_VEL * ESCAPE_VEL;
else if (boxes[3].Intersects(ast.Boundary()))
ship.velocityLinear.Y = -SHIP_VEL * ESCAPE_VEL;
//check for a “kill,” intersection with small inner box
Rectangle kill = ship.Boundary();
int shrinkh = kill.Width / 4;
int shrinkv = kill.Height / 4;
kill.Inflate(-shrinkh, -shrinkv);
if (kill.Intersects(ast.Boundary()))
kills++;
}
bool BoundaryCollision(Rectangle A, Rectangle B)
{
return A.Intersects(B);
}
bool RadialCollision(Sprite A, Sprite B)
{
float radius1 = A.image.Width / 2;
float radius2 = B.image.Width / 2;
return RadialCollision(A.position, B.position, radius1, radius2);
}
bool RadialCollision(Vector2 A, Vector2 B, float radius1, float radius2)
{
double diffX = A.X – B.X;
double diffY = A.Y – B.Y;
double dist = Math.Sqrt(Math.Pow(diffX, 2) + Math.Pow(diffY, 2));
return (dist < radius1 + radius2);
}
}
[/code]

This is perhaps our longest sample program so far, even taking into account that that Sprite class is now found in a different source code file. Game A.I. is a challenging subject. By studying the way the algorithm responds to asteroid collisions, you could use this technique for any number of game scenarios to improve gameplay and/or increase the difficulty by making your A.I. sprites more intelligent.

We learned about collision detection via boundary and radial collision methods, and used these algorithms to study collision response in gameplay code. When a collision algorithm is available, it really opens up our ability to make a game for the first time. As we saw in the final example of this hour, the level of response to collisions can be basic or advanced, rudimentary or quite intelligent, as the spaceship in this example demonstrated.

Developing ASP.NET Applications

Considering IronPyth on Web Application Compatibility

IronPython (and Python for that matter) have huge potential for making Web sites significantly easier to build and manage. In fact, there are a lot of options for using Python (and IronPython) to perform Web development tasks that you can see at http://wiki.python.org/ moin/WebProgramming and http://www.fredshack.com/docs/pythonweb.html. The problem with IronPython is that it can’t use the C/C++ libraries used by Python, which means that some of these solutions won’t work. For example, it’s unlikely that you could use IronPython in a Common Gateway Interface (CGI) application because CGI relies on modules written in C/ C++. Consequently, the first rule for Web development with IronPython is to make sure that IronPython can actually interact with the desired modules.

Unfortunately, IronPython isn’t integrated into Visual Studio. Because Visual Studio relies on some level of integration to provide full Web development support, you’ll almost certainly find that some projects are out of reach. It’s not that you can’t create them using IronPython; it’s that the process would be so horribly time consuming and error prone that using another language would be a better idea. You do have full .NET capability when working with IronPython, as demonstrated by the example in this chapter. All you really need is to add Dynamic Language Runtime (DLR) support to the picture and things will work out just fine.

The picture isn’t all gloom. IronPython actually proves to be a good development language for some tasks. As you progress through this chapter, you’ll find that IronPython actually makes code behind tasks exceptionally easy. It’s conceivable that you can build and test simple Web projects using IronPython considerably faster than using another language such as C# or Visual Basic.NET.

It’s important to remember that you don’t have to work with IronPython alone. Your Web application can include other technologies, such as Silverlight. You can also rely on other languages, such as C# or Visual Basic.NET, to fill in the gaps in IronPython coverage. Having another language at your disposal is all about flexibility, and IronPython is a great add-on language for any Web application project.

Obtaining ASP.NET Dynamic Language Support

Microsoft built ASP.NET with extensibility in mind, but the native support tends to focus more on static languages such as Visual Basic.NET and C#, rather than dynamic languages such as IronPython. The Dynamic Language Runtime (DLR) is an add-on for ASP.NET that makes it possible to use languages, such as IronPython, that make typing decisions at run time, rather than compile time. You must download and install this support before you can use IronPython to create a Web application. The following sections describe DLR in more detail.

DLR Limitations

The DLR is currently a work in progress. The overall feel is of an alpha product that shows promise, but still has more than a few warts. In addition, the product currently lacks these features (in order of their importance to you as a developer):

  • IntelliSense: Most developers depend on IntelliSense to provide clues as to what works and what doesn’t — in essence, what to write next. Without IntelliSense support, most developers find themselves peaking at the documentation and spending hours being frustrated with the development environment. Because a default relies so heavily on IntelliSense during the entire development process, its lack is keenly felt. Let’s hope that Microsoft will choose to add this feature sooner rather than later.
  • Limited designer support: If you worked through the examples in Chapter 8, you probably have a good idea of why designer support is so important. Sure, you can create a perfectly usable interface without a designer, but doing so becomes time consuming and many developers give up before they get their user interface completely right. The better the designer support, the faster you can work.
  • Project templates: The lack of templates isn’t anything new. You’ve created examples throughout this book without them, so not having them now isn’t that big a deal. However, it will be nice to have the convenience of project templates when Microsoft creates them.
  • ASP.NET Model-View-Controller (MVC) pattern: MVC is a development pattern that Microsoft is pushing very hard because it provides better control over the development process. You can learn more about ASP.NET MVC at http://www.asp.net/mvc/. Microsoft eventually plans to add MVC to DLR by extending MVC for IronRuby (see http:// github.com/jschementi/ironrubymvc for additional details).
  • Language Services Support: A new language feature that Microsoft plans to provide sometime in the future. Details about this feature are unavailable as of this writing, but it’s likely that Language Services Support will somehow make DLR more flexible and able to support a myriad of languages.

If you read the information at http://www.asp.net/DynamicLanguages/ carefully, you notice that it contains a wealth of caveats. The DLR is essentially an alpha version of a product that may not even appear as part of ASP .NET. Consequently, you need to use DLR to see what’s possible, rather than as a production tool for needs you have today. Anything you create using DLR today is likely to require updates and changes tomorrow (assuming you can use DLR at all). The lack of solid DLR commitment by Microsoft is one reason this chapter provides an overview of ASP.NET application development, rather than in-depth information.

Getting DLR

Before you can use DLR, you must download and install it. The files you need for the installation appear at http://aspnet.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=17613. Download both the documentation and binaries files so that you have a complete setup. The documentation file is very small, so you might not think you even received the download at first.

The files are simple ZIP files, so you can extract them to your root directory. The examples in this chapter use a source folder of C:ironpython-2.6-beta1-aspnet-200905 for the DLR-specific examples. You need to change the path in the examples to match the location you used to extract the files on your machine.

The ironpython-2.6-beta1-aspnet-200905.ZIP file contains two folders: bin and examples. The bin folder contains a complete set of IronPython files, including the files required to make an IronPython script work as an ASP.NET application.

Using the Sample Applications

The ironpython-2.6-beta1-aspnet-200905examples folder contains two example applications. You should run at least one of these examples to ensure you have a good installation (simple as it is, sometimes there’s a configuration on your machine that prevents the examples from working as intended). To start as simply as possible, use the following steps to run the basic Hello Web forms example:

  1. Open a command prompt. It doesn’t have to be a VS2010 command prompt — any command prompt will do. If you’re using Vista or above, make sure you open the command prompt with administrator rights by right-clicking the Command Prompt icon in the Start menu and choosing Run As Administrator from the context menu.
  2. Type CD WINDOWSMicrosoft.NETFrameworkv2.0.50727 and press Enter to change directories to the .NET Framework 2.0 folder on your system. If you don’t have the .NET Framework 2.0 installed on your system, then type CD Program FilesCommon Files Microsoft SharedDevServer10.0 and press Enter to gain access to the ASP.NET Development Server folder on your system.
  3. Type WebDev.WebServer /port:85 /path:C:ironpython-2.6-beta1-aspnet-200905 exampleshello-webforms and press Enter (change your path information if you need to do so). You may need to type WebDev.WebServer20 if you’re using an alternate folder location. This action will start the ASP.NET Development Server on port 85. You’ll see an ASP.NET Development Server icon appear in the Notification Area.
  4. Right-click the ASP.NET Development Server icon and choose Open in Web Browser from the context menu. Your Web browser should open up and you should see a simple Web page like the one shown in Figure 11-1.

    This simple Web page relies on IronPython for support.
    Figure 11-1: This simple Web page relies on IronPython for support.
  5. Test the Web page to see if it works. Type your name in the Enter Your Name field and click Submit. You should see your name appear in place of the Your Name Here label shown in Figure 11-1. It’s a really simple example, but it will tell you whether you‘re getting the right results.
  6. Right-click the ASP.NET Development Server icon and choose Stop from the context menu. The server is no longer available.

The DLR package includes a second example. To use it, simply type WebDev.WebServer /port:85 / path:C:ironpython-2.6-beta1-aspnet-200905examplesalbum-handler and press Enter at the command prompt (again, make sure you use a path that matches your machine setup). This example is a little more complicated. When you initially display the browser, you see a list of filenames, which isn’t particularly helpful. Click the album-ipy.aspx entry and wait a few seconds. Eventually, you’ll see the Test icon shown in Figure 11-2.

The second example is a little more interesting
Figure 11-2: The second example is a little more interesting — at least it has graphics.

Click the Test icon and you’ll see more graphics. You can click the La Flore icon to see some flowers, or click one of the scenic images, as shown in Figure 11-3. Spend a bit of time with this application and you’ll find that it really is pretty interesting. Now, consider that this application is written in IronPython. Even though DLR isn’t a fully supported technology yet, it does have some amazing capabilities.

Creating a Web Site

You might think that creating an IronPython Web site is going to be complex at first — especially with a lack of IDE support. However, an IronPython Web site doesn’t really contain many parts that you have to worry about. A basic Web site starts with the root folder, which contains three items:

  • An Active Server Page Framework (.ASPX) file containing the user interface
  • The code behind file for the .ASPX file containing IronPython code
  • The Web.CONFIG file that contains all of the required configuration entries (such as the location of the special binaries used to interpret the IronPython script)

In addition to these files, you need a bin folder that contains the executables for working with IronPython. These files won’t vary between projects, so you may as well copy the bin folder whenever you create a new project. The following list is the files found in the bin folder.

  • IronPython.DLL
  • IronPython.Modules.DLL
  • Microsoft.Dynamic.DLL
  • Microsoft.Scripting.Core.DLL
  • Microsoft.Scripting.DLL
  • Microsoft.Scripting.ExtensionAttribute.DLL
  • Microsoft.Web.Scripting.DLL

It’s important to remember that IronPython is scripted. As a consequence, much of the compiled language baggage that you might have had to consider in the past isn’t an issue when working with IronPython. Don’t let the seeming simplicity of the Web site fool you, however; IronPython is just as capable as any other language.

Building a Basic ASP.NET Site Using IronPython

You’ve seen someone else’s examples for working with IronPython on a Web site. Now it’s time to create an IronPython Web site of your own. The example in this section isn’t meant to do anything too impressive. This section focuses on a process you can use to build ASP.NET applications of your own instead. The following sections describe every step you need to take to create any ASP.NET application using IronPython.

Creating the Project

Neither Visual Studio nor DLR provides a project template for you to use. In addition, you have more work to do when creating an ASP.NET application, so creating a project isn’t quite as easy as it should be. The DLR instructions suggest copying the hello-webforms folder to another location and using it as a starting point, which will work but will prove cumbersome. The following steps create a different kind of setup, one that will prove better in the long run, but require more work on your part now.

  1. Create a folder for your application. The example uses Calculator, but you can use any name you want.
  2. Copy the hello-webformsbin folder to your project folder.
  3. Create a .BAT file (the example uses Start.BAT) with the following content:
    [code]
    @Echo Off
    REM Change this directory to the location of WebDev.Webserver
    CD WINDOWSMicrosoft.NETFrameworkv2.0.50727
    REM Start the Web server.
    Start /B WebDev.WebServer /port:%1 /path:%2
    REM Open the Web browser to the right Web site.
    Start “C:Program FilesMozilla FirefoxFirefox” http://localhost:%1/
    @Echo On
    [/code]
    This batch file changes directories to the correct location for the WebDev.Webserver utility. It then starts the WebDev.Webserver utility with the port and path information you provide as part of the project arguments. Finally, it starts your browser so you can see the results.
  4. Start Visual Studio and choose File ➪ Open ➪ Project/Solution. Locate CMD.EXE found in the WindowsSystem32 folder and click Open. This step creates a project based on the command processor.
  5. Right-click cmd in Solution Explorer and choose Properties from the context menu. You see the General tab of the cmd Properties window, as shown in Figure 11-4.

    Set the properties for your project.
    Figure 11-4: Set the properties for your project.
  6. Type /C Start.BAT 85 “C:255 – Source CodeChapter11Calculator“ in the Arguments field. The arguments start with the /C command line switch, which tells the command processor to process all of the commands you’ve requested, and then terminate itself. The Start.BAT argument is the file you created in Step 3. This batch file requires two input arguments, the port number you want to use and the location of the example application.
  7. Type the location of your project, such as C:255 – Source CodeChapter11 Calculator, in the Working Directory folder.
  8. Right-click the Solution entry in Solution Explorer and choose Add ➪ New Item from the context menu. You’ll see the Add New Item dialog box shown in Figure 11-5.
  9. Highlight the HTML Page template and type Default.ASPX in the Name field. Click Add. Visual Studio adds a new ASPX file to your project.
  10. Right-click the Solution entry in Solution Explorer and choose Add ➪ New Item from the context menu to display the Add New Item dialog box again.
  11. Highlight the Text File template and type Default.ASPX.py in the Name field. Click Add. Visual Studio adds the code behind file for the Default.ASPX page.
  12. Copy the Web.CONFIG file from the hello-webforms folder to your project folder.
  13. Right-click the Solution entry in Solution Explorer and choose Add ➪ Existing Item from the context menu to display the Add Existing Item dialog box shown in Figure 11-6.
    Select items to add from the Add New Item dialog box.
    Figure 11-5: Select items to add from the Add New Item dialog box.

    Copy the Web.CONFIG file from an existing project.
    Figure 11-6: Copy the Web.CONFIG file from an existing project.
  14. Locate and highlight the Web.CONFIG file as shown in Figure 11-6. Click Add. Your project is now ready to go.

Defining the User Interface

The template used for this example is actually an .HTM file so it doesn’t contain a few essential entries; you need to use it as an .ASPX file. First, you must tell the Web service which language to use and where to find the code behind file. Add the following code as the first line in the .ASPX file.

[code]
<%@ Page Language=”IronPython” CodeFile=”Default.aspx.py” %>
[/code]

When you complete this step, close and then reopen the Default.ASPX file. Otherwise, the IDE is going to spend a lot of time complaining. The next step is to change where the code runs. You want it to run at the server so you change the <head> tag, as shown in the following code:

[code]
<head runat=”server”>
[/code]

Now you need to create content for the Web page. In this case, the user interface provides a simple four-function calculator. Listing 11-1 shows the code needed to perform this task.

Listin g 11-1: Defining a user interface for the example application

[code]
<form ID=”form1” runat=”server”>
<div>
<asp:Label ID=”lblInput” runat=”server”
text=”Type an input value:”/>
<asp:TextBox ID=”txtInput” runat=”server” />
<asp:Label ID=”lblError” runat=”server”
text=”Type a number in the input field!”
style=”color:red”
visible=”false” />
</div>
<div>
<asp:Button ID=”btnAdd” runat=”server” text=”+”
OnClick=”btnAdd_Click” />
<asp:Button ID=”btnSub” runat=”server” text=”-“
OnClick=”btnSub_Click” />
<asp:Button ID=”btnMul” runat=”server” text=”*“
OnClick=”btnMul_Click” />
<asp:Button ID=”btnDiv” runat=”server” text=”/“
OnClick=”btnDiv_Click” />
</div>
<div>
<asp:Label ID=”lblResult” runat=”server”
text=”Current Value:” />
<asp:TextBox ID=”txtResult” runat=”server” text=”0”
readonly=”true” />
</div>
<div>
<asp:Button ID=”btnClear” runat=”server” text=”Clear”
OnClick=”btnClear_Click” />
</div>
</form>
[/code]

As the listing shows, you work with IronPython code in the same way that you work with any .ASPX file. Unfortunately, the designer has not a clue as to what to do with your code, so you have to write it all by hand. Theoretically, you could start a Web project and then simply move the .ASPX file from that project to your IronPython project, but that seems like a lot of work unless your interface is relatively complex. If you find that you can’t quite remember all the ASP.NET controls at your disposal, you can find a complete list at http://www.w3schools.com/ASPNET/aspnet_refwebcontrols.asp in an easily accessible form.

In this case, the controls appear in four separate groups: input, control, result, and clearing. Figure 11-7 shows a typical view of the example form. Some common errors that developers make are not including the runat=”server” attribute and not providing the proper connectivity to events in the code behind, such as OnClick=”btnClear_Click”. Notice that you can use styles, just as you normally do, with the style attribute. One of the attributes that developers can forget about is visible=”false”, which makes the control invisible.

The example form is a simple four-function calculator.
Figure 11-7: The example form is a simple four-function calculator.

Creating the Code Behind

The code behind for this example is in pure IronPython. So, while you won’t see much difference in Default.ASPX, you’ll find that Default.ASPX.py looks completely different from any Web project you’ve worked with in the past. Listing 11-2 shows the code for this example.

Listin g 11-2: Creating the code behind for the example

[code]
# Import the required assemblies.
from System import *
# Respond to an Add button click.
def btnAdd_Click(sender, e):
# Get the current value.
Value = Int32.Parse(txtResult.Text)
# Reset the error message label.
lblError.Visible = False
# Obtain the new input value.
try:
Addition = Int32.Parse(txtInput.Text)
except:
# Display an error message when necessary.
lblError.Visible = True
return
# Perform the task and return the result.
Value = Value + Addition
txtResult.Text = str(Value)
# Respond to a Subtraction button click.
def btnSub_Click(sender, e):
# Get the current value.
Value = Int32.Parse(txtResult.Text)
# Reset the error message label.
lblError.Visible = False
# Obtain the new input value.
try:
Subtract = Int32.Parse(txtInput.Text)
except:
# Display an error message when necessary.
lblError.Visible = True
return
# Perform the task and return the result.
Value = Value – Subtract
txtResult.Text = str(Value)
# Respond to a Multiplication button click.
def btnMul_Click(sender, e):
# Get the current value.
Value = Int32.Parse(txtResult.Text)
# Reset the error message label.
lblError.Visible = False
# Obtain the new input value.
try:
Multiply = Int32.Parse(txtInput.Text)
except:
# Display an error message when necessary.
lblError.Visible = True
return
# Perform the task and return the result.
Value = Value * Multiply
txtResult.Text = str(Value)
# Respond to a Division button click.
def btnDiv_Click(sender, e):
# Get the current value.
Value = Int32.Parse(txtResult.Text)
# Reset the error message label.
lblError.Visible = False
# Obtain the new input value.
try:
Divide = Int32.Parse(txtInput.Text)
except:
# Display an error message when necessary.
lblError.Visible = True
return
# Perform the task and return the result.
Value = Value / Divide
txtResult.Text = str(Value)
# Respond to a Clear button click.
def btnClear_Click(sender, e):
txtResult.Text = ‘0’
[/code]

The code begins by importing the required assemblies. As with any IronPython application, you can use a combination of Python modules and .NET assemblies to create your application. You also have full access to both Python and .NET functionality in your application, so the considerable flexibility that IronPython provides is still available in this environment.

Each of the event handlers must provide both the sender and e arguments as shown. You don’t include a self argument in this case, as you would with other IronPython code. As you might expect, the sender argument contains a reference to the control that called the event handler, while e contains a list of event arguments (normally set to None).

The four math buttons begin by obtaining the current value of txtResult (the output TextBox) as an Int32 value. Because txtResult is read-only, you don’t need to worry about someone putting an incorrect value into it. Consequently, this task doesn’t provide any error trapping code.

The next step is to obtain the new value for the math operation from txtInput. In this case, you’re relying on the user to provide the correct input value, which means that the application code could receive anything. Someone might even try to enter a script in order to fool your application into doing something improper. Using the Int32.Parse() method means that any input other than a number triggers an exception, which your code can handle by simply not processing the input. The try…except structure does just that. If the user inputs an incorrect value, the Web page displays an error message, rather than doing anything with the input.

Now that the code has two inputs to process, it performs the required math operation. After the math operation is complete, the code outputs the result to txtResult.Text.

The btnClear_Click() event handler is relatively simple. All it does is place a 0 in txtResult .Text. The next math operation starts with a zero value, which means that txtResult is cleared.

Starting the Visual Studio Built-In Web Server

It’s time to begin testing your application. Many developers don’t realize it, but the .NET Framework includes a special utility that makes it possible to host Web sites without having a full-fledged Web server. The WebDev.WebServer utility originally appeared as part of the .NET Framework 2.0. When you build an application for testing purposes with Visual Studio, you’re using this built-in Web server to execute the code.

You find the WebDev.WebServer.EXE file in the WINDOWSMicrosoft.NETFrameworkv2.0.50727 folder on your system. Alternatively, you can also find versions of this utility in the Program Files Common FilesMicrosoft SharedDevServer10.0 folder as WebDev.WebServer20.EXE or WebDev.WebServer40.EXE.

The amazing part of the built-in Web server is that it works fine for any Web site using any kind of code. If you want to test your standard HTML pages, that’s fine — just point the built-in Web server to the correct directory on your hard drive. Of course, you can’t run some types of applications because the built-in Web server isn’t designed to handle them. For example, you can’t execute your PHP code. This little deficiency doesn’t matter for your IronPython application, however, because the built-in Web server will see it as a standard ASP.NET application.

Unlike your full-fledged Web server, the built-in Web server doesn’t provide outside access, which is the reason you want to use it to test your unsecured, experimental IronPython Webforms application. You don’t have to worry about prying eyes seeing your tests and possibly using them as a means to gain entrance to your machine. More important, because this server is virtual, it’s less likely that a failed experiment will cause your system to crash. The following sections describe the WebDev.WebServer utility in more detail.

Understanding the WebDev.WebServer Command Line Syntax

The WebDev.WebServer utility provides only a few command line switches because you perform most configuration tasks using a special Notification Area icon. Here’s the command line syntax for this utility.

[code]
WebDev.WebServer /port:<PortNumber> /path:<PhysicalPath> [/vpath:<VirtualPath>]
WebDev.WebServer20 /port:<PortNumber> /path:<PhysicalPath>
[/vpath:<VirtualPath>]
WebDev.WebServer40 /port:<PortNumber> /path:<PhysicalPath>
[/vpath:<VirtualPath>]
[/code]

The following list provides an explanation of each of the command line switches.

  • /port:PortNumber: Defines the port number used to host the application. Because your application isn’t accessible to the outside world and you use a local browser to access the Web server, the port you use isn’t as important as it usually is. You can’t select port 80 if you also have IIS installed on the system. Any port number between 1 and 65,535 will work as long as the port you select isn’t in use.
  • /path:PhysicalPath: Specifies the physical location of the application you want to host in the browser. You must provide the full path, but you don’t include a filename.
  • /vpath:VirtualPath: Provides a virtual path for the application where VirtualPath is normally the application name, such as /MyApp. The default setting provides a virtual path of /.
  • ?: Displays the help information for the WebDev.WebServer utility.

Using the Built-In Web Server with a Batch File

When you use a batch (.BAT) file to start the application, use the Start utility to execute WebDev .WebServer. Otherwise, the Web server won’t start properly. In addition, you should include the Start utility’s /B command line switch (it isn’t mandatory). The /B command line switch tells Windows not to open a new window to start the application. If Windows opens a new window, the Web server will start, but it may not display the Web page. Here’s a modified command line for batch files.

[code]
Start /B WebDev.WebServer /port:7171 /path:”F:My Web Site”
[/code]

Interacting with the Built-In Web Server

The WebDev.WebServer utility creates an icon in the Notification Area when you start it. In fact, a popup message alerts you to the presence of this icon. Right-click this icon and you see three options:

  • Open in Web Browser: Tells the utility to start the default Web browser and navigate to the Web page hosted by the Web server. In most cases, WebDev.WebServer uses the same defaults as any full Web server you have set up on your machine. Otherwise, you can count on these defaults working:
    • Default.HTM
    • Default.ASP
    • Default.ASPX
    • Index.HTM
  • Stop: Stops the server and makes any Web pages inaccessible.
  • Show Details: Displays the ASP.NET Development Server dialog box shown in Figure 11-8 where you can see details about the Web server. In addition, this dialog box provides a link to access the default Web page.
The ASP.NET Development Server dialog box provides details about the Web server.
Figure 11-8: The ASP.NET Development Server dialog box provides details about the Web server.

Considering the Built-In Web Server Limitations

It’s important to realize that we’re not using the WebDev.WebServer utility for production purposes. The following list helps you better understand why you can’t use this utility for every purpose.

  • Functionality: The WebDev.WebServer utility doesn’t create a full-fledged Web server. Some of the functionality you rely on, such as the ability to add users or work with virtual directories, simply isn’t available.
  • Security: Any security that you want to set up has to appear as part of your application in a Web.CONFIG file. The WebDev.WebServer utility does tend to follow whatever rules you set for Internet Information Server (IIS) if you have it installed. However, you can’t count on this behavior when working on your own and you certainly can’t count on it when you send the application to other machines.
  • Administrative tools support: Anything you normally configure through the Internet Information Services console located in the Administrative Tools folder of the Control Panel is unavailable when working with the WebDev.WebServer utility. Consequently, if your application relies on a special ISAPI Filter, you won’t be able to execute it using the WebDev.WebServer utility. The same holds true for anything else that you normally have to add using the Internet Information Services console.
  • Single user Web server: It’s important to remember that this is a single-user Web server. No one outside the local machine can access the Web server because theoretically, it doesn’t exist — it’s virtual. This means that you can’t perform some types of testing using the built-in Web server. The feature that makes it so secure also prevents you from performing some kinds of real-world testing. Multi-user applications simply won’t work with the built-in Web server.

Savvy developers can get around some of the WebDev.WebServer configuration limitations through judicious use of the Web.CONFIG file and by creating resources locally. Make sure you don’t assume that an application won’t work simply because you have to configure it in a new way.

Performing Basic Testing

Your IronPython Web application works just like any other Web application you create. Because IronPython is fully .NET capable, you can use any control set you want within the application. Of course, you also have access to standard Web controls. All of this flexibility increases complexity and makes it necessary to test your application fully.

The example application is relatively simple, so testing isn’t cumbersome. When you first start the application by pressing Ctrl+F5, you see the Web page shown in Figure 11-7. When you type a value into the Type an Input Value field and click one of the math buttons (+, -, *, or /), the application performs the desired task. Trying to input an invalid value triggers an error message like the one shown in Figure 11-9.

Incorrect values trigger an error message.
Figure 11-9: Incorrect values trigger an error message.

Interestingly enough, the scripting nature of IronPython makes it possible to use IronPython to test your Web application. This gives you an advantage over compiled languages such as C# and Visual Basic.NET.

Considering Debugging

You may be wondering whether this project can provide any debugging. The fact is that you don’t get direct debugging when working with DLR, even if you use a full Web server. However, there are four ways in which you can debug your application.

  • Use print statements and other old standbys to determine what your application is doing.
  • Attach the debugger to the running process after the fact by choosing Debug ➪ Attach to Process within Visual Studio.
  • Import the Microsoft.Scripting.Debugging.DLL found in the Program Files IronPython 2.6 folder and add debugging information manually.
  • Rely on the output error message from the Web server, such as the one shown in Figure 11-10.

    The Web server provides you with error messages as needed.
    Figure 11-10: The Web server provides you with error messages as needed.

Even though Webforms use isn’t quite ready for prime time, you should still take time to experiment. At the very least, try the example programs that come with DLR to see that IronPython is a viable Web development language. Use the example application in this chapter as a starting point for your own experimentation. In short, have a bit of fun using IronPython to create Web pages. Consider how the dynamic nature of IronPython could help you in future Web application development, because DLR won’t remain in alpha status forever.

Advanced Linear and Angular Velocity

Calculating Angular Velocity

We have done a lot of work already with sprite transforms, so now it’s time to put some of these new features to the test in a real-world situation that often comes up in game projects. We’ll have a sprite move on the screen based on user input, and move in the direction it is facing. This requires some familiar trigonometry functions used in a creative way.

To begin, we need to understand the starting point for trigonometric calculations. The artwork in a game is often oriented with the “front” or “nose” pointing upward. But in our math calculations, that starting point will always be to the right, or 90 degrees clockwise from the up direction, as shown in Figure 9.1.

Trigonometry functions assume that angle 0 is right, not up.
FIGURE 9.1 Trigonometry functions assume that angle 0 is right, not up.

Math functions dealing with rotation angles and velocities always work with radians, not degrees. Using degrees in code is fine, but angles must be converted to radians during calculations. This can be done with MathHelper.ToDegrees() and MathHelper.ToRadians().

We use cosine() to calculate the X component, and sine() to calculate the Y component for the velocity of an object. In XNA, we can use the Math.Cos() and Math.Sin() methods to perform these calculations. The sole parameter passed to both of these methods is the angle that an object is facing or moving toward.

The angle will be any value from 0 to 360 degrees, including decimal values for partial degrees. When the calculations are made, the angle must be converted to radians. Suppose that the angle is 10 degrees. We convert this to radians with the following:

[code]
float radians = MathHelper.ToRadians( 10 );
// answer: radians = 0.174532925
[/code]

The angular velocity is calculated using this radian value, rounded to 0.1745 for our purposes (although the complete floating-point value is used with all decimal places in memory):

[code]
Velocity X = Cos( 0.1745 )
Velocity Y = Sin( 0.1745 )
[/code]

Figure 9.2 shows a circle with the angle and calculated values.

Calculating angular velocity.
FIGURE 9.2 Calculating angular velocity.

The results are X = 0.9848 and Y = 0.1736, as shown in the illustration. Consider the direction the arrow is facing in the illustration (10 degrees). The X and Y velocity values make sense, given that angle. Considering pixel movement on the screen, at this angle a sprite will move in the X axis much more than in the Y axis, a ratio of about five-and-a-half to one (5.5:1). So, when a sprite is moving across the screen at an angle of 10 degrees, it will move 5.5 pixels to the right (+X) for every 1 pixel down (+Y). If the angle were 180, for instance, the arrow would be pointing to the left, which would result in a negative X velocity.

Updating the Sprite Class

Some changes are needed for the Sprite class to work with the upcoming sample program. There are some new variables and some improvements to the Draw() and Rotate() methods. To make rotation more versatile, the origin variable (a float) has been moved out of Draw() and into the class’s public declarations so that it can be modified as a public variable. The Rotate() method has some improvements to make its boundary checking more accurate. The changes are included in Listing 9.1.

LISTING 9.1 Yet even more changes to the Sprite class!

[code]
public class Sprite
{
private ContentManager p_content;
private SpriteBatch p_spriteBatch;
public Texture2D image;
public Vector2 position;
public Vector2 velocityLinear;
public Color color;
public float rotation;
public float velocityAngular;
public Vector2 scaleV;
public Vector2 origin; //new
public bool alive; //new
public bool visible; //new
public Sprite(ContentManager content, SpriteBatch spriteBatch)
{
p_content = content;
p_spriteBatch = spriteBatch;
image = null;
position = Vector2.Zero;
velocityLinear = Vector2.Zero;
color = Color.White;
rotation = 0.0f;
velocityAngular = 0.0f;
scaleV = new Vector2(1.0f);
origin = Vector2.Zero; //new
alive = true; //new
visible = true; //new
}
public float scale
{
get { return scaleV.X; }
set
{
scaleV.X = value;
scaleV.Y = value;
}
}
public bool Load(string assetName)
{
try
{
image = p_content.Load<Texture2D>(assetName);
origin = new Vector2(image.Width / 2, image.Height / 2); //new
}
catch (Exception) { return false; }
return true;
}
public void Draw()
{
//Vector2 origin = new Vector2(image.Width / 2, image.Height / 2);
p_spriteBatch.Draw(image, position, null, color, rotation,
origin, scaleV, SpriteEffects.None, 0.0f);
}
public void Move()
{
position += velocityLinear;
}
public void Rotate()
{
rotation += velocityAngular;
if (rotation > Math.PI * 2)
rotation -= (float)Math.PI * 2; //change
else if (rotation < 0.0f)
rotation = (float)Math.PI * 2 – rotation; //change
}
}
[/code]

There’s a great tutorial lesson on all the functions of trigonometry on Wikipedia here: http://en.wikipedia.org/wiki/Trigonometric_functions.

Apache Helicopter Demo

The example for this section is included in the hour resource files, so you may open the project while studying the code in Listing 9.2. This demo draws a small sprite of an Apache helicopter firing bullets at whatever angle it is facing. Touching the top of the screen will cause the chopper’s nose to rotate upward. Likewise, touching the bottom of the screen will rotate the chopper’s nose downward. Touching the center of the screen will cause the chopper to fire its bullets in its current facing angle. Figure 9.3 shows the program running, and it looks almost like the start of a game! It could be, with a little work! As we continue to improve the Sprite class, the source code for our example programs continue to shrink!

The Apache helicopter demo.
FIGURE 9.3 The Apache helicopter demo.

LISTING 9.2 Source code to the Apache helicopter demo utilizing the improved Sprite class.

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Viewport screen;
SpriteFont font;
Sprite chopper;
Texture2D bullet;
Sprite[] bullets;
float rotation;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
screen = GraphicsDevice.Viewport;
//create the font
font = Content.Load<SpriteFont>(“WascoSans”);
//create the helicopter sprite
chopper = new Sprite(Content, spriteBatch);
chopper.Load(“apache”);
chopper.position = new Vector2(120, 240);
chopper.origin = new Vector2(100, 22);
//load bullet image
bullet = Content.Load<Texture2D>(“bullet”);
//create bullet sprites
bullets = new Sprite[10];
for (int n = 0; n < 10; n++)
{
bullets[n] = new Sprite(Content, spriteBatch);
bullets[n].image = bullet;
bullets[n].alive = false;
}
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
//get state of touch inputs
TouchCollection touchInput = TouchPanel.GetState();
//get rotation
rotation = MathHelper.ToDegrees(chopper.rotation);
//look at all touch points
foreach (TouchLocation touch in touchInput)
{
if (touch.Position.Y < 180) //top of screen
rotation -= 1.0f;
else if (touch.Position.Y > 300) //bottom
rotation += 1.0f;
else
Fire(); //middle
}
//keep rotation in bounds
if (rotation < 0.0f)
rotation = 360.0f – rotation;
else if (rotation > 360.0f)
rotation = 360.0f – rotation;
//save rotation
chopper.rotation = MathHelper.ToRadians(rotation);
//move the bullets
for (int n = 0; n < 10; n++)
{
if (bullets[n].alive)
{
bullets[n].Move();
if (bullets[n].position.X > 800)
bullets[n].alive = false;
}
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
//draw the chopper
chopper.Draw();
//draw the bullets
for (int n = 0; n < 10; n++)
{
if (bullets[n].alive)
bullets[n].Draw();
}
string text = “Angle: “ + rotation.ToString(“N4”);
spriteBatch.DrawString(font, text, new Vector2(200, 440),
Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
void Fire()
{
//look for an unused bullet
for (int n = 0; n < 10; n++)
{
if (!bullets[n].alive)
{
bullets[n].alive = true;
bullets[n].position = chopper.position;
bullets[n].rotation = chopper.rotation;
//calculate angular velocity
float x = (float)Math.Cos(bullets[n].rotation) * 10.0f;
float y = (float)Math.Sin(bullets[n].rotation) * 10.0f;
bullets[n].velocityLinear = new Vector2(x,y);
break;
}
}
}
}
[/code]

“Pointing” a Sprite in the Direction of Movement

You might be thinking, Didn’t we just do this? That’s an astute question! In fact, in the preceding example, the bullet sprites were pointed in a certain direction, and we just added the angular velocity code to make them move in that direction. Now we’re going to do the reverse: Given the direction a sprite is already moving, we want it to “point” in that direction so that it looks right. To demonstrate, we’ll cause the spaceship sprite to “orbit” around a planet and rotate while moving. To more accurately describe this situation, we want a sprite to move and point toward a target.

The trigonometry (“circular”) functions we’ve been using can be considered elementary physics. In a sense, then, we’re working on our own simple physics engine here, which is a bit of a stretch but still compelling!

Have you ever played an RTS (real-time strategy) game in which you can select units, then right-click somewhere on the map, and they would move toward that location? That is the basic way most RTS games work. Along the path, if your units encounter enemy units, they will usually fight or shoot at the enemy, unless you tell them to target a specific enemy unit with a similar right-click on it.

Well, we can do something like that with the concept covered here. Oh, there’s a lot more involved in an RTS game than just moving toward a destination, but at the core of the game is code similar to what we’re going to learn about here.

Calculating the Angle to Target

In the preceding section, we used Math.Cos() and Math.Sin() to calculate the respective X and Y components of velocity (a Vector2). These values could then be used to move a sprite in any desired direction. There’s a related calculation we can perform to do the opposite: Given a sprite’s current location, and a target location, we can calculate the angle needed to get there.

We won’t be using sine and cosine to calculate the angle. Those trig functions are useful only if you know the angle already. The reverse is, knowing where we are already headed, what is that angle?

This concept is powerful in terms of gameplay! Let’s say you do know the angle and use it to calculate velocity, then send a bullet on its way, as we did in the preceding example. Okay, that’s great. But what if you wanted to slow down that sprite, make it stop, and even begin moving in reverse? Not in terms of a bullet, but any sprite, like a spaceship or a car? We can do these things.

This code could be used to cause one sprite to continually point at another sprite. Instead of using a target screen coordinate, use the location of a moving target sprite instead!

The secret behind all this advanced velocity code is another trig function called arctangent. Arctangent is an inverse trigonometric function—specifically, the inverse of tangent, which itself is opposite over adjacent (side a divided by side b), as shown in Figure 9.4. Since I don’t want to get into deriving these trig functions, let’s just jump to the function name in XNA. There are two versions: Math.Atan(), which takes one parameter, and Math.Atan2(), which takes two parameters (double y, double x). This math function returns the angle whose tangent is the quotient of two specified numbers.

A right triangle is the basis for trigonometry. Illustration courtesy of Wikipedia.
FIGURE 9.4 A right triangle is the basis for trigonometry. Illustration courtesy of Wikipedia.

We can’t just pass the position (X and Y) of a target screen coordinate to this function, because the two parameters are actually delta values (the difference between the X and Y values of two points). That’s not as bad as it sounds, as any math major will tell you. Delta is just the difference between the two points: X2 – X1 and Y2 – Y1.

[code]
double deltaX = x2 – x1;
double deltaY = y2 – y1;
[/code]

Having these variables ready, we can calculate the angle toward a target with arctangent in XNA like so:

[code]
double angle = Math.Atan2(deltaY,deltaX);
[/code]

Shuttle Orbit Demo

So now we know how to calculate the angle to a target location. What now? Let’s put this new knowledge to use in another sample program. This one will simulate a spaceship orbiting a planet. While it’s rotating around the planet, the nose of the ship will rotate so it is oriented in the direction of movement. This isn’t exactly moving toward a target on the screen, but given the ship’s previous position and the current position, we can figure out what in direction the nose should be pointing in reverse. Figure 9.5 shows the program running, showing the space shuttle not just rotating around a planet, but rotating to keep the nose pointing forward. This isn’t exactly realistic, because while in orbit, it doesn’t matter which direction a ship or satellite is facing—it will continue to orbit the planet. But it looks cool this way in a game, especially to a younger audience. Listing 9.3 contains the source code for the program.

The spaceship points toward its path while rotating around the planet.
FIGURE 9.5 The spaceship points toward its path while rotating around the planet.

LISTING 9.3 Source code for the orbiting spaceship program.

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Viewport screen;
SpriteFont font;
Sprite shuttle, planet;
float orbitRadius, orbitAngle;
Vector2 oldPos;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
screen = GraphicsDevice.Viewport;
//create the font
font = Content.Load<SpriteFont>(“WascoSans”);
//create the planet sprite
planet = new Sprite(Content, spriteBatch);
planet.Load(“planet1”);
planet.scale = 0.5f;
planet.position = new Vector2(400, 240);
//create the ship sprite
shuttle = new Sprite(Content, spriteBatch);
shuttle.Load(“shuttle”);
shuttle.scale = 0.2f;
orbitRadius = 200.0f;
orbitAngle = 0.0f;
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
//remember position for orientation
oldPos = shuttle.position;
//keep angle within 0-360 degs
orbitAngle += 1.0f;
if (orbitAngle > 360.0f)
orbitAngle -= 360.0f;
//calculate shuttle position
float x = 400 + (float)Math.Cos(MathHelper.ToRadians(orbitAngle))
* orbitRadius;
float y = 240 + (float)Math.Sin(MathHelper.ToRadians(orbitAngle))
* orbitRadius;
//move the position
shuttle.position = new Vector2(x, y);
//point shuttle’s nose in the right direction
float angle = TargetAngle(shuttle.position, oldPos);
//subtract 180 degrees to reverse the direction
angle = MathHelper.WrapAngle(angle – MathHelper.ToRadians(180));
//adjust for artwork pointing up
angle += MathHelper.ToRadians(90);
//update shuttle’s rotation
shuttle.rotation = angle;
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
planet.Draw();
shuttle.Draw();
string text = “Angle: “ + shuttle.rotation.ToString(“N4”);
spriteBatch.DrawString(font, text, new Vector2(200, 440),
Color.White);
text = “Radius: “ + orbitRadius.ToString(“N0”);
spriteBatch.DrawString(font, text, new Vector2(450, 440),
Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
float TargetAngle(Vector2 p1, Vector2 p2)
{
return TargetAngle(p1.X, p1.Y, p2.X, p2.Y);
}
float TargetAngle(double x1,double y1,double x2,double y2)
{
double deltaX = (x2-x1);
double deltaY = (y2-y1);
return (float)Math.Atan2(deltaY,deltaX);
}
}
[/code]

More Sprite Transforms: Rotation and Scaling

Rotating a Sprite

XNA performs sprite rotation for us. That, in a nutshell, is the gist of this section on the subject. Digging into the trigonometry reveals that rotation calculations are in relation to the origin of the coordinate system at (0,0). It is possible to rotate a sprite the “hard way” by rotating each pixel of the sprite using the trigonometry functions sine() and cosine() the way they were used in the previous hour to rotate planets around the sun in the Solar System Demo. The same basic principle is used to rotate a sprite, but it is done very quickly in XNA thanks to a very fast SpriteBatch. Draw() method that can handle rotation like a cinch.

Since we have full control over how fast a sprite can rotate or revolve in a scene (such as the planets in the Solar System Demo), it is possible to use rotation with just partial circles to give a sprite complex movements by linking arclike movements together in interesting ways.

Rotation Angles

Not too many years ago, artists on a game project would prerotate all sprites in order to preserve quality. Back in the early days of game development, graphics hardware was very limited—which is why game programming in the “early years” was such a black art, because it required extensive knowledge of the hardware in order to eke out every possible CPU cycle to the fullest. Most games for Nintendo’s original NES fit on a 512Kb ROM cartridge. Compare that with games today, in which a single sprite animation stored as a texture might take up a few megabytes.

Figure 8.1 shows an example of the rotation frames often used in early games. The first direction is usually 0 degrees (north), and each successive frame is 45 degrees clockwise, as listed in Table 8.1.

A sprite pointing in the most common eight directions.
FIGURE 8.1 A sprite pointing in the most common eight directions.

 

Sprite Rotation Frame Directions and Angles

As the number of frames increases, so does the quality of the rotated animation, at which point we might end up with the 32 frames of rotation shown in Figure 8.2. Despite the large number of animation frames here in the form of a sprite sheet, this is not truly animation; it is just a prerendered rotation sequence. Each angle in the 32 frames of rotation represents 11 degrees of rotation. In this situation, compared to the 8-frame rotation, with 45 degrees per angle, you can see that there are four times as many frames for much higher quality.

A 32-frame prerotated sprite sheet.
FIGURE 8.2 A 32-frame prerotated sprite sheet.

Rotation actually takes place around the origin, and in trigonometric terms, we’re still working with the Cartesian coordinate system. Figure 8.3 shows an illustration of a point being rotated around the origin a certain interval.

Rotation takes place around the origin (0,0) in Cartesian coordinates.
FIGURE 8.3 Rotation takes place around the origin (0,0) in Cartesian coordinates.

Sprite Class Changes

We have been slowly adding new features to the Sprite class over the past two hours, and now the class will receive some much-needed rotation know-how. First, to differentiate between the old velocity and what we will need for rotational velocity, I have renamed the old velocity variable to velocityLinear, which is still a Vector2. A new variable has been added to the Sprite class called velocityAngular. A support method has been added, called Rotate(). Assuming that the velocityAngular property variable is set to a value other than zero, the Rotate() method will cause the sprite to rotate automatically (as the Move() method did for linear velocity in the preceding hour). Here is our new Sprite class:

LISTING 8.1 Source code for the even further improved Sprite class!

[code]
public class Sprite
{
private ContentManager p_content;
private SpriteBatch p_spriteBatch;
public Texture2D image;
public Vector2 position;
public Vector2 velocityLinear;
public Color color;
public float rotation;
public float velocityAngular;
public Sprite(ContentManager content, SpriteBatch spriteBatch)
{
p_content = content;
p_spriteBatch = spriteBatch;
image = null;
position = Vector2.Zero;
velocityLinear = Vector2.Zero;
color = Color.White;
rotation = 0.0f;
velocityAngular = 0.0f;
}
public bool Load(string assetName)
{
try
{
image = p_content.Load<Texture2D>(assetName);
}
catch (Exception) { return false; }
return true;
}
public void Draw()
{
p_spriteBatch.Draw(image, position, color);
}
public void Move()
{
position += velocityLinear;
}
public void Rotate()
{
rotation += velocityAngular;
if (rotation > Math.PI * 2)
rotation = 0.0f;
else if (rotation < 0.0f)
rotation = (float)Math.PI * 2;
}
}
[/code]

Drawing with Rotation

Besides the rotation code, some huge changes have had to be made to the Draw() method to accommodate rotation. We’re using the seventh and final overload of the SpriteBatch.Draw() method, which looks like this:

[code]
public void Draw( Texture2D texture,
Vector2 position,
Rectangle? sourceRectangle,
Color color,
float rotation,
Vector2 origin,
float scale,
SpriteEffects effects,
float layerDepth );
[/code]

There are nine parameters in this version of Draw(), which is capable of servicing all of our sprite needs for the next few chapters. But first things first: the rotation factor. The fifth parameter requires the rotation value as a float, so this is where we will pass the new rotation property (added to the preceding class).

For rotation to work correctly, we need to pass the right value for the origin parameter. The origin defines the point at which rotation will occur. If we pass Vector2.Zero, as in the sample call

[code]
Vector2 origin = new Vector2(0, 0);
float scale = 1.0f;
p_spriteBatch.Draw(image, position, null, color, rotation, origin,
scale, SpriteEffects.None, 0.0f);
[/code]

then the origin is set to the upper-left corner of the sprite (0,0), causing the result to look as shown in Figure 8.4, with the image rotation around the upper left.

The origin parameter defines the point at which rotation takes place.
FIGURE 8.4 The origin parameter defines the point at which rotation takes place.

We need to calculate the center of the image so that it will rotate correctly from the center of the image rather than the upper-left corner. This can be calculated easily enough using the width and height of the image:

[code]
Vector2 origin = new Vector2(image.Width/2,image.Height/2);
float scale = 1.0f;
p_spriteBatch.Draw(image, position, null, color, rotation,
origin, scale, SpriteEffects.None, 0.0f);
[/code]

This is the new and final code for Draw() at this point. We will also make minor changes to it again in the next section on scaling. The new version is shown in Figure 8.5.

Changing the origin to the center of the sprite’s image affects its position as well as rotation center—the origin becomes the focus for the position, even if the sprite is not rotated (with rotation set to 0.0f).

The sprite now rotates correctly, with the origin set to the center of the image.
FIGURE 8.5 The sprite now rotates correctly, with the origin set to the center of the image.

Sprite Rotation Demo

For this rotation demo, we will use user input to rotate the sprite either right or left, depending on where the screen is touched, on the right side or left side of the screen. The second screenshot of this demo is shown in Figure 8.5, referenced earlier. Now the sprite is centered on the screen and rotating from the center. Listing 8.2 contains the source code for the test program.

LISTING 8.2 Test program for the new rotation capability of our Sprite class.

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Viewport screen;
SpriteFont font;
Sprite ship;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
screen = GraphicsDevice.Viewport;
//create the font
font = Content.Load<SpriteFont>(“WascoSans”);
//create the ship sprite
ship = new Sprite(Content, spriteBatch);
ship.Load(“ship”);
ship.position = new Vector2(screen.Width/2, screen.Height/2);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
//get state of touch inputs
TouchCollection touchInput = TouchPanel.GetState();
//look at all touch points (usually 1)
foreach (TouchLocation touch in touchInput)
{
if (touch.Position.X > screen.Width / 2)
ship.velocityAngular = 0.05f;
else if (touch.Position.X < screen.Width / 2)
ship.velocityAngular = -0.05f;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
ship.Rotate();
ship.Draw();
string text = “Rotation: “ + ship.rotation.ToString(“N4”);
Vector2 size = font.MeasureString(text);
float x = (screen.Width – size.X) / 2;
spriteBatch.DrawString(font, text, new Vector2(x, 440), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}
[/code]

Scaling a Sprite

Sprite scaling is also handled by SpriteBatch.Draw(), but it doesn’t hurt to learn a little bit about how scaling happens. Like rotation, scaling occurs in relation to the origin of the Cartesian coordinate system, used for many trigonometry functions, although the most common we use in game programming are sine() and cosine(). Figure 8.6 shows a before-and-after result of an object (represented with just two points, which might be a line). The points are scaled by a factor of 0.5 (50%), resulting in the new positions shown.

Scaling also occurs in relation to the origin (0,0).
FIGURE 8.6 Scaling also occurs in relation to the origin (0,0).

In our previous example on rotation, the scale factor was hard-coded in the Sprite.Draw() method like so:

[code]
public void Draw()
{
Vector2 origin = new Vector2(image.Width / 2, image.Height / 2);
float scale = 1.0f;
p_spriteBatch.Draw(image, position, null, color, rotation,
origin, scale, SpriteEffects.None, 0.0f);
}
[/code]

We need to delete this out of Draw() and add a new variable to the class’s public declaration, with a new initializer in the constructor.

[code]
private ContentManager p_content;
private SpriteBatch p_spriteBatch;
public Texture2D image;
public Vector2 position;
public Vector2 velocityLinear;
public Color color;
public float rotation;
public float velocityAngular;
public Vector2 scale;
public Sprite(ContentManager content, SpriteBatch spriteBatch)
{
p_content = content;
p_spriteBatch = spriteBatch;
image = null;
position = Vector2.Zero;
velocityLinear = Vector2.Zero;
color = Color.White;
rotation = 0.0f;
velocityAngular = 0.0f;
scale = new Vector2(1.0f);
}
[/code]

The scale variable is no longer just a mere float, but has been upgraded to a Vector2. This will allow us to scale the sprite horizontally and vertically at different rates, if desired. This can be annoying, however. Every time you need to change the scale value, a Vector2 has to be set. I would much rather just set the scale as a float by default, and have the option of setting the scale individually for the horizontal or vertical axes. This is not because setting a Vector2 is time-consuming, but because the scale will most often be uniform on both axes. We want the most often-used properties and methods to reflect the most common need, not unusual needs.

We need to rename the scale variable to scaleV, and add a new property called scale. After making the modification, scaleV is initialized in the constructor, which sets both the X and the Y values. Anytime Sprite.scale is changed, both the X and the Y properties are changed.

[code]
scaleV = new Vector2(1.0f);
[/code]

The new scale property looks like this:

[code]
public float scale
{
get { return scaleV.X; }
set {
scaleV.X = value;
scaleV.Y = value;
}
}
[/code]

In the Sprite Scaling Demo coming up shortly, we can scale the sprite very large or very small by simply modifying the Sprite.Draw() method to use Sprite.scaleV (which is a Vector2). It is also now possible to scale the width and height of a sprite. See Figure 8.7.

We’re not going to add a scale velocity because that is so rarely needed that if the need does arise, it can be done with a global variable outside of the Sprite class. Or you can go ahead and add that capability to your version of the Sprite class if you want! In fact, let’s see the complete new version of the Sprite class (in Listing 8.3), just to be thorough, since that was a lot of new information to sort out.

Scaling a sprite up to 5× the normal size.
FIGURE 8.7 Scaling a sprite up to 5× the normal size.

LISTING 8.3 Source code for the updated Sprite class with new scaling abilities.

[code]
public class Sprite
{
private ContentManager p_content;
private SpriteBatch p_spriteBatch;
public Texture2D image;
public Vector2 position;
public Vector2 velocityLinear;
public Color color;
public float rotation;
public float velocityAngular;
public Vector2 scaleV;
public Sprite(ContentManager content, SpriteBatch spriteBatch)
{
p_content = content;
p_spriteBatch = spriteBatch;
image = null;
position = Vector2.Zero;
velocityLinear = Vector2.Zero;
color = Color.White;
rotation = 0.0f;
velocityAngular = 0.0f;
scaleV = new Vector2(1.0f);
}
public float scale
{
get { return scaleV.X; }
set {
scaleV.X = value;
scaleV.Y = value;
}
}
public bool Load(string assetName)
{
try
{
image = p_content.Load<Texture2D>(assetName);
}
catch (Exception) { return false; }
return true;
}
public void Draw()
{
Vector2 origin = new Vector2(image.Width / 2, image.Height / 2);
p_spriteBatch.Draw(image, position, null, color, rotation,
origin, scaleV, SpriteEffects.None, 0.0f);
}
public void Move()
{
position += velocityLinear;
}
public void Rotate()
{
rotation += velocityAngular;
if (rotation > Math.PI * 2)
rotation = 0.0f;
else if (rotation < 0.0f)
rotation = (float)Math.PI * 2;
}
}
[/code]

The complete Sprite Scaling Demo is found in Listing 8.4. A view of the program with the scale set very small is shown in Figure 8.8.

Scaling a sprite down to 10% of normal size.
FIGURE 8.8 Scaling a sprite down to 10% of normal size.

LISTING 8.4 Source code for the updated Sprite class with new scaling abilities.

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Viewport screen;
SpriteFont font;
Sprite ship;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
screen = GraphicsDevice.Viewport;
//create the font
font = Content.Load<SpriteFont>(“WascoSans”);
//create the ship sprite
ship = new Sprite(Content, spriteBatch);
ship.Load(“ship”);
ship.position = new Vector2(screen.Width / 2, screen.Height / 2);
//rotate the sprite to the right
ship.rotation = MathHelper.ToRadians(90.0f);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
//get state of touch inputs
TouchCollection touchInput = TouchPanel.GetState();
foreach (TouchLocation touch in touchInput)
{
if (touch.Position.X > screen.Width / 2)
ship.scale += 0.05f;
else if (touch.Position.X < screen.Width / 2)
ship.scale -= 0.05f;
}
//keep the scaling within limits
if (ship.scale < 0.10f)
ship.scale = 0.10f;
else if (ship.scale > 5.0f)
ship.scale = 5.0f;
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
ship.Rotate();
ship.Draw();
string text = “Scale: “ + ship.scale.ToString(“N4”);
Vector2 size = font.MeasureString(text);
float x = (screen.Width – size.X) / 2;
spriteBatch.DrawString(font, text, new Vector2(x, 440),
Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}
[/code]

That concludes our study of the three basic transforms: translation, rotation, and scaling. These techniques, along with SpriteBatch and some good math formulas, can produce any type of special effect or animated movement that we need for a game, on small game sprites, or even whole backgrounds (give that a try!).

Transforming Sprites

Translating (Moving) a Sprite

Sprite translation, or movement, can be done directly or indirectly. The direct approach involves writing code to move a sprite specifically to one location or another. The indirect approach involves setting up properties and variables that cause the sprite to move on its own based on those properties—we only write code that updates and draws the sprite, but the properties determine the movement. Although it is possible to cause a sprite to move in an arc or to follow a curve, that is not the type of movement we will be discussing in this hour. Instead, we’ll see how to move a sprite a short distance per frame (and remember, although an XNA game normally draws at 60 fps, a WP7 game runs at only 30). The “curves” our sprites will follow are actually complete circles, as you’ll see in the Solar System Demo coming up.

The math calculations we will perform in this hour to translate (move) a sprite are based on the 2D Cartesian coordinate system, represented in Figure 7.1. There are two axes, X and Y, which is why we refer to it as a “2D” system. Both the X and the Y axes start at the center, which is called the origin, residing at position (0,0). The X axis goes left (-) and right (+) from the origin. The Y axis goes down (-) and up (+) from the origin.

2D Cartesian coordinates are composed of two axes, X and Y.
FIGURE 7.1 2D Cartesian coordinates are composed of two axes, X and Y.

When making trigonometric calculations to transform a sprite’s position, the Y value must be reversed. If you look at the figure, you can see why! On a computer screen, as well as the WP7 screen, the origin (0,0) is located at the upper left, with X increasing right and Y increasing down. Since the Cartesian system represents Y positive going up, any Y coordinates we calculate must be reversed, or negated. This can be done by multiplying every Y result by -1.

Moving the position of a sprite needn’t be overly complicated. This is the easiest of the three transforms that we can calculate, because the calculation can just be skipped and the point can be moved to the desired target position (at this early stage). See Figure 7.2.

Translation (movement) from one point to another.
FIGURE 7.2 Translation (movement) from one point to another.

Using Velocity as Movement Over Time

Velocity can be calculated as distance over time. Mathematically, the formula is V = D / T. This calculation is more useful after a certain distance has been traveled in a certain amount of time, to figure out the average speed. But it is not as useful when it comes to moving sprites across the screen. For that purpose, we need to set a certain velocity ahead of time, while the distance and time variables are not as important. Let’s add a new velocity property to our ever-evolving Sprite class. Since velocity is a public property, we can use it outside of the class. Just for fun, let’s add a new method to the class to move the sprite based on velocity, as shown in Listing 7.1.

LISTING 7.1 Adding a new property to our Sprite class.

[code]
public class Sprite
{
private ContentManager p_content;
private SpriteBatch p_spriteBatch;
public Texture2D image;
public Vector2 position;
public Vector2 velocity;
public Color color;
public Sprite(ContentManager content, SpriteBatch spriteBatch)
{
p_content = content;
p_spriteBatch = spriteBatch;
image = null;
position = Vector2.Zero;
color = Color.White;
}
public bool Load(string assetName)
{
try
{
image = p_content.Load<Texture2D>(assetName);
}
catch (Exception) { return false; }
return true;
}
public void Draw()
{
p_spriteBatch.Draw(image, position, color);
}
public void Move()
{
position += velocity;
}
}
[/code]

The Move() method does something very simple—adds the velocity to the sprite’s position. The position will be rounded to the nearest X,Y pixel coordinate on the screen, but the velocity definitely will not be represented with whole numbers. Considering a game running at 60 fps, the velocity will be a fraction of a pixel, such as 0.01 (1/100th or 1% of a pixel). At this rate, assuming that the game is running at 60 fps, the sprite will move one pixel in 6/10ths of a second. This is equal to roughly two pixels per second, because 0.01 times 60 is 0.60. See Figure 7.3.

Sprite velocity in relation to frame rate.
FIGURE 7.3 Sprite velocity in relation to frame rate.

An XNA WP7 program runs at only 30 fps, so expect a difference in performance compared to Windows and Xbox 360 projects (which usually run at 60 fps).

We can put this simple addition to the Sprite class to the test. Included with this hour is an example called Sprite Movement. Open the project and take it for a spin. You’ll see that a funny-looking spaceship is moving over a background image, as shown in Figure 7.4, wrapping from right to left as it reaches the edge of the screen. Two asset files are required to run this project: craters800x480.tga and fatship256.tga, also included with the project. The source code is found in Listing 7.2.

Testing velocity with sprite motion.
FIGURE 7.4 Testing velocity with sprite motion.

LISTING 7.2 Test project for the new and improved Sprite class with movement capability.

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Sprite background;
Sprite ship;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
background = new Sprite(Content, spriteBatch);
background.Load(“craters800x480”);
ship = new Sprite(Content, spriteBatch);
ship.Load(“fatship256”);
ship.position = new Vector2(0, 240 – ship.image.Height / 2);
ship.velocity = new Vector2(8.0f, 4.0f);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
//move the sprite
ship.Move();
//rebound from top and bottom
if (ship.position.Y < -70 | ship.position.Y > 480 – 185)
{
ship.velocity.Y *= -1;
}
//wrap around right to left
if (ship.position.X > 800)
ship.position.X = -256;
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
//draw the background
background.Draw();
//draw the sprite
ship.Draw();
spriteBatch.End();
base.Draw(gameTime);
}
}
[/code]

Moving Sprites in a Circle

We’re going to use the sun and planet artwork introduced in the preceding hour to make a simple solar system simulation. The calculations will not be meaningful to an astronomy fan; we just want the planet sprites to rotate around the sun, with no relation to our actual solar system. For this project, we need several planet bitmaps with alpha channel transparency, but because the background will just be black, if the planets have a black background that will suffice.

The sun needs to be positioned at the center of the screen, with the planets moving around it in concentric circular orbits. To make a sprite move in a circle, we need to use basic trigonometry—sine and cosine functions.

To make the code in our Solar System Demo a little cleaner, I’ve created a quick subclass of Sprite called Planet that adds three properties: radius, angle, and velocity. Notice that the Planet constructor, Planet(), calls the Sprite constructor using the base() call, passing ContentManager and SpriteBatch as the two required parameters to Sprite. The syntax for inheritance from a base class is shown in the class definition that follows. This is very helpful as we can count on Sprite to initialize itself.

[code]
public class Planet : Sprite
{
public float radius;
public float angle;
public float velocity;
public Planet(ContentManager content, SpriteBatch spriteBatch)
: base(content, spriteBatch)
{
radius = 0.0f;
angle = 0.0f;
velocity = 0.0f;
}
}
[/code]

The rest of the source code for the Solar System Demo is up next. There are just four planets, but it is still efficient to use a List container to store them rather than using global variables. We’re slowly seeing a little more code added to our WP7 “programmer’s toolbox” with each new hour and each new example, which is a good thing. There’s a lot more code in the initialization part of ContentLoad() than in either Update() or Draw(), but that is to be expected in a program with so few sprites on the screen. Figure 7.5 shows the program running, and the source code is found in Listing 7.3.

Solar System demo.
FIGURE 7.5 Solar System demo.

LISTING 7.3 The Solar System Demo project code.

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Viewport screen;
Sprite sun;
List<Planet> planets;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
screen = GraphicsDevice.Viewport;
//create sun sprite
sun = new Sprite(Content, spriteBatch);
sun.Load(“sun”);
sun.position = new Vector2((screen.Width – sun.image.Width)/2,
(screen.Height – sun.image.Height)/2);
//create planet sprites
planets = new List<Planet>();
//add 1st planet
Planet planet = new Planet(Content, spriteBatch);
planet.Load(“planet1”);
planet.velocity = 0.2f;
planet.radius = 100;
planet.angle = MathHelper.ToRadians(30);
planets.Add(planet);
//add 2nd planet
planet = new Planet(Content, spriteBatch);
planet.Load(“planet2”);
planet.velocity = 0.16f;
planet.radius = 140;
planet.angle = MathHelper.ToRadians(60);
planets.Add(planet);
//add 3rd planet
planet = new Planet(Content, spriteBatch);
planet.Load(“planet3”);
planet.velocity = 0.06f;
planet.radius = 195;
planet.angle = MathHelper.ToRadians(90);
planets.Add(planet);
//add 4th planet
planet = new Planet(Content, spriteBatch);
planet.Load(“planet4”);
planet.velocity = 0.01f;
planet.radius = 260;
planet.angle = MathHelper.ToRadians(120);
planets.Add(planet);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
//update planet orbits
foreach (Planet planet in planets)
{
//update planet’s orbit position
planet.angle += planet.velocity;
//calculate position around sun with sine and cosine
float orbitx = (float)(Math.Cos(planet.angle) * planet.radius);
float orbity = (float)(Math.Sin(planet.angle) * planet.radius);
//center the planet so it orbits around the sun
float x = (screen.Width – planet.image.Width) / 2 + orbitx;
float y = (screen.Height – planet.image.Height) / 2 + orbity;
//save new position
planet.position = new Vector2(x, y);
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
//draw the sun
sun.Draw();
//draw the planets
foreach (Planet planet in planets)
{
planet.Draw();
}
spriteBatch.End();
base.Draw(gameTime);
}
}
[/code]

Did you find the code in LoadContent() a bit disconcerting, given that the same planet variable was used over and over again for each planet sprite? The important thing is that each one was added to the planets list, which became a “group” of sprites. You should do this whenever possible with related sprites, because it greatly simplifies everything about your code!

We can also use these sine() and cosine() functions to fire projectiles (such as a missile) from a launcher toward any target at any position on the screen.

The Sprite Movement Demo showed how a sprite can be moved automatically based on its velocity property, although we still have to step in and manage the event when the sprite reaches a screen edge. Although rotation is a subject of the next hour, we did explore moving a sprite around in a circle. This is different from sprite rotation, which involves actually changing the image. In our Solar System Demo, the sprite images were not rotated; they remained in a fixed rotation but followed a circular path around the sun using trigonometry.

Treating Bitmaps as Sprites

Bringing Bitmaps to Life

A sprite is a bitmap with benefits. XNA provides the SpriteBatch class to draw bitmaps with the SpriteBatch.Draw() method, of which there are several overloaded variants. But despite the name, SpriteBatch does not give us “sprite” capabilities from the gameplay perspective. SpriteBatch is a rendering class, used solely for drawing, not “managing” game entities traditionally known as “sprites.” The difference might seem a subtle one, but it’s actually quite a distinction. SpriteBatch might have been a somewhat incorrect name for the class. The “Batch” part of the name refers to the way in which “bitmaps” (not sprites) are drawn—in a batch. That is, all bitmap drawing via SpriteBatch.Draw() is put into a queue and then all the drawing is done quickly when SpriteBatch.End() is called. It’s faster to perform many draw calls at once in this manner, since the video card is going to be switching state only once. Every time SpriteBatch.Begin() and SpriteBatch.End() are called, that involves a state change (which is very slow in terms of rendering). The fewer state changes that happen, the better!

SpriteBatch.Draw() can handle animation, rotation, scaling, and translation (movement). But, without properties, we have to write custom code to do all of these things with just global variables. It can get tedious! We will see how tedious by writing an example using all global variables. Then, for comparison, we’ll write a simple Sprite class and convert the program. This is not just an illustration of how useful object-oriented programming can be (which is true) but to show why we need a Sprite class for gameplay.

SpriteBatch.Draw() works fine. We don’t need an alternative replacement because it can do anything we need. But the code to draw sprites is very specific. If we don’t want to manually draw every character, or vehicle, or avatar in the game, we have to automate the process in some manner. The key to making this work is via properties. A property is a trait or an attribute that partially describes something. In the case of a person, one property would be gender (male or female); other properties include race, height, weight, and age. No single property fully describes a person, but when all (or most) of the properties are considered, it gives you a pretty good idea of what that person looks like.

The simplest and most common property for a sprite is its position on the screen. In the previous hour, we used a variable called Vector2 shipPos to represent the position of the bitmap, and a variable called Texture2D shipImage to represent the image. These two variables were properties for a game object—a spaceship to be used in a sci-fi game. Wouldn’t it be easier to manage both of these properties inside a Sprite class? Before we do that, let’s see whether it’s really that big of a deal to keep track of properties with global variables.

Drawing Lots of Bitmaps

Let’s create a short example. Now, working with just one bitmap is a piece of cake, because there’s only one call to SpriteBatch.Draw(), only one position variable, and only one image variable. When things get messy is when about five or more bitmaps need to be manipulated and drawn. Up to that point, managing variables for four or so images isn’t so bad, but as the number climbs, the amount of manual code grows and becomes unwieldy (like a giant two-handed sword).

[code]
Vector2 position1, position2, position3, position4, position5;
Texture2D image1, image2, image3, image4, image5;
[/code]

It’s not just declaring and using the variables that can be a problem. It’s the ability to use any more than this practically in a game’s sources. But let’s give it a try anyway for the sake of the argument. Figure 6.1 shows the output for this short program, which is a prototype for a solar system simulation we’ll be creating in this hour. The source code is found in Listing 6.1.

The Many Bitmaps Demo program.
FIGURE 6.1 The Many Bitmaps Demo program.

LISTING 6.1 Source code for the Many Bitmaps Demo program, a precursor to a larger project.

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Vector2 position1, position2, position3, position4, position5;
Texture2D image1, image2, image3, image4, image5;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
image1 = Content.Load<Texture2D>(“sun”);
image2 = Content.Load<Texture2D>(“planet1”);
image3 = Content.Load<Texture2D>(“planet3”);
image4 = Content.Load<Texture2D>(“planet2”);
image5 = Content.Load<Texture2D>(“planet4”);
position1 = new Vector2(100, 240-64);
position2 = new Vector2(300, 240-32);
position3 = new Vector2(400, 240-32);
position4 = new Vector2(500, 240-16);
position5 = new Vector2(600, 240-16);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
spriteBatch.Draw(image1, position1, Color.White);
spriteBatch.Draw(image2, position2, Color.White);
spriteBatch.Draw(image3, position3, Color.White);
spriteBatch.Draw(image4, position4, Color.White);
spriteBatch.Draw(image5, position5, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}
[/code]

Running into Limits with Global Variables

The Many Bitmaps Demo program wasn’t too difficult to deal with, was it? I mean, there were only five bitmaps to draw, so we needed five position variables. But what if there were 20, or 50, or 100? With so many game objects, it would be impossible to manage them all with global variables. Furthermore, that’s bad programming style when there are better ways to do it. Obviously, I’m talking about arrays and collections. But not only is it a quantity issue with regard to the global variables, but if we want to add another property to each object, we’re talking about adding another 20, 50, or 100 variables for that new property!

Let’s rewrite the program using an array. Later in the hour, we’ll work with a list, which is a container class, but for this next step, an array is a little easier to follow. Here is a new version using arrays. The new source code is found in Listing 6.2.

LISTING 6.2 New source code for the program rewritten to more efficiently store the planet bitmaps and vectors in arrays.

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D[] images;
Vector2[] positions;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
images = new Texture2D[5];
images[0] = Content.Load<Texture2D>(“sun”);
images[1] = Content.Load<Texture2D>(“planet1”);
images[2] = Content.Load<Texture2D>(“planet3”);
images[3] = Content.Load<Texture2D>(“planet2”);
images[4] = Content.Load<Texture2D>(“planet4”);
positions = new Vector2[5];
positions[0] = new Vector2(100, 240-64);
positions[1] = new Vector2(300, 240-32);
positions[2] = new Vector2(400, 240-32);
positions[3] = new Vector2(500, 240-16);
positions[4] = new Vector2(600, 240-16);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
for (int n = 0; n < 5; n++)
{
spriteBatch.Draw(images[n], positions[n], Color.White);
}
spriteBatch.End();
base.Draw(gameTime);
}
}
[/code]

As you examine the code in this version of the program, what stands out? I notice that in ContentLoad(), the initialization code is actually a bit more complicated than it was previously, but the code in Draw() is shorter. We’re not counting lines of code, but in Draw(), the for loop could accommodate 100 or 1,000 objects with the same amount of code. This is the most significant difference between this and the previous program—we now can handle any arbitrary number of objects.

We could shorten the code in LoadContent() even further by building the filename for each planet. The key is to make the asset names consistent. So, if we rename ”sun” to ”planet0”, then loading the assets becomes a simple for loop. Here is an improvement:

[code]
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
images = new Texture2D[5];
positions = new Vector2[5];
for (int n = 0; n < 5; n++)
{
string filename = “planet” + n.ToString();
images[n] = Content.Load<Texture2D>(filename);
}
positions[0] = new Vector2(100, 240 – 64);
positions[1] = new Vector2(300, 240 – 32);
positions[2] = new Vector2(400, 240 – 32);
positions[3] = new Vector2(500, 240 – 16);
positions[4] = new Vector2(600, 240 – 16);
}
[/code]

The positions array must still be set manually due to the differences in the sizes of the planet images. But I think we could automate that as well by looking at the width and height of each image. The point is not just to make the code shorter, but to find ways to improve the code, make it more versatile, more reusable, and easier to modify. Using a consistent naming convention for asset files will go a long way toward that end.

Creating a Simple Sprite Class

Now let’s experiment with some code that actually does something interesting besides drawing fixed images. We’ll start by creating a simple class to encapsulate a sprite, and then add some features to make the Sprite class useful. This will be a hands-on section where we build the Sprite class in stages. If you are an experienced programmer, you may skip this section.

Creating the Sprite Class

Let’s begin with a new project. The project type will be, as usual, Windows Phone (4.0), and the name is Sprite Demo. In the Game1.cs file, which is the main source code file for the game, we’re going to add the new Sprite class to the top of the file, above the Game1 class. After a while, the Sprite class can be moved into its own file called Sprite.cs. I find this more convenient while working on a new class. See Listing 6.3 for the complete source code.

Classes do not need to be stored in unique source code files; that’s a practice to keep a large project tidy and easier to maintain. But it is acceptable (and often practical) to define a new class inside an existing file. This is especially true when several classes are closely related and you want to better organize the project. The best practice, though, for large classes, is to define each class in its own file.

LISTING 6.3 Source code for the new project with included Sprite class.

[code]
public class Sprite
{
public Texture2D image;
public Vector2 position;
public Color color;
}
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Viewport viewport;
Sprite sun;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
}protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
//get screen dimensions
viewport = GraphicsDevice.Viewport;
//create sun sprite
sun = new Sprite();
sun.image = Content.Load<Texture2D>(“sun”);
//center sun sprite on screen
float x = (viewport.Width – sun.image.Width) / 2;
float y = (viewport.Height – sun.image.Height) / 2;
sun.position = new Vector2(x,y);
//set color
sun.color = Color.White;
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
//draw the sun
spriteBatch.Draw(sun.image, sun.position, sun.color);
spriteBatch.End();
base.Draw(gameTime);
}
}
[/code]

The code listings in this book omit the using statements at the beginning of every XNA project, as well as the namespace line and surrounding brackets, to focus on just the functional part of a program, only when that code is generated by Visual Studio. You should assume that those lines are required to compile every example listed herein.

Scope and Clarity

This is how most classes begin life, with just some public properties that could have been equally well defined in a struct. In fact, if you just want to create a quick container for a few variables and don’t want to deal with a class, go ahead and do it. Structures (defined as struct) can even have constructor methods to initialize their properties, as well as normal methods. But in a struct, there is no scope; everything is public. In contrast, everything in a class is private by default. This would work, for example:

[code]
struct Sprite
{
Texture2D image;
Vector2 position;
Color color;
}
[/code]

But one limitation with a struct is room for growth; with a class, we can make changes to scope (which means changing whether individual properties and methods are visible outside of the class). At this simplistic level, there’s very little difference between a class and a struct. Some programmers differentiate between them by using structs only as property containers, with no methods, reserving methods only for defined classes. It’s ultimately up to you!

An OOP (object-oriented programming) “purist” would demand that our image, position, and color properties be defined with private scope, and accessed via property methods. The difference between property variables and property methods is fuzzy in C#, whereas they are very clear-cut in a more highly precise language such as C++. Let’s see what the class will look like when the three variables (image, position, and color) are converted into private properties with public accessor methods.

[code]
public class Sprite2 //”good” OOP version
{
Texture2D p_image;
Vector2 p_position;
Color p_color;
public Texture2D image
{
get { return p_image; }
set { p_image = value; }
}
public Vector2 position
{
get { return p_position; }
set { p_position = value; }
}
public Color color
{
get { return p_color; }
set { p_color = value; }
}
}
[/code]

In general, I prefer to not hide property variables (such as public Texture2D image in the Sprite class), because it just requires extra code to access the property later. This is, again, a matter of preference, and might be dependent on the coding standards of your team or employer. If it’s up to you, just focus on writing clean, tight code, and don’t worry about making your code “OOP safe” for others.

Initializing the Sprite Class with a Constructor

Compared to the original Sprite class defined in Game1.cs, what do you think of the “safe” version (renamed to Sprite2 to avoid confusion)? The three variable names have had “p_” added (to reflect that they are now private in scope), and now in their place are three “properly defined” properties. Each property now has an accessor method (get) and a mutator method (set). If you prefer this more highly structured form of object-oriented C#, I encourage you to continue doing what works best for you. But for the sake of clarity, I will use the original version of Sprite with the simpler public access property variables.

Let’s give the Sprite class more capabilities. Currently, it’s just a container for three variables. A constructor is a method that runs automatically when an object is created at runtime with the new operator, for example:

[code]
Sprite sun = new Sprite();
[/code]

The term Sprite(), with the parentheses, denotes a method—the default method since it requires no parameters. Here is ours:

[code]
public class Sprite
{
public Texture2D image;
public Vector2 position;
public Color color;
public Sprite()
{
image = null;
position = Vector2.Zero;
color = Color.White;
}
}
[/code]

Here, we have a new constructor that initializes the class’s properties to some initial values. This is meant to avoid problems later if one forgets to initialize them manually. In our program now, since color is automatically set to Color.White, we no longer need to manually set it, which cleans up the code in LoadContent() a bit.

[code]
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
//get screen dimensions
viewport = GraphicsDevice.Viewport;
//create sun sprite
sun = new Sprite();
sun.image = Content.Load<Texture2D>(“sun”);
//center sun sprite on screen
float x = (viewport.Width – sun.image.Width) / 2;
float y = (viewport.Height – sun.image.Height) / 2;
sun.position = new Vector2(x,y);
}
[/code]

Writing Reusable Code with Abstraction

A usual goal for an important base game class like Sprite is to abstract the XNA code, at least somewhat, to make the class stand on its own as much as possible. This becomes a priority when you find yourself writing games on several platforms. Within the XNA family, we have Windows, Xbox 360, and Windows Phone. But on a larger scale, it’s fairly common to port games to other systems. After you have rewritten your Sprite class a few times for different platforms (and even languages, believe it or not!), you begin to see similarities among the different systems, and begin to take those similarities into account when writing game classes.

There are two aspects that I want to abstract in the Sprite class. First, there’s loading the image. This occurs in LoadContent() when we simply expose the image property to Content.Load(). Second, there’s drawing the sprite. This occurs in Draw(), also when we expose the image property. To properly abstract the class away from XNA, we need our own Load() and Draw() methods within Sprite itself. To do this, the Sprite class must have access to both ContentManager and SpriteBatch. We can do this by passing those necessary runtime objects to the Sprite class constructor. Listing 6.4 contains the new source code for the Sprite class.

LISTING 6.4 Source code for the expanded Sprite class.

[code]
public class Sprite
{
private ContentManager p_content;
private SpriteBatch p_spriteBatch;
public Texture2D image;
public Vector2 position;
public Color color;
public Sprite(ContentManager content, SpriteBatch spriteBatch)
{
p_content = content;
p_spriteBatch = spriteBatch;
image = null;
position = Vector2.Zero;
color = Color.White;
}
public bool Load(string assetName)
{
try
{
image = p_content.Load<Texture2D>(assetName);
}
catch (Exception) { return false; }
return true;
}
public void Draw()
{
p_spriteBatch.Draw(image, position, color);
}
}
[/code]

Putting our new changes into action (in Listing 6.5) reveals some very clean-looking code in LoadContent() and Draw(), with the output shown in Figure 6.2.

LISTING 6.5 Modifications to the project to support the new Sprite features.

[code]
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
viewport = GraphicsDevice.Viewport;
//create sun sprite
sun = new Sprite(Content, spriteBatch);
sun.Load(“sun”);
//center sun sprite on screen
float x = (viewport.Width – sun.image.Width) / 2;
float y = (viewport.Height – sun.image.Height) / 2;
sun.position = new Vector2(x, y);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
sun.Draw();
spriteBatch.End();
base.Draw(gameTime);
}
[/code]

Demonstrating the Sprite class.
FIGURE 6.2 Demonstrating the Sprite class.

Error Handling

The Sprite.Load() method has error handling built in via a try…catch block. Inside the try block is a call to Content.Load(). If the passed asset name is not found, XNA generates an exception error, as shown in Figure 6.3. We don’t want the end user to ever see an exception error, and in a very large game project, it is fairly common for asset files to be renamed and generate errors like this—it’s all part of the development process to track down and fix such common bugs.

To assist, the code in Sprite.Load() returns false if an asset is not found—rather than crashing with an exception error. The problem is, we can’t exit on an error condition from within LoadContent(); XNA is just not in a state that will allow the program to terminate at that point. What we need to do is set a flag and look for it after LoadContent() is finished running.

I have an idea. What if we add a feature to display an optional error message in a pre-shutdown process in the game loop? All it needs to do is check for this error state and then print whatever is in the global errorMessage variable. The user would then read the message and manually shut down the program by closing the window. Let’s just try it out; this won’t be a permanent fixture in future chapters, but you may continue to use it if you want to. First, we need some new variables.

 An exception error occurs when an asset cannot be found.
FIGURE 6.3 An exception error occurs when an asset cannot be found.

[code]
//experimental error handling variables
bool errorState;
string errorMessage;
SpriteFont ErrorFont;
Next, we initialize them.
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
errorMessage = ““;
errorState = false;
}
[/code]

And in LoadContent(), we need to trap the exception error (note that ”sun” was temporarily renamed to ”sun1” to demonstrate the exception error).

[code]
//create sun sprite
sun = new Sprite(Content, spriteBatch);
if (!sun.Load(“sun1”))
{
errorState = true;
errorMessage = “Asset file ‘sun’ not found.”;
return;
}
[/code]

Draw() is where the error handling process comes into play.

[code]
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
//experimental error handler
if (errorState)
{
spriteBatch.DrawString(ErrorFont, “CRITICAL ERROR”,
Vector2.Zero, Color.Red);
spriteBatch.DrawString(ErrorFont, errorMessage,
new Vector2(0,100), Color.Red);
}
else
{
sun.Draw();
}
spriteBatch.End();
base.Draw(gameTime);
}
[/code]

When run again with the new error handling code in place, the previous exception error now becomes a nice in-game notification, as shown in Figure 6.4.

The exception error has been handled nicely.
FIGURE 6.4 The exception error has been handled nicely.

We’ve just scratched the surface of what will be possible with the new Sprite class in this hour. Over the next several chapters, the class will be enhanced significantly, making it possible—with properties and methods—to perform transformations

Drawing Bitmaps

A bitmap is a 2D image, also known as a texture in the realm of 3D programming. Bitmap images can be created in memory, but they are more often loaded from an existing bitmap file. The most common file types are BMP, PNG, and JPG. For games, we want high-quality bitmaps without lossy compression (in which pixels are changed to shrink the file size). The BMP format does work well but it has fallen out of favor in recent years due to format incompatibilities (such as some BMP files supporting an alpha channel, and some not). A good compromise is the PNG format, which offers decent file size and 32-bit ARGB color. For the sake of discussion, images will just be referred to as “bitmaps” regardless of the file format. In this hour, we’ll learn to add bitmap files to the Content project, and then load and draw them. This is the basis for most games!

Adding a Bitmap File to an XNA Project

Bitmap programming is the precursor to sprite programming, which is where we really want to go in order to draw game characters, ships, or whatever the game calls for. We can’t draw a 3D shape with just a bitmap, although bitmaps are used as textures when rendering in 3D. A texture is an image wrapped around the 3D shape, such as a tree, a car, or a human figure. Texturing can get pretty complex! Fortunately, we’ll start with the basics of 3D in a later hour and learn how to draw with lights and special effects. But first we need to learn 2D or bitmap programming.

There’s so much we can do with bitmaps in XNA! I’m eager to jump right into the advanced rendering stuff, but we’ll start off at a steady pace and first learn the basics. In the next hour, we’ll do some really fascinating things with bitmaps, like scaling and rotation.

We need to create a new project for our explorations of bitmap programming. Let’s begin with a new project.

Adding a Bitmap Asset to an XNA Project

Follow these steps to create the Bitmap Demo project that we will use as an example while learning how to load and draw bitmaps in this hour:

  1. Create a new Windows Phone (4.0) project called Bitmap Demo.
  2. Create or find a bitmap file containing an image you want to draw. The example uses an image of a spaceship that you can borrow. The bitmap file is a 32-bit PNG with a transparent region (via alpha channel).
  3. Right-click the Content project in Solution Explorer (it is called Bitmap Demo- Content (Content)), as shown in Figure 5.1. From the context menu that comes up, choose Add, Existing Item.

    Adding an existing bitmap file to the project.
    FIGURE 5.1 Adding an existing bitmap file to the project.
  4. The Add Existing Item dialog should come up. Select the bitmap file you want to add to the project. The file is added to the project as shown in Figure 5.2.

    The ship.png file has been added.
    FIGURE 5.2 The ship.png file has been added.
  5. If the Properties window is not visible, open it with View, Properties Window. When you select an asset file in the Content project, the file’s properties will be shown. Note the properties in the case of ship.png. Both the Content Importer and the Content Processor properties are automatically set to Texture – XNA Framework. This means that XNA recognized the file type and will process it as a texture. If you look at the other items in the drop-down list, you should find the following:
    • Effect – XNA Framework
    • Autodesk FBX – XNA Framework
    • Sprite Font Description – XNA Framework
    • WAV Audio File – XNA Framework
    • WMA Audio File – XNA Framework
    • WMV Video File – XNA Framework
    • XACT Project – XNA Framework
    • X File – XNA Framework
    • XML Content – XNA Framework

    These are the types of asset files XNA recognizes automatically. You can also create your own custom content importer to convert data files of any type to work with XNA—although you still have to write the code to read the file.

Loading a Bitmap File as an Asset

The name of the bitmap file added to the project is important, but the extension is not. The ship.png file added in the preceding Try It Yourself tutorial will be converted into a file called ship.xnb when compiled, and will be recognized by the name “ship” in the content manager. Let’s see how to load the bitmap.

Writing Code to Load a Bitmap File

Follow these steps to write the code to load a bitmap file:

  1. Add two new variables near the top of the Game1 class definition (where other global variables are found) as shown. The first variable is of type Texture2D, which is a class for bitmaps. The second is a Vector2 to keep track of the position of the bitmap on the screen.
    [code]
    public class Game1 : Microsoft.Xna.Framework.Game
    {
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    //texture variable
    Texture2D shipImage;
    //position variable
    Vector2 shipPos;
    [/code]
  2. Load the “ship” bitmap in the LoadContent() method and set the position to (0,0).
    [code]
    protected override void LoadContent()
    {
    spriteBatch = new SpriteBatch(GraphicsDevice);
    //load the ship bitmap
    shipImage = Content.Load<Texture2D>(“ship”);
    shipPos = Vector2.Zero;
    }
    [/code]

If XNA cannot find an asset file that your code tries to load with Content.Load() in the program’s main LoadContent() method, an exception error will be generated!

Drawing a Bitmap with SpriteBatch

After adding the bitmap file as an asset item to the Content project, adding the variables, and loading the bitmap as a Texture2D, we can now draw the bitmap onto the screen. For that, we need to jump down to the Draw() method. Here is the easiest way to draw a bitmap:

[code]
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
//begin drawing
spriteBatch.Begin();
//draw the bitmap
spriteBatch.Draw(shipImage, shipPos, Color.White);
//finish drawing
spriteBatch.End();
base.Draw(gameTime);
}
[/code]

The output from this program is shown in Figure 5.3.

SpriteBatch is an XNA core class that can draw both bitmaps and text, with many variations of the Draw() and DrawString() methods. In the next hour, we will work with SpriteBatch.Draw() extensively, exploring all of its capabilities. Let’s look at all the overloaded variations of Draw() here to get a feel for these capabilities. We will study these in more detail in the next hour.

The first parameter is always a Texture2D. The second parameter is always the destination— either a Rectangle or a Vector2. Beyond those two are a variety of parameters that make it possible to perform the following actions:

  • Rotate
  • Scale
  • Animate
  • Flip horizontally
  • Flip vertically
The Bitmap Demo program.
FIGURE 5.3 The Bitmap Demo program.

We will learn to use these special effects in upcoming chapters, as well as take a peek under the hood of the SpriteBatch.Begin() and SpriteBatch.End() methods that have been so mysterious up to this point. We have used only the second version, although the first one is easy enough to follow as well. Overloads 3 to 7 will be examined in the next hour and others following in depth, so think of this as just a sneak peek! Some of this code might be hard to read, because these are the definitions of the methods. I’m only going into this much detail now for Draw() because we will use it a lot!

[code]
public void Draw(Texture2D texture, Rectangle destinationRectangle,
Color color);
public void Draw(Texture2D texture, Vector2 position, Color color);
public void Draw(Texture2D texture, Rectangle destinationRectangle,
Rectangle? sourceRectangle, Color color);
public void Draw(Texture2D texture, Vector2 position,
Rectangle? sourceRectangle, Color color);
public void Draw(Texture2D texture, Rectangle destinationRectangle,
Rectangle? sourceRectangle, Color color, float rotation,
Vector2 origin, SpriteEffects effects, float layerDepth);
public void Draw(Texture2D texture, Vector2 position,
Rectangle? sourceRectangle, Color color, float rotation,
Vector2 origin, float scale, SpriteEffects effects, float layerDepth);
public void Draw(Texture2D texture, Vector2 position,
Rectangle? sourceRectangle, Color color, float rotation,
Vector2 origin, Vector2 scale, SpriteEffects effects,
float layerDepth);
[/code]

Drawing Bitmaps with Transparency

The ship.png file used in the example in this hour has an alpha channel and draws without showing the background pixels around the shape of the actual ship inside the bitmap. Let’s learn how to create an alpha channel. I’ll use Gimp because it’s a free graphics editor and it has many features similar to the uber-powerful Adobe Photoshop (download Gimp from www.gimp.org).

XNA will draw an image transparently if it has an alpha channel, and we don’t need to do anything extra for this to happen. This really makes life easier for an XNA game programmer. If you supply XNA with a bitmap file containing transparent pixels, it will draw that image with transparency.

I have used many graphics editors, including Photoshop, PaintShop Pro, Corel- DRAW, and Gimp. Although each behaves somewhat differently, they all share a similar toolset, including the ability to work with alpha channels. The instructions here for Gimp will be basically similar for other graphics editors.

Creating an Alpha Channel

Let’s take a look at how to create an alpha channel. To add a transparency layer to an image, you need to locate the Magic Wand tool available in most graphics editors.

  1. After selecting the Magic Wand tool, click somewhere in the background (which should be a solid color). The Magic Wand locates the edges of the game object and highlights everything around it, as shown in Figure 5.4. Another, more precise way to select a background is with the Color Picker or the Select by Color tool.
  2. Now that you have a selection available, you can create a layer mask. In Gimp, open the Layer menu and choose Mask, Add Layer Mask, as shown in Figure 5.5.
  3. The Add Layer Mask dialog shown in Figure 5.6 comes up. Choose the Selection option and check the Invert Mask option; then click the Add button.
    Finding the edges of the game object with the Magic Wand tool.
    FIGURE 5.4 Finding the edges of the game object with the Magic Wand tool.
    Preparing to add a layer mask.
    FIGURE 5.5 Preparing to add a layer mask.

    The Add Layer Mask dialog is used to choose options for a layer mask.
    FIGURE 5.6 The Add Layer Mask dialog is used to choose options for a layer mask.
  4. The next step is to apply the layer mask. You can tell Gimp to apply the mask using the Layer menu, then Mask, Apply Layer Mask, as shown in Figure 5.7.

    Applying the new layer mask makes it permanent.
    FIGURE 5.7 Applying the new layer mask makes it permanent.
  5. In the final figure, shown in Figure 5.8, the alpha channel has been created based on the masked selection. The checkerboard background behind the asteroid image shows the transparent region. The result looks very nice! You can load this image into your XNA project, and it will draw with transparency so that the outer edges of the image (where the solid background is located) will not overwrite the background of the screen. Just be sure to save the file using a bitmap file format that supports transparency and works with XNA. The most common formats are PNG and TGA.

    The asteroid image now has a masked transparency layer.
    FIGURE 5.8 The asteroid image now has a masked transparency layer.

This hour began a journey that will last for essentially the rest of the book. Most of our attention will be on 2D graphics in upcoming chapters, with all the variations of the SpriteBatch.Draw() method and the special effects that are possible with it. Being able to add a bitmap file to the project (with transparency information), and then load and draw it, is essential for every XNA programmer.

Getting User Input

Exploring Windows Phone Touchscreen Input

Programming a game’s input system really does require a lot of design consideration ahead of time because all we really can use is the touchscreen! Oh, there is an accelerometer that can be used for input, but it is a rare and often niche game that uses the accelerometer to read the phone’s orientation (the angle and position at which it is being held). The touchscreen, for all practical purposes, is treated like mouse input without a visible mouse cursor. Windows programmers will have a slightly harder time adjusting than someone who has been working with the Xbox 360 or another console, which already requires an adjustment in one’s assumptions about user input. Windows games are a cakewalk, with 100-plus keyboard keys, the mouse, and an optional controller! That’s a lot of input! On the Windows Phone, though, all we have is the touchscreen. So we need to make the most of it, being mindful that a user’s finger is rather large compared to the precise input of a mouse cursor.

For the purpose of detecting input for a game, the most obvious method might be to just detect the touch location and convert that to an average coordinate—like mouse input. But the Windows Phone touchscreen is capable of multitouch, not just single-touch input. Although the screen is very small compared to a tablet or PC screen, it is still capable of detecting input from up to four fingers at once. To develop a multitouch game, you will need the actual Windows Phone hardware— either a real phone or an unlocked development model (with no phone service).

Multitouch is a significant feature for the new phone! For our purposes here, however, we will just be concerned with single “tap” input from one finger, simulated with mouse motion in the emulator. We can do a single tap with a mouse click, or a drag operation by touching the screen and moving the finger across the screen. Again, this will have to be done with your mouse for development on the emulator (unless your Windows system uses a touchscreen!).

You will not be able to test multitouch in the emulator without a multitouch screen on your Windows development system or an actual Windows Phone device.

Simulating Touch Input

The key to touch input with a WP7 device with XNA is a class called TouchPanel. The XNA services for mouse, keyboard, and Xbox 360 controller input are not available in a WP7 project. So, for most XNA programmers, TouchPanel will be a new experience. Not to worry; it’s similar to the mouse code if we don’t tap into the multitouch capabilities.

The TouchPanel class includes one very interesting property that we can parse— MaximumTouchCount represents the number of touch inputs that the device can handle at a time.

The second class that we need to use for touch input is called TouchCollection. As the name implies, this is a collection that will be filled when we parse the TouchPanel while the game is running. TouchCollection has a State property that is similar to MouseState, with the enumerated values Pressed, Moved, and Released.

The Touch Demo Project, Step by Step

Let’s create a sample project to try out some of this code. I’ll skip the usual new project instructions at this point since the steps should be familiar by now. Just create a new project and add a font so that we can print something on the screen. I’ve used Moire Bold 24 as the font in this example.

  1. Add some needed variables for working with text output. (The code for the touchscreen input system will be added shortly.)
    [code]
    public class Game1 : Microsoft.Xna.Framework.Game
    {
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    SpriteFont MoireBold24;
    Vector2 position;
    Vector2 size;
    string text = “Touch Screen Demo”;
    [/code]
  2. Initialize the font and variables used in the program.
    [code]
    protected override void LoadContent()
    {
    spriteBatch = new SpriteBatch(GraphicsDevice);
    MoireBold24 = Content.Load<SpriteFont>(“MoireBold24”);
    size = MoireBold24.MeasureString(text);
    Viewport screen = GraphicsDevice.Viewport;
    position = new Vector2((screen.Width – size.X) / 2,
    (screen.Height – size.Y) / 2);
    }
    [/code]
  3. Write the update code to get touch input.
    [code]
    protected override void Update(GameTime gameTime)
    {
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
    ButtonState.Pressed)
    this.Exit();
    //get state of touch inputs
    TouchCollection touchInput = TouchPanel.GetState();
    //look at all touch points (usually 1)
    foreach(TouchLocation touch in touchInput)
    {
    position = new Vector2(touch.Position.X – size.X / 2,
    touch.Position.Y – size.Y / 2);
    }
    base.Update(gameTime);
    }
    [/code]
  4. Print the message on the screen at the touch coordinates.
    [code]
    protected override void Draw(GameTime gameTime)
    {
    GraphicsDevice.Clear(Color.CornflowerBlue);
    spriteBatch.Begin();
    spriteBatch.DrawString(MoireBold24, text, position, Color.White);
    spriteBatch.End();
    base.Draw(gameTime);
    }
    [/code]

Figure 4.1 shows the output of the program running in the WP7 emulator. Although the static figure doesn’t show movement, the message “Touch Screen Demo” moves on the screen with touch input! On a real Windows Phone device, this would work with finger input on the screen.

The Windows Phone hardware specification calls for at least four simultaneous touchscreen inputs!

The text message moves based on touch input.
FIGURE 4.1 The text message moves based on touch input.

How to Simulate More Inputs

What if you really do have a great design for a multitouch game but have no way to test it (that is, you do not have a WP7 device or a touchscreen for your Windows PC)? One alternative is to develop and test your game with simulated inputs for each finger, and then test each input separately in the emulator. This actually works quite well! Suppose your game requires one input to move a ship left or right on the screen, and another input to fire a weapon. The easy approach is to just leave the ship’s movement alone (presumably at the center of the screen without movement) to test the firing input. When that is working satisfactorily, then you might test leftto- right movement input with random or regular shooting intervals. This is how most developers will approach the problem.

Using Gestures on the Touchscreen

A related capability of the TouchPanel class is a gesture interface. This is potentially a really great feature for WP7 games, so don’t pass it up! A gesture is essentially nonverbal communication with your hands. A gesture on a touchscreen might be to flick an object across the screen rather than dragging to the exact location where you want it to go. Instead of manually moving something one direction or another, one might just flick it in the general direction. So, it’s up to the game to interpret the gestures based on the game’s user interface design.

Since gesture input can be used in place of touch input, you might want to just use gesture input instead. If all you need for your game is single-touch input, a tap gesture would work in place of the mouselike touch code seen previously. The main difference between the two methods is that gesture input does not support multitouch.

XNA supports gestures with the same TouchPanel class used for touch input. Here are the contents of the GestureType enumeration:

  • None
  • FreeDrag
  • Tap
  • Pinch
  • DoubleTap
  • Flick
  • Hold
  • DragComplete
  • HorizontalDrag
  • PinchComplete
  • VerticalDrag

Gesture-based input does not support multitouch. Only a single gesture (with one finger) can be used at a time.

To use gesture input, we have to enable it, because gesture input is not automatic. There are obvious problems with input if all gestures are enabled by default, because the game will behave erratically with flick gestures in a game using a lot of “drag”-style input to move and interact with the game. Even the simplest of arcadestyle or puzzle games will involve some dragging of the finger across the screen for input, and this could be misinterpreted as a flick if the intended movement is too fast. To enable both tap and flick gestures, add this line to the constructor, Game1(), or LoadContent():

[code]
TouchPanel.EnabledGestures = GestureType.Tap | GestureType.Flick;
[/code]

A simple example can be added to the Update() method of the Touch Demo to see how it works. Note that the first if condition is required. Trying to read a gesture when none is available will cause an exception!

[code]
if (TouchPanel.IsGestureAvailable)
{
GestureSample gesture = TouchPanel.ReadGesture();
if (gesture.GestureType == GestureType.Tap)
{
position = new Vector2(gesture.Position.X – size.X / 2,
gesture.Position.Y – size.Y / 2);
}
}
[/code]

Running this code, you might be surprised to find that the tap gesture is more like a mouse click-and-release event. Click-dragging does not produce the gesture! You can see for yourself by commenting out the touch input code in the Touch Demo program, and running just the gesture code instead. If you’re really interested in gesture input, a real WP7 device is a must-have! The emulator just doesn’t satisfy. If you are doing WP7 development for profit, a dev device or an actual Windows Phone device (with carrier subscription) is a good investment.

Touch input can be quite satisfying to a game designer after working with the traditional keyboard and mouse, because it offers the potential for very creative forms of input in a game. Although regular multitouch input will be the norm for most Windows Phone games, the ability to use gestures also might prove to be fun.