Drawing with Z-Index Ordering

Prioritized Drawing

We already have the ability to perform prioritized “z-index” drawing of a sprite image with the SpriteBatch.Draw() method, and we have been using the z-index parameter all along, just set to a value of zero. This effectively gave every sprite the same priority. When that is the case, priority will be based entirely on the order at which sprites are drawn (in gameplay code). SpriteBatch.Draw() has the capability to automatically prioritize the drawing of some sprites over the top of other sprites using this z-index buffer.

What does “z-index” mean, you may be wondering? When doing 2D sprite programming, we deal only with the X and Y coordinates on the screen. The Z coordinate, then, is the position of the sprite in relation to other sprites that overlap each other. The range for the z-index goes from 0.0 to 1.0. So, a sprite with a z-index priority of 0.6 will draw over a sprite with a z-index priority of 0.3. Note that 1.0 is the highest, and 0.0 is the lowest.

Sprite Class Changes

Adding Z-Buffering to the Sprite Class

A very minor change is required to enable z-index drawing in our Sprite class. We’ll go over those changes here.

  1. Add a zindex variable to the class:
    [code]
    public float zindex;
    [/code]
  2. In the Sprite class constructor, initialize the new variable:
    [code]
    zindex = 0.0f;
    [/code]
  3. Replace the hard-coded zero with the zindex variable in the SpriteBatch.Draw() calls inside Sprite.Draw() (there are two of them):
    [code]
    public void Draw()
    {
    if (!visible) return;
    if (totalFrames > 1)
    {
    Rectangle source = new Rectangle();
    source.X = (frame % columns) * (int)size.X;
    source.Y = (frame / columns) * (int)size.Y;
    source.Width = (int)size.X;
    source.Height = (int)size.Y;
    p_spriteBatch.Draw(image, position, source, color,
    rotation, origin, scale, SpriteEffects.None, zindex);
    }
    else
    {
    p_spriteBatch.Draw(image, position, null, color, rotation,
    origin, scaleV, SpriteEffects.None, zindex);
    }
    }
    [/code]

Adding Rendering Support for Z-Buffering

To use a z-buffer for prioritized drawing, a change must be made to the call to SpriteBatch.Begin(), which gets drawing started in the main program code. No change is made to SpriteBatch.End(). There are actually five overloads of SpriteBatch.Begin()! The version we want requires just two parameters: SpriteSortMode and BlendState. Table 16.1 shows the SpriteSortMode enumeration values.

SpriteSortMode Enumeration

The default sorting mode is Deferred, which is what SpriteBatch uses when the default Begin() is called. For z-index ordering, we will want to use either BackToFront or FrontToBack. There is very little difference between these two except the weight direction of each sprite’s z-index.

When using BackToFront, smaller z-index values have higher priority, with 0.0 being drawn over other sprites within the range up to 1.0.

When using FrontToBack, the opposite is true: Larger z-index values (such as 1.0) are treated with higher priority than lower values (such as 0.0).

It works best if you just choose one and stick with it to avoid confusion. If you think of a z-index value of 1.0 being “higher” in the screen depth, use FrontToBack. If 0.0 seems to have a higher priority, use BackToFront.

In our example, we will use FrontToBack, with larger values for the z-index having higher priority.

Z-Index Demo

To demonstrate the effect z-index ordering has on a scene, I have prepared an example that draws a screen full of sprites and then moves a larger sprite across the screen. Based on the z-index value of each sprite, the larger sprite will appear either over or under the other sprites. In this example, the screen is filled with animated asteroid sprites, and the larger sprite is an image of a shuttle. Figure 16.1 shows the demo with the shuttle sprite drawn on top of the first half of the asteroids.

The shuttle appears over the first half of the rows.
FIGURE 16.1 The shuttle appears over the first half of the rows.

Figure 16.2 shows the shuttle sprite farther across the screen, where it appears under the second half of the rows of asteroid sprites (which have a higher z-index than the shuttle sprite).

The shuttle appears under the second half of the rows.
FIGURE 16.2 The shuttle appears under the second half of the rows.

Wrapping Around the Screen Edge

To assist with this demo, a new Animation subclass has been written to automatically wrap a sprite around the edges of the screen. This completely eliminates any gameplay code that would otherwise have to be added to the Update() method. This is called synergy—when a combination of simple things produces awesome results! We’re starting to see that happen with our sprite and animation code now. Wrapping around the edges of the screen might be considered more of a behavior than an animation.

[code]
public class WrapBoundary : Animation
{
Rectangle boundary;
public WrapBoundary(Rectangle boundary)
: base()
{
animating = true;
this.boundary = boundary;
}
public override Vector2 ModifyPosition(Vector2 original)
{
Vector2 pos = original;
if (pos.X < boundary.Left)
pos.X = boundary.Right;
else if (pos.X > boundary.Right)
pos.X = boundary.Left;
if (pos.Y < boundary.Top)
pos.Y = boundary.Bottom;
else if (pos.Y > boundary.Bottom)
pos.Y = boundary.Top;
return pos;
}
}
[/code]

Z-Index Demo Source Code

Listing 16.1 contains the source code for the Z-Index Demo. Note the code highlighted in bold—these lines are relevant to our discussion of z-buffering.

LISTING 16.1 Source Code for the Z-Index Demo

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
TouchLocation oldTouch;
Random rand;
SpriteFont font;
List<Sprite> objects;
Texture2D asteroidImage;
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”);
//create object list
objects = new List<Sprite>();
//create shuttle sprite
Sprite shuttle = new Sprite(Content, spriteBatch);
shuttle.Load(“shuttle”);
shuttle.scale = 0.4f;
shuttle.position = new Vector2(0, 240);
shuttle.rotation = MathHelper.ToRadians(90);
shuttle.velocityLinear = new Vector2(4, 0);
Rectangle bounds = new Rectangle(-80, 0, 800 + 180, 480);
shuttle.animations.Add(new WrapBoundary(bounds));
shuttle.zindex = 0.5f;
objects.Add(shuttle);
//load asteroid image
asteroidImage = Content.Load<Texture2D>(“asteroid”);
//create asteroid sprites with increasing z-index
for (int row = 0; row < 13; row++)
{
float zindex = row / 12.0f;
for (int col = 0; col < 8; col++)
{
Sprite spr = new Sprite(Content, spriteBatch);
spr.image = asteroidImage;
spr.columns = 8;
spr.totalFrames = 64;
spr.animations.Add(new FrameLoop(0, 63, 1));
spr.position = new Vector2(30 + 60 * row, 30 + col * 60);
spr.size = new Vector2(60, 60);
spr.zindex = zindex;
objects.Add(spr);
}
}
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
foreach (Sprite spr in objects)
{
spr.Rotate();
spr.Move();
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend);
foreach (Sprite spr in objects)
{
spr.Animate();
spr.Draw();
}
spriteBatch.End();
base.Draw(gameTime);
}
}
[/code]

Transforming Frame Animations

Drawing Frames with Color Mods

We will first test the frame animation system with color modifications, then get into transform mods, and then create a new frame animation mod system as well.

Pivot Optimization

The sprite’s pivot point used for rotation, called origin, is a potentially serious source of difficulty and head scratching. When recurring events or calculations can be automated, they should be. Anytime the sprite frame size is changed, the origin should be modified along with it. This calls for either a method or a property. Presently, both size and origin are defined as public Vector2 variables in Sprite:

[code]
public Vector2 size;
public Vector2 origin;
[/code]

We want to make a change so that it doesn’t wreck any previous code that relies on the Sprite class, although those prior examples could just use an earlier version of the class if necessary. Perhaps the easiest way to do this is with a property, with size made private. This will require several changes in the Sprite class, as size is used quite a bit.

Sprite Class Modifications to Support, Well, Modifications

We will make changes to the Sprite class from the preceding chapter to support color and transform animation of a sprite with frame animation support.

  1. First, we’ll make a variable change in Sprite:
    [code]
    //public Vector2 size;
    private Vector2 p_size;
    [/code]
  2. Next, we’ll take care of initialization in the constructor:
    [code]
    //size.X = size.Y = 0;
    size = Vector2.Zero;
    [/code]
  3. Now we’ll work on the new property, with the changes made to the pivot/origin being done automatically:
    [code]
    public Vector2 size
    {
    get { return p_size; }
    set
    {
    p_size = value;
    origin = new Vector2(p_size.X / 2, p_size.Y / 2);
    }
    }
    [/code]
  4. One of the additional locations in the class where size is used is in Load(), which must be modified as follows. Note that with the property now being used, the new line of code causes origin to be changed as well! Now we’re already starting to see the benefit of this property right inside the Sprite class, and it will be equally helpful outside. Now, we can still manually change the origin whenever needed (for instance, recall the clock example from Hour 13, which needed to set the origin for the clock hands manually for it to rotate correctly). Just remember that changing size automatically changes the pivot point (origin).
    [code]
    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; }
    //size.X = image.Width;
    //size.Y = image.Height;
    size = new Vector2(image.Width, image.Height);
    return true;
    }
    [/code]

Revisiting CycleColorBounce

The CycleColor class was introduced back during Hour 12 as a way to fade any color in a desired direction, going to fully red, or full whiteout, or fade to black, or any combination in between. A subclass was then developed, called CycleColorBounce, that could do the same color transforms, but would not automatically stop animating when the limits were reached. Instead, it would continue to cycle. We also considered a class called SolidColor that did no real color changes, but that would be helpful in some cases when just a solid color is needed without changing the base Sprite.color property manually. In other words, without creating an additional “old color” style property in the class, SolidColor allowed a sprite to change color temporarily and then return.

Despite the relative simplicity of color modification in principle, it turns out that— ironically—our color mod classes are the most complex. Here again in Listing 15.1 are the Animation subclasses for reference, since we are using them in the example.

LISTING 15.1 Review of CycleColor Class for Reference (Not New Code)

[code]
public class CycleColor : Animation
{
public int red, green, blue, alpha;
public CycleColor(int red, int green, int blue, int alpha)
: base()
{
this.red = red;
this.green = green;
this.blue = blue;
this.alpha = alpha;
animating = true;
}
public override Color ModifyColor(Color original)
{
Color modified = original;
if (animating)
{
int R = original.R + red;
int G = original.G + green;
int B = original.B + blue;
int A = original.A + alpha;
if (R < 0 || R > 255 || G < 0 || G > 255 ||
B < 0 || B > 255 || A < 0 || A > 255)
{
animating = false;
}
modified = new Color(R, G, B, A);
}
return modified;
}
}
public class CycleColorBounce : CycleColor
{
public int rmin, rmax, gmin, gmax, bmin, bmax, amin, amax;
public CycleColorBounce(int red, int green, int blue, int alpha)
: base(red,green,blue,alpha)
{
rmin = gmin = bmin = amin = 0;
rmax = gmax = bmax = amax = 255;
}
public override Color ModifyColor(Color original)
{
Color modified = original;
if (animating)
{
int R = original.R + red;
int G = original.G + green;
int B = original.B + blue;
int A = original.A + alpha;
if (R < rmin)
{
R = rmin;
red *= -1;
}
else if (R > rmax)
{
R = rmax;
red *= -1;
}
if (G < gmin)
{
G = gmin;
green *= -1;
}
else if (G > gmax)
{
G = gmax;
green *= -1;
}
if (B < bmin)
{
B = bmin;
blue *= -1;
}
else if (B > bmax)
{
B = bmax;
blue *= -1;
}
if (A < amin)
{
A = amin;
alpha *= -1;
}
else if (A > amax)
{
A = amax;
alpha *= -1;
}
modified = new Color(R, G, B, A);
}
return modified;
}
}
[/code]

Listing 15.2 shows some source code showing how we will test color modification in the upcoming example; we will delay going into the example until the next section, which features a combined example.

LISTING 15.2 Sample Source Code Demonstrating the Latest Sprite Changes

[code]
//create skeleton sprite
skelly = new Sprite(Content, spriteBatch);
skelly.Load(“skeleton_attack”);
skelly.position = new Vector2(425, 240);
skelly.size = new Vector2(96,96);
skelly.scale = 2.0f;
skelly.columns = 10;
skelly.totalFrames = 80;
skelly.animations.Add(new CycleColorBounce(1, 2, 2, 0));
objects.Add(skelly);
[/code]

We haven’t tried to do anything this complex yet. The question is, will it work—will color and frame animations work correctly “out of the box” or will changes be needed?

The particular sprite sheet used for the color mod example is shown in Figure 15.1. As you can see, it is an animated skeleton with a sword (courtesy of Reiner Prokein via his website, http://www.reinerstilesets.de). We will see this awesome skeleton sprite in the upcoming example.

Animated skeleton warrior used as an example with color modification.
FIGURE 15.1 Animated skeleton warrior used as an example with color modification.

Drawing Frames with Transform Mods

Now we will test the frame animation system with transform modifications. Color modification is rather trivial compared to transform modifications, which involve gradually affecting movement, rotation, or scaling over time, very much in parallel with how frame animation changes the image over time. When we’re dealing with transforms, though, the image size and position are crucial for these mods to work correctly. If an image is loaded in from a bitmap file that has dimensions of 1024×1024, and then transforms are applied to the sprite based on this image size, most likely the sprite will not even show up on the WP7 screen, because frame animation is also being done, and the image is being thrown out of the range of the screen, in all likelihood. So, the initial setup of the sprite’s properties is essential to make transformed frame animation work correctly.

Figure 15.2 shows the sprite sheet for the animated swordsman also featured in the example (also courtesy of Reiner Prokein, http://www.reinerstilesets.de).

Animated swordsman used to demo two combined animations.
FIGURE 15.2 Animated swordsman used to demo two combined animations.

Custom Frame Animation

We have been using the simple version of the Sprite.Animate() method so far, passing just the time value to cause the animation to run at a consistent frame rate. But there is a second version of Sprite.Animate() that is called from this first one, giving us the ability to fine-tune or manually intervene in the way the animation runs. Here are the two methods again for reference (covered in the preceding hour):

[code]
public void Animate(double elapsedTime)
{
Animate(0, totalFrames-1, elapsedTime, 30);
}
public void Animate(int startFrame, int endFrame,
double elapsedTime, double speed)
{
if (totalFrames <= 1) return;
startTime += elapsedTime;
if (startTime > speed)
{
startTime = 0;
if (++frame > endFrame) frame = startFrame;
}
}
[/code]

The second method allows us to pass the start frame and end frame for a specific range of frames to be animated. By default, the first Animate() method just draws all frames (0 to totalFrames-1). This is a simple looped animation: When the last frame is reached, the animation system loops back around to the first frame again. But there are more advanced forms of frame animation that can be performed as well, such as a frame bounce rather than a loop: When the last frame is reached, the animation direction reverses downward toward the first frame again, and then reverses again in the positive direction. This can be actually quite useful if the artwork has only half of the total frames needed to create a complete loop, and instead the bounce technique must be used.

Now we come to the dilemma: The game loop does not have room for custom, manual method calls! Here is the code in Update() that updates the sprite list:

[code]
//update all objects
foreach (Sprite spr in objects)
{
spr.Rotate();
spr.Move();
}
And here is the code in Draw() that animates and draws all the sprites:
foreach (Sprite spr in objects)
{
spr.Animate(); //mods
spr.Animate(gameTime.ElapsedGameTime.Milliseconds); //frames
spr.Draw();
}
[/code]

It is true that we can jump into these foreach loops and make manual changes to a specific sprite in the list. Or we can just leave a sprite out of the list and handle it separately. That works and there’s nothing wrong with that approach. But it kind of defeats the benefits of having an automated game loop, in which sprites are updated and drawn automatically based on properties. This describes a data-driven game loop, and it is simply unmatched in versatility and power when it comes to building a complex game. In other words, we want this to work correctly; we really don’t want to bypass it. If a new feature is needed, it’s better to work that into the loop than to make exceptions.

What we need is a new modification method in the Animation class. Do you see where we’re going with this?

Updating the Animation Class

The Sprite class will retain the two new Animate() methods from the preceding hour, and that form of frame animation can be used when it will suffice. But we will now add a more versatile frame animation system that uses the Animation class, fully merging the functionality of color and transform animation with frame animation. This calls for some changes to the Animation class, but no changes to the Sprite class are needed because it already supports multiple animations.

Add the following new method called ModifyFrame() to the Animation class as shown:

[code]
public class Animation
{
. . . some portions skipped
public virtual int ModifyFrame(int current)
{
return current;
}
}
[/code]

New Modifications to the Sprite Class

To make the new frame animation work with Sprite, some things must be modified.

  1. A new variable, frameDir, must be added to the class:
    [code]
    public int columns, frame, totalFrames, frameDir;
    [/code]
  2. It is initialized in the constructor:
    [code]
    columns = 1;
    frame = 0;
    totalFrames = 1;
    frameDir = 1;
    [/code]
  3. The Animate() method can be updated like so (note the line in bold). Remember, this call to ModifyFrame() is the default or base method call, and the current frame is passed as the sole parameter. Any class that inherits from Animate and overrides ModifyFrame() will have the animation range set to specific values.
    [code]
    public void Animate()
    {
    if (animations.Count == 0) return;
    foreach (Animation anim in animations)
    {
    if (anim.animating)
    {
    color = anim.ModifyColor(color);
    position = anim.ModifyPosition(position);
    rotation = anim.ModifyRotation(rotation);
    scaleV = anim.ModifyScale(scaleV);
    frame = anim.ModifyFrame(frame);
    }
    else
    {
    animations.Remove(anim);
    return;
    }
    }
    }
    [/code]

New Animation Subclass Examples

Strangely enough, that’s it! That’s all we had to do to add frame animation support to the Animation class. Now we can create custom subclasses that inherit from Animation to create any kind of special frame animation we need. Listing 15.3 gives two sample animations involving frame modifications that you can study as a reference.

LISTING 15.3 New Animations

[code]

public class FrameLoop : Animation
{
public FrameLoop(int start, int end, int current, int direction)
: base()
{
animating = true;
}
public override int ModifyFrame(int start, int end, int current,
int direction)
{
current += direction;
if (current > end)
current = start;
else if (current < start)
current = end;
return current;
}
}
public class FrameBounce : Animation
{
public FrameBounce(int start, int end, int current, int direction)
: base()
{
animating = true;
}
public override int ModifyFrame(int start, int end, int current,
int direction)
{
current += direction;
if (current > end)
{
direction *= -1;
current = end;
}
else if (current < start)
{
direction *= -1;
current = start;
}
return current;
}
}
[/code]

If you want frame animation to continue to function as it did before, one option is to automatically add a FrameLoop object to the animation list when a sprite is created (from the Sprite constructor). But it’s probably better to just work with the more advanced system now in place.

The FrameLoop class duplicates the existing functionality of parameterized Sprite.Animate(), which updates frame animation. Those two methods could now be removed since FrameLoop replicates that functionality, but I will just leave them alone since they might be useful. Next, the FrameBounce class handles the situation in which an animation will go from start frame to end frame, and then reverse direction down toward start frame again, rather than looping.

Figure 15.3 shows an animated dragon that will also be used in the example.

 Animated dragon used to demo OrbitalMovement and FrameBounce.
FIGURE 15.3 Animated dragon used to demo OrbitalMovement and FrameBounce.

As of the preceding hour, we had an updated Draw() that handles sprite frame animation like so:

[code]
foreach (Sprite spr in objects)
{
spr.Animate(); //mods
spr.Animate(gameTime.ElapsedGameTime.Milliseconds); //frames
spr.Draw();
}
[/code]

The second line in the method, the second call to Animate(), can now be removed, after FrameLoop or FrameBounce has been added as an animation. But if you do remove that line, you must be sure to add FrameLoop or FrameBounce (or some variation thereof) to the animation list after a sprite has been created. For instance:

[code]
//create dragon sprite
dragon = new Sprite(Content, spriteBatch);
dragon.Load(“dragon”);
dragon.animations.Add(new FrameLoop(48, 56, 48, 1));
[/code]

Animation Mods Demo

We need a complete example to demonstrate how the new improvements to the animation system work. Figure 15.4 shows the final output of the demo for this hour. On the left is the swordsman character animated with FrameLoop and ThrobBounce.

The final Animation Mods Demo program animates three sprites with several animations at once.
FIGURE 15.4 The final Animation Mods Demo program animates three sprites with several animations at once.

The New ThrobBounce Class

The ThrobBounce class inherits from Throb and perpetuates the “throb” animation by overriding the animating property (making it continue when the animation normally ends). For reference, the original Throb class is shown here in Listing 15.4 as well since it is inherited.

LISTING 15.4 New ThrobBounce Class (Throb Repeated for Reference)

[code]
public class Throb : Animation
{
public float startScale, endScale, speed;
private bool p_started;
public Throb(float startScale, float endScale, float speed)
: base()
{
p_started = false;
animating = true;
this.startScale = startScale;
this.endScale = endScale;
this.speed = speed;
}
public override Vector2 ModifyScale(Vector2 original)
{
if (!animating) return original;
Vector2 modified = original;
if (!p_started)
{
modified.X = startScale;
modified.Y = startScale;
p_started = true;
}
modified.X += speed;
modified.Y += speed;
if (modified.X >= endScale)
speed *= -1;
else if (modified.X <= startScale)
animating = false;
return modified;
}
}
public class ThrobBounce : Throb
{
public ThrobBounce(float startScale, float endScale, float speed)
: base(startScale, endScale, speed)
{
}
public override Vector2 ModifyScale(Vector2 original)
{
//keep it going
Vector2 scale = base.ModifyScale(original);
if (!animating)
{
animating = true;
speed *= -1;
}
return scale;
}
}
[/code]

The Animation Mods Demo Source Code

Here in Listing 15.5 is the complete source code for the Animation Mods Demo that wraps up all the techniques of this hour in one short example.

LISTING 15.5 Source Code for the Animation Mods Demo Program

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
TouchLocation oldTouch;
Random rand;
SpriteFont font;
List<Sprite> objects;
Sprite swordsman, skelly, dragon;
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”);
//create object list
objects = new List<Sprite>();
//create swordsman sprite
swordsman = new Sprite(Content, spriteBatch);
swordsman.Load(“swordsman_walking”);
swordsman.position = new Vector2(150, 240);
swordsman.size = new Vector2(96, 96);
swordsman.columns = 8;
swordsman.totalFrames = 64;
swordsman.animations.Add(new FrameLoop(0, 63, 1));
swordsman.animations.Add(new ThrobBounce(0.5f, 4.0f, 0.1f));
objects.Add(swordsman);
//create skeleton sprite
skelly = new Sprite(Content, spriteBatch);
skelly.Load(“skeleton_attack”);
skelly.position = new Vector2(425, 240);
skelly.size = new Vector2(96,96);
skelly.scale = 2.0f;
skelly.columns = 10;
skelly.totalFrames = 80;
skelly.animations.Add(new FrameLoop(0, 79, 1));
skelly.animations.Add(new CycleColorBounce(1, 2, 2, 0));
objects.Add(skelly);
//create dragon sprite
dragon = new Sprite(Content, spriteBatch);
dragon.Load(“dragon”);
dragon.position = new Vector2(700, 240);
dragon.size = new Vector2(128, 128);
dragon.scale = 1.5f;
dragon.columns = 8;
dragon.totalFrames = 64;
dragon.animations.Add(new FrameBounce(48, 55, 1));
dragon.animations.Add(new OrbitalMovement(
dragon.position, 50, 0, 0.1f));
objects.Add(dragon);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
//update all objects
foreach (Sprite spr in objects)
{
spr.Rotate();
spr.Move();
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
foreach (Sprite spr in objects)
{
spr.Animate();
spr.Draw();
}
spriteBatch.DrawString(font, ““, Vector2.Zero, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}
[/code]

The sprite animation system is now completely functional, with lots of room for growth and with support for custom Animation subclasses. In a professional game, these subclasses would be programmed by designers with Lua script code, allowing designers to make their own custom animations, and the job of the programmers essentially being done at this point with regard to the Animation and Sprite classes. But we aren’t getting into scripting, so feel free to create your own awesome new animation classes to see what interesting effects you can come up with! In the following hour, we will study one last subject related to sprites: z-index ordering. This will allow our sprites to draw over each other with some having higher priority than others.

 

 

Sprite Transform Animation

The preceding hour saw the introduction of a powerful new Animation class that added color animation effect capabilities to Sprite objects. By subclassing Animation, we can create specific animation effects in reusable classes, such as the CycleColor class that demonstrated color cycling over time. There are many more possibilities with the Animation class that have not been explored yet, so we will spend this hour delving into new aspects of animation that are easy to implement and that produce great results for relatively little effort. Specifically, we’ll look at animating the transforms that can be applied with existing properties in the Sprite class: position, rotation, and scaling. These properties are important as they are currently implemented in Sprite, so we won’t mess up what already works. Instead, variations of the Animation class will set up modifiers for these Sprite properties, with changes applied to the Sprite.Animate() method to make it work.

Adding Transform Support to the Animation Class

To perform transforms on a sprite, we need to add some new code to the Sprite.Animate() method. Currently, the method supports only color animation, and we want transforms as well. So, here are the changes:

[code]
public void Animate()
{
if (p_animation != null)
{
if (p_animation.animating)
{
color = p_animation.ModifyColor(color);
position = p_animation.ModifyPosition(position);
rotation = p_animation.ModifyRotation(rotation);
scaleV = p_animation.ModifyScale(scaleV);
}
}
}
[/code]

This is all that is needed to give Animation access to Sprite properties, and we will take full advantage of it!

Position Transforms

When it comes to “animating” the position of a sprite with a custom animation class, we are really getting into custom behaviors, and the term “animation” may not be as appropriate—but it is what it is, so let’s just work with it. If you want to use the term “behavior” in the name for your own transform animation classes, that might seem more appropriate. Since we can do anything with the position, a really dumb but technically valid translation “animation” could be to simply position a sprite to one hard-coded location and never let it leave. That’s silly but it can be done, because the translation modifier added to the Sprite class makes it possible.

The important thing to remember when writing a translation class is to override the ModifyPosition() method (coming from the base Animation class). Any of these base methods that are not overridden will return the passed parameter back, so that no changes are made. Here is just one possible translation class called OrbitalMovement, shown in Listing 13.1. This class inherits from Animation, and exposes a number of properties (which will usually just be passed to the constructor to make initialization simple). ModifyPosition() is where all the work is done. Using some trig, the incoming position is completely ignored, while a calculated orbital position for the object is returned. In other words, it doesn’t matter where a Sprite is located; when this method runs, it calculates position based on sine and cosine calculations, using the properties provided.

LISTING 13.1 Source Code for the OrbitalMovement Class

[code]
public class OrbitalMovement : Animation
{
public int radius;
public Vector2 center;
public double angle;
public float velocity;
public OrbitalMovement(Vector2 center, int radius, double angle,
float velocity)
: base()
{
animating = true;
this.center = center;
this.radius = radius;
this.angle = angle;
this.velocity = velocity;
}
public override Vector2 ModifyPosition(Vector2 original)
{
Vector2 modified = original;
angle += velocity;
modified.X = center.X + (float)(Math.Cos(angle) * radius);
modified.Y = center.Y + (float)(Math.Sin(angle) * radius);
return modified;
}
}
[/code]

Figure 13.1 shows the output of the Transform Animation Demo program, which is found in Listing 13.2. There are a lot of asteroids orbiting a black hole in this small simulation! The really great thing about it is that no orbit code is found anywhere in the main program—it’s all stuffed in the OrbitalMovement class! Let this be just one example of what you can do on your own with similar results. I don’t know about you, but I enjoy producing interesting results with relatively small amounts of code. It is a challenge! Consider how to combine several simple rules to produce interesting results, rather than taking the brute force approach and coding a solution one step at a time. You may be surprised by how this synergy of code works. Following is the code for the program so that you can see how the black hole and asteroids are created and drawn.

The Transform Animation Demo uses a custom animation class to simulate an orbit.
FIGURE 13.1 The Transform Animation Demo uses a custom animation class to simulate an orbit.

LISTING 13.2 Source Code for the Transform Animation Demo Program

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Random rand;
SpriteFont font;
Texture2D asteroidImg;
List<Sprite> objects;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
rand = new Random();
spriteBatch = new SpriteBatch(GraphicsDevice);
font = Content.Load<SpriteFont>(“WascoSans”);
objects = new List<Sprite>();’
Sprite blackhole = new Sprite(Content, spriteBatch);
blackhole.Load(“blackhole”);
blackhole.position = new Vector2(400, 240);
blackhole.scale = 0.5f;
blackhole.origin = new Vector2(64, 64);
blackhole.velocityAngular = 1.0f;
objects.Add(blackhole);
asteroidImg = Content.Load<Texture2D>(“asteroid”);
for (int n = 0; n < 500; n++)
{
Sprite ast = new Sprite(Content, spriteBatch);
ast.image = asteroidImg;
ast.origin = new Vector2(32, 32);
ast.scale = 0.25f + (float)(rand.NextDouble() * 0.5);
ast.velocityAngular = (float)(rand.NextDouble() * 0.1);
Vector2 pos = new Vector2(380 + rand.Next(40),
220 + rand.Next(40));
double angle = rand.NextDouble() * 6.0;
int radius = rand.Next(60, 400);
float velocity = (float)(rand.NextDouble() * 0.01 *
((400-radius) * 0.1) );
ast.SetAnimation(new OrbitalMovement(pos, radius,
angle, velocity));
objects.Add(ast);
}
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
foreach (Sprite spr in objects)
{
spr.Move();
spr.Rotate();
}
base.Update(gameTime);
}’
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
foreach (Sprite spr in objects)
{
spr.Animate();
spr.Draw();
}
spriteBatch.DrawString(font, “Transform Animation Demo”,
new Vector2(0, 0), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}
[/code]

Rotation and Scaling Transforms

We don’t need a custom Animation subclass just to rotate a Sprite object, so what is the purpose of a so-called rotation animation? Don’t think of rotation in terms of absolute rotation value being set and used to draw. Consider rotation instead in terms of rotation over time. We can transform an animation in small increments over time for interesting results. For example, a sprite could rotate back and forth between 180 degrees to look like it’s wobbling, or it could rotate around at one-second increments like the hand of a clock. It’s all based on the code in your own Animation subclass. To demonstrate, I’ve created an analog clock example. The clock will be based around a class called ClockHand, found in Listing 13.3.

LISTING 13.3 Source Code for the ClockHand Class

[code]
public class ClockHand : Animation
{
public int direction;
public ClockHand(int direction) //0 to 59
: base()
{’’
animating = true;
this.direction = direction;
}
public override float ModifyRotation(float original)
{
float angle = (float)(direction / 60.0f) *
(float)(2.0f * Math.PI);
return angle;
}
}
[/code]

This class is on the simple side so it can be used for hours, minutes, and seconds. Assuming you supply the project with an arrow for the “clock hands,” the ClockHand.direction property represents the time value. For minutes and seconds, this comes to a value from 0 to 59. For hours, we can use the same range by just multiplying hours by 5 (because 60 / 12 = 5). The time values are passed to ClockHand.direction at regular intervals in the program’s Update() method:

[code]
hours.direction = DateTime.Now.Hour * 5;
minutes.direction = DateTime.Now.Minute;
seconds.direction = DateTime.Now.Second;
[/code]

Figure 13.2 shows the output of the Rotate/Scale Animation Demo, but we can just call it the “Clock Demo” for short. This example does scale the clock-hand sprites during initialization, but the scale factor is not being actively used in this example. Given the ease with which rotation was modified in this example, I’m sure you will see how easy scaling would be as well. The source code is found in Listing 13.4.

LISTING 13.4 Source Code for the Clock Demo Program

[code]
public class Game1 : Microsoft.Xna.Framework.Game’’
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Random rand;
SpriteFont font;
List<Sprite> objects;
ClockHand hours, minutes, seconds;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
rand = new Random();
spriteBatch = new SpriteBatch(GraphicsDevice);
font = Content.Load<SpriteFont>(“WascoSans”);
objects = new List<Sprite>();
Sprite clock = new Sprite(Content, spriteBatch);
clock.Load(“clock”);
clock.position = new Vector2(400, 240);
objects.Add(clock);
hours = new ClockHand(0);
minutes = new ClockHand(0);
seconds = new ClockHand(0); ’’
Sprite hourHand = new Sprite(Content, spriteBatch);
hourHand.Load(“arrow200”);
hourHand.position = new Vector2(400, 240);
hourHand.origin = new Vector2(30, 199);
hourHand.scaleV = new Vector2(1.5f, 0.7f);
hourHand.color = new Color(250, 50, 250);
hourHand.SetAnimation(hours);
objects.Add(hourHand);
Sprite minuteHand = new Sprite(Content, spriteBatch);
minuteHand.Load(“arrow200”);
minuteHand.position = new Vector2(400, 240);
minuteHand.origin = new Vector2(30, 199);
minuteHand.scaleV = new Vector2(1.0f, 0.9f);
minuteHand.color = new Color(250, 100, 150);
minuteHand.SetAnimation(minutes);
objects.Add(minuteHand);
Sprite secondHand = new Sprite(Content, spriteBatch);
secondHand.Load(“arrow200”);
secondHand.position = new Vector2(400, 240);
secondHand.origin = new Vector2(30, 199);
secondHand.scaleV = new Vector2(0.25f, 1.0f);
secondHand.color = new Color(120, 80, 150);
secondHand.SetAnimation(seconds);
objects.Add(secondHand);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
foreach (Sprite spr in objects)
{
spr.Rotate();
spr.Move();
}
hours.direction = DateTime.Now.Hour * 5;
minutes.direction = DateTime.Now.Minute;
seconds.direction = DateTime.Now.Second;
base.Update(gameTime);
}’’
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
foreach (Sprite spr in objects)
{
spr.Animate();
spr.Draw();
}
spriteBatch.DrawString(font, “Rotate/Scale Animation Demo”,
new Vector2(0, 0), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}’’
[/code]

The Clock Demo actually shows how to use an animation class that modifies rotation.
The Clock Demo actually shows how to use an animation class that modifies rotation.

Code Consistency

An interesting thing has happened since we started using the Animation class and all of its children. Have you noticed that much of the source code in our examples changes very little from one project to the next? Oh, there are often different variables in use, but LoadContent() seems to be where most of the code is being written at this point. Have you really noticed much of a change to Update() or Draw() in quite some time? This is a good sign! When standard code begins to remain unchanged, that means we have replaced the core logic of the program with a statedriven, property-based game engine. Sure, it’s a very, very simple game engine, but that is the path we are now on. The end result is some very solid code, easy to debug, easy to modify, easy to understand. Strive for this type of code in your own projects!

Combining Multiple Animations

We have quite a bit of good animation code now with the ability to add new effects fairly easily using the techniques learned in the preceding two chapters. The next step is quite revolutionary: doing more than one animation at a time! The current animation system will apply a behavior to a game sprite using a single method, Sprite.SetAnimation(). This is going to be replaced with a more powerful mechanism that will support many animations stored in a list.

Although it is technically possible to remove an animation from the list, we’re not to be concerned with seldom-used features like that. Our sprite animation system currently supports just one animation, but it can be replaced at any time with the SetAnimation() method just mentioned. The same will be true when multiple animation support is added, because the list container will have public scope.

Remember the 80/20 rule! Focus most of your efforts on writing code that will be used 80% of the time, not on features rarely used (unless you have time to kill).

Sprite Class Modifications

Adding Multiple Animation Support

We need to make a few changes to the Sprite class to support multiple animations.

  1. Remove the old Animation variable, p_animation, and replace it with a List:
    [code]
    //private Animation p_animation;
    public List<Animation> animations;
    [/code]
  2. Completely remove the SetAnimation() method, which is no longer used:
    [code]
    //public void SetAnimation(Animation animation)
    //{
    // p_animation = animation;
    //}
    [/code]
  3. Make the following changes to the Animate() method. This is the most dramatic change to the class required at this time, adding support for multiple animations. Now, when an animation has completed (by setting the animating property to false), it is actually removed!
    [code]
    public void Animate()
    {
    if (animations.Count == 0) return;
    foreach (Animation anim in animations)
    {
    if (anim.animating)
    {
    color = anim.ModifyColor(color);
    position = anim.ModifyPosition(position);
    rotation = anim.ModifyRotation(rotation);
    scaleV = anim.ModifyScale(scaleV);
    }
    else
    {
    animations.Remove(anim);
    return;
    }
    }
    }
    [/code]

New Animations

To support the upcoming example, here are a couple of new animations to examine. They are called Spin and Throb, affecting the rotation and scaling, respectively. First up is the Spin class, which does a 360-degree spin and then stops. Note that this class overrides only ModifyRotation(), but none of the other modification methods. The source code is found in Listing 13.5.

LISTING 13.5 Source Code for the Spin Class

[code]
public class Spin : Animation
{
private float angleDist, velocity;
public Spin(float velocity)
: base()
{
animating = true;
this.velocity = velocity;
angleDist = 0.0f;
}
public override float ModifyRotation(float original)
{
if (animating)
{
float fullCircle = (float)(2.0 * Math.PI);
angleDist += velocity;
if (angleDist > fullCircle)
animating = false;
original += velocity;
}
return original;
}
}t
[/code]

Next up is the Throb class, which performs a scaling “throb” of a sprite by cycling between a start and an end scale value. This class implements only ModifyScale(), because it does not need to touch any other property. The source code is found in Listing 13.6.

LISTING 13.6 Source Code for the Throb Class

[code]
public class Throb : Animation
{
public float startScale, endScale, speed;
private bool p_started;
public Throb(float startScale, float endScale, float speed)
: base()
{t
p_started = false;
animating = true;
this.startScale = startScale;
this.endScale = endScale;
this.speed = speed;
}
public override Vector2 ModifyScale(Vector2 original)
{
if (!animating) return original;
Vector2 modified = original;
if (!p_started)
{
modified.X = startScale;
modified.Y = startScale;
p_started = true;
}
modified.X += speed;
modified.Y += speed;
if (modified.X >= endScale)
speed *= -1;
else if (modified.X <= startScale)
animating = false;
return modified;
}
}t
[/code]

Multiple Animation Demo

With all of these enhancements, we can now do multiple animations per sprite. Remember, the behavior of the animations is entirely up to you, so if you don’t like how an animation is automatically removed when it is done, then change it! Figure 13.3 shows the sample program. The Throb animation is automatically renewed anytime the animations have all completed. Also, you can tap the screen to add a Spin animation. The fun thing about this demo is that you can tap the screen repeatedly to give the sprite a superfast spin! That is, you can do so until each Spin has finished its 360-degree rotation and is removed. Listing 13.7 has the source code.

Testing multiple animations with the Spin and Throb animations simultaneously.
FIGURE 13.3 Testing multiple animations with the Spin and Throb animations simultaneously.

LISTING 13.7 Source Code for the Multiple Animation Demo Program

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
TouchLocation oldTouch;
Random rand;
SpriteFont font;
Sprite ship;
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”);
ship = new Sprite(Content, spriteBatch);
ship.Load(“ship”);
ship.position = new Vector2(400, 240);
ship.rotation = (float)rand.NextDouble();
ship.animations.Add(new Spin(0.1f));
ship.animations.Add(new Throb(0.5f, 3.0f, 0.2f));
}f’
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)
{
ship.animations.Add(new Spin(0.1f));
}
oldTouch = touch;
}
ship.Rotate();
ship.Move();
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
ship.Animate();
//reset throb
if (ship.animations.Count == 0)
ship.animations.Add(new Throb(0.5f, 3.0f, 0.2f));
ship.Draw();
int anims = ship.animations.Count;
spriteBatch.DrawString(font, “Tap screen to add animation…”,
new Vector2(0,0), Color.White);
spriteBatch.DrawString(font, “Animations:” + anims.ToString(),
new Vector2(600,0), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}f’
}
[/code]

Sprite Color Animation

Getting Started with Color Animation

The first important point to consider when building an animation system that works with the Sprite class without infiltrating the Sprite class—that is, without mucking it up—is to design the animation system in such a way that transform or change values are used, not the internal properties of Sprite. Animation will be a passive, nonintrusive helper for Sprite, producing transform values that can be used or ignored, as the programmer sees fit. Animation can be integrated inside Sprite, or one or more animations can be applied to a sprite in the main program code instead.

The second significant aspect to this animation system is that animation applied to a sprite will not be permanent. A single animation is processed to a conclusion and then removed. What this allows us to do is write “fire and forget” animation code by setting up an animation event, turning it on, and then just watching it work. When completed, the animation object is deleted. Now, it’s perfectly fine if you want an animation to continue. You have control over when and where the animating property is set to false (which will cause it to be removed). To make this work in such a “fire and forget” manner, the Sprite class will need to be able to handle more than one animation at a time. This calls for a managed list.

The Main Animation Class

The primary or base Animation class, from which all “functional” animation subclasses will be derived, must have certain methods in place for modifying properties within Sprite. If we want to animate the color, a ModifyColor() method must be included. If we want to animate the scale, a ModifyScale() method is required. There really aren’t very many such methods needed in the base Animation class, because we need to animate only things that will affect a sprite’s appearance or behavior. Remember, our Sprite class already has the capability to perform transforms (changing the position, rotation, and scaling). Although it is possible to animate the basic transform properties in Sprite, that will be rare. Nevertheless, we still must give the programmer the ability to animate the transforms. Listing 12.1 contains our base Animation class.

LISTING 12.1 Animation Class Source Code

[code]
public class Animation
{
public bool animating;
public Animation()
{
animating = false;
}
public virtual void Update()
{
}
public virtual Color ModifyColor(Color original)
{
return original;
}
public virtual Vector2 ModifyPosition(Vector2 original)
{
return original;
}
public virtual float ModifyRotation(float original)
{
return original;
}
public virtual float ModifyScale(float original)
{
return original;
}
}
[/code]

Using Animation as a Base Class

Note how the methods in Animation return the passed parameter. This is a default behavior that will just perpetuate any properties that are not modified by a subclass. For instance, if we are doing just color animation, and not changing rotation or any other property, then rotation is completely ignored by the subclass and will have no effect on the animation being processed.

A subclass of Animation should override at least one of the ModifyXXX() methods in order to do something useful. Unless at least one of them is modified, no animation will occur and the class will essentially do nothing. On the flip side, more than one type of animation can be performed by the same class! Suppose you want a sprite to move back and forth on the screen while rotating and scaling—this animation system can handle that task. The sprite can then be used for collision testing, and all other aspects of sprite behavior remain intact! Perhaps the simplest functional subclass of Animation might look like this:

[code]

public class AnimationTemplate : Animation
{
public AnimationTemplate()
: base()
{
}
public void Update()
{
}
}
[/code]

You can use this as a template for your own custom animations. Just override any of the ModifyXXX() methods (such as ModifyColor()) to modify the properties you want to affect a sprite’s behavior. The Update() method can be used for generalpurpose processing. In the next two hours, we will use Update() as a general-purpose “pump” or “motor” for frame animation and transforms. Note that you can also add any variables you need to your Animation subclass. You can also override the parameter list of the modification methods to produce custom results (such as custom subsets of an existing animation).

Modifications to the Sprite Class

The Sprite class will need a few changes to support animation. First, there’s the addition of the Animation variable as a private:

[code]
private Animation p_animation;
[/code]

Next, there’s initialization of the variable in the Sprite constructor:

[code]
p_animation = null;
[/code]

And next, we need a method to set the current animation. We will eventually replace this with a List and support more than one animation at a time, but for now we’ll start with first things first!

[code]
public void SetAnimation(Animation animation)
{
p_animation = animation;
}
[/code]

Finally, we need a new method to get animation up and running! The following Animate() method will evolve quite a bit over the next two chapters. At this point, we need to work with only one type of animation (color cycling).

[code]
public void Animate()
{
if (p_animation != null)
{
if (p_animation.animating)
{
this.color = p_animation.ModifyColor(this.color);
}
}
}
[/code]

Color Animation

Color animation begins with a solid color that can then be transformed by manipulating the color components (red, green, blue, and alpha). We’ll see how to write a basic solid-color class to begin experimenting with color animation before getting into color cycling.

Solid Colors

A solid-color “animation” might seem contradictory, but there actually is a very good use for such a thing. Although it is true that a sprite can be drawn in any desired color, that often requires additional global variables because the Sprite class does not keep track of color changes, just the color property. To change the color on the fly while a game is running would require globals to keep track of the colors and a conditional variable used to trigger the color change. We can do this more easily with a subclass of Animation that works with solid colors.

Let’s begin with Listing 12.2, the source code to the new SolidColor class, which inherits from Animation. There’s very little to this class, which makes it a good starting point for our study of color animation coming up.

LISTING 12.2 Source Code for the SolidColor Class

[code]
public class SolidColor : Animation
{
public Color color;
public SolidColor(Color color)
: base()
{
this.color = color;
animating = true;
}
public override Color ModifyColor(Color original)
{
return color;
}
}
[/code]

The Solid Color project is part of the Color Animation Demo solution included with this hour (several projects in the solution share a common content project for convenience). To run this specific project in the solution, right-click the project name and select Debug, Start New Instance. In this manner, any project in a larger solution can be debugged, while F5 will try to run the currently “active” project. Figure 12.1 shows the program running. There are three boxes that move on the screen, and they change color anytime they hit the edge. Simple as that! The source code to the project is found in Listing 12.3.

Solid color transform as a form of “animation.”
FIGURE 12.1 Solid color transform as a form of “animation.”

LISTING 12.3 Source Code for the Solid Color Project

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Random rand;
SpriteFont font;
Texture2D shapeImage;
List<Sprite> shapes;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
rand = new Random();
spriteBatch = new SpriteBatch(GraphicsDevice);
font = Content.Load<SpriteFont>(“WascoSans”);
shapeImage = Content.Load<Texture2D>(“box”);
Sprite box1 = new Sprite(Content, spriteBatch);
box1.image = shapeImage;
box1.position = new Vector2(rand.Next(0, 200), rand.Next(0, 380));
box1.velocityLinear = new Vector2(4.0f, 3.0f);
box1.origin = new Vector2(64, 64);
box1.SetAnimation(null);
Sprite box2 = new Sprite(Content, spriteBatch);
box2.image = shapeImage;
box2.position = new Vector2(rand.Next(200, 400), rand.Next(0, 380));
box2.velocityLinear = new Vector2(4.0f, 3.0f);
box2.origin = new Vector2(64, 64);
box2.SetAnimation(null);
Sprite box3 = new Sprite(Content, spriteBatch);
box3.image = shapeImage;
box3.position = new Vector2(rand.Next(400, 600), rand.Next(0, 380));
box3.velocityLinear = new Vector2(4.0f, 3.0f);
box3.origin = new Vector2(64, 64);
box3.SetAnimation(null);
shapes = new List<Sprite>();
shapes.Add(box1);
shapes.Add(box2);
shapes.Add(box3);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
foreach (Sprite spr in shapes)
{
spr.Move();
if (spr.position.X < 0 || spr.position.X > 800 ||
spr.position.Y < 0 || spr.position.Y > 480)
{
spr.SetAnimation(new SolidColor(
new Color(rand.Next(255), rand.Next(255),
rand.Next(255))));
if (spr.position.X < -64 || spr.position.X > 800+64)
spr.velocityLinear.X *= -1;
if (spr.position.Y < -64 || spr.position.Y > 480+64)
spr.velocityLinear.Y *= -1;
}
else
spr.SetAnimation(new SolidColor(Color.White));
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
foreach (Sprite spr in shapes)
{
spr.Animate();
spr.Draw();
}
spriteBatch.DrawString(font, “Solid Color Demo”,
new Vector2(0, 0), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}
[/code]

Color Cycling

One common color animation involves cycling a color within a range and either going back and forth between two limits or wrapping around in either direction. A base CycleColor class will help us get started and then we can write subclasses from it to do specific color movements. The source code is found in Listing 12.4.

LISTING 12.4 Source Code for the CycleColor Class

[code]
public class CycleColor : Animation
{
public int red, green, blue, alpha;
public CycleColor(int red, int green, int blue, int alpha)
: base()
{
this.red = red;
this.green = green;
this.blue = blue;
this.alpha = alpha;
animating = true;
}
public override Color ModifyColor(Color original)
{
Color modified = original;
if (animating)
{
int R = original.R + red;
int G = original.G + green;
int B = original.B + blue;
int A = original.A + alpha;
if (R < 0 || R > 255 || G < 0 || G > 255 || B < 0 || B > 255
|| A < 0 || A > 255)
{
animating = false;
}
modified = new Color(R, G, B, A);
}
return modified;
}
}
[/code]

This class will cycle a color from its original values upward or downward based on the color cycling modifiers passed to the CycleColor() constructor until the range is exceeded, at which point animation stops. The modifiers will usually be +1 or -1 for each color component, unless faster color cycling is desired. Let’s test it with a quick sample program called Cycle Color Animation (see Listing 12.5).

Three boxes are added: colored red, green, and blue. The boxes fade completely to black based on the CycleColor class’s properties, but if different colors were used for each one, they would not fade completely to black, but would just modify the color components to produce different color results. Setting the initial conditions (such as original color) does have an effect and should be considered ahead of time. In the combined Visual Studio solution for this hour, this project is called Cycle Color Animation, and is shown in Figure 12.2.

Demonstrating color cycling with three rotating boxes of different colors.
FIGURE 12.2 Demonstrating color cycling with three rotating boxes of different colors.

LISTING 12.5 Source Code to the Cycle Color Animation Project

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
TouchLocation oldTouch;
SpriteFont font;
Texture2D shapeImage;
List<Sprite> shapes;
CycleColor cycleRed, cycleGreen, cycleBlue;
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);
font = Content.Load<SpriteFont>(“WascoSans”);
shapeImage = Content.Load<Texture2D>(“box”);
Sprite redBox = new Sprite(Content, spriteBatch);
redBox.image = shapeImage;
redBox.position = new Vector2(200, 240);
redBox.origin = new Vector2(64, 64);
redBox.velocityAngular = 0.05f;
redBox.color = new Color(255, 0, 0);
Sprite greenBox = new Sprite(Content, spriteBatch);
greenBox.image = shapeImage;
greenBox.position = new Vector2(400, 240);
greenBox.origin = new Vector2(64, 64);
greenBox.velocityAngular = 0.05f;
greenBox.color = new Color(0, 255, 0);
Sprite blueBox = new Sprite(Content, spriteBatch);
blueBox.image = shapeImage;
blueBox.position = new Vector2(600, 240);
blueBox.origin = new Vector2(64, 64);
blueBox.velocityAngular = 0.05f;
blueBox.color = new Color(0, 0, 255);
cycleRed = new CycleColor(-1, 0, 0, 0);
redBox.SetAnimation(cycleRed);
cycleGreen = new CycleColor(0, -1, 0, 0);
greenBox.SetAnimation(cycleGreen);
cycleBlue = new CycleColor(0, 0, -1, 0);
blueBox.SetAnimation(cycleBlue);
shapes = new List<Sprite>();
shapes.Add(redBox);
shapes.Add(greenBox);
shapes.Add(blueBox);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
foreach (Sprite spr in shapes)
{
spr.Rotate();
spr.Move();
}
if (!cycleBlue.animating)
{
cycleBlue.blue *= -1;
cycleBlue.animating = true;
}
if (!cycleGreen.animating)
{
cycleGreen.green *= -1;
cycleGreen.animating = true;
}
if (!cycleRed.animating)
{
cycleRed.red *= -1;
cycleRed.animating = true;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
foreach (Sprite spr in shapes)
{
spr.Animate();
spr.Draw();
}
spriteBatch.DrawString(font, “Color Animation Demo”,
new Vector2(0, 0), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}
[/code]

The examples in this hour are combined into a single Visual Studio solution so that they can all share the same content (a single white box image). To run a specific project in a combined solution, right-click the project and use Debug, Start New Instance. Optionally, you can highlight any one of the projects, set it as the Active Project, and then press F5 to run it.

Color Bouncing

The code in the previous example can be improved upon with a new subclass of CycleColor that automatically “bounces” the color components within a specified range. The new subclass is called CycleColorBounce, as found in Listing 12.6. Since this class does not assume that the color component range will bounce between 0 and 255, there is a min and max property for each color component, defined as integers. Although a more complex data type like Vector4 would clean up the code a bit, I was going for clarity. Now, this Animation subclass does not ever end by setting animating to false, so this animation will continue forever unless an intervention takes place. Another possibility is to have it perform a single bounce forward and backward before ending. Then, if you want to perform the animation again, it can be restarted.

LISTING 12.6 Source Code to the CycleColorBounce Class

[code]
public class CycleColorBounce : CycleColor
{
public int rmin, rmax, gmin, gmax, bmin, bmax, amin, amax;
public CycleColorBounce(int red, int green, int blue, int alpha)
: base(red,green,blue,alpha)
{
rmin = gmin = bmin = amin = 0;
rmax = gmax = bmax = amax = 255;
}
public override Color ModifyColor(Color original)
{
Color modified = original;
if (animating)
{
int R = original.R + red;
int G = original.G + green;
int B = original.B + blue;
int A = original.A + alpha;
if (R < rmin)
{
R = rmin;
red *= -1;
}
else if (R > rmax)
{
R = rmax;
red *= -1;
}
if (G < gmin)
{
G = gmin;
green *= -1;
}
else if (G > gmax)
{
G = gmax;
green *= -1;
}
if (B < bmin)
{
B = bmin;
blue *= -1;
}
else if (B > bmax)
{
B = bmax;
blue *= -1;
}
if (A < amin)
{
A = amin;
alpha *= -1;
}
else if (A > amax)
{
A = amax;
alpha *= -1;
}
modified = new Color(R, G, B, A);
}
return modified;
}
}
[/code]

The example for color bouncing is very similar to the previous example, with only minor changes to the variable declarations and initialization of the objects. I opted to turn off rotation of the sprites just to make this example stand apart from the previous one a bit. The source code to the Color Bouncing program that demonstrates this class is found in Listing 12.7.

LISTING 12.7 Source Code to the Color Bouncing Program

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
SpriteFont font;
Texture2D shapeImage;
List<Sprite> shapes;
CycleColorBounce cycleRed, cycleGreen, cycleBlue;
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);
font = Content.Load<SpriteFont>(“WascoSans”);
shapeImage = Content.Load<Texture2D>(“box”);
Sprite redBox = new Sprite(Content, spriteBatch);
redBox.image = shapeImage;
redBox.position = new Vector2(200, 240);
redBox.origin = new Vector2(64, 64);
redBox.color = new Color(255, 0, 0);
Sprite greenBox = new Sprite(Content, spriteBatch);
greenBox.image = shapeImage;
greenBox.position = new Vector2(400, 240);
greenBox.origin = new Vector2(64, 64);
greenBox.color = new Color(0, 255, 0);
Sprite blueBox = new Sprite(Content, spriteBatch);
blueBox.image = shapeImage;
blueBox.position = new Vector2(600, 240);
blueBox.origin = new Vector2(64, 64);
blueBox.color = new Color(0, 0, 255);
cycleRed = new CycleColorBounce(-5, 0, 0, 0);
cycleRed.rmin = 100;
cycleRed.rmax = 200;
redBox.SetAnimation(cycleRed);
cycleGreen = new CycleColorBounce(0, -10, 0, 0);
cycleGreen.gmin = 150;
cycleGreen.gmax = 255;
greenBox.SetAnimation(cycleGreen);
cycleBlue = new CycleColorBounce(0, 0, -20, 0);
cycleBlue.bmin = 0;
cycleBlue.bmax = 255;
blueBox.SetAnimation(cycleBlue);
shapes = new List<Sprite>();
shapes.Add(redBox);
shapes.Add(greenBox);
shapes.Add(blueBox);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
foreach (Sprite spr in shapes)
{
spr.Rotate();
spr.Move();
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
foreach (Sprite spr in shapes)
{
spr.Animate();
spr.Draw();
}
spriteBatch.DrawString(font, “Color Bouncing Demo”,
new Vector2(0, 0), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}
[/code]

This example is included in the Color Animation Demo solution, called Color Bouncing, and is shown in Figure 12.3. Each of the three boxes performs color cycle bouncing at a different rate to demonstrate the versatility of the animation system.

All the color animation being demonstrated in this hour can be applied to sprites with actual game artwork rather than a white shape. These special effects are demonstrated on a simple white box only for illustration, to show what is possible!

Animating color components bouncing between minimum and maximum values.
FIGURE 12.3 Animating color components bouncing between minimum and maximum values.

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.

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]

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