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]