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();
}
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.
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!