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]

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.

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.

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

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();
}
{
rand = new Random();
spriteBatch = new SpriteBatch(GraphicsDevice);
//create object list
objects = new List<Sprite>();
//create shuttle sprite
Sprite shuttle = new Sprite(Content, spriteBatch);
shuttle.scale = 0.4f;
shuttle.position = new Vector2(0, 240);
shuttle.velocityLinear = new Vector2(4, 0);
Rectangle bounds = new Rectangle(-80, 0, 800 + 180, 480);
shuttle.zindex = 0.5f;
//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.position = new Vector2(30 + 60 * row, 30 + col * 60);
spr.size = new Vector2(60, 60);
spr.zindex = zindex;
}
}
}
protected override void Update(GameTime gameTime)
{
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]

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.

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();
}
{
rand = new Random();
spriteBatch = new SpriteBatch(GraphicsDevice);
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>();
}
protected override void Update(GameTime gameTime)
{
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.

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();
}
{
spriteBatch = new SpriteBatch(GraphicsDevice);
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>();
}
protected override void Update(GameTime gameTime)
{
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();
}
{
spriteBatch = new SpriteBatch(GraphicsDevice);
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>();
}
protected override void Update(GameTime gameTime)
{
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!