Jump to content

  • Log In with Google      Sign In   
  • Create Account

We're offering banner ads on our site from just $5!

1. Details HERE. 2. GDNet+ Subscriptions HERE. 3. Ad upload HERE.


Like
5Likes
Dislike

3D Animation Techniques with XNA Game Studio 4.0

By Sean James | Published Feb 23 2012 02:33 PM in DirectX and XNA

frames position new timespan rotation vector3 mathhelper vector3(0 add(new
If you find this article contains errors or problems rendering it unreadable (missing images or files, mangled code, improper text formatting, etc) please contact the editor so corrections can be made. Thank you for helping us improve this resource

In this article, we will look at several ways to make the objects in our scene move. First, we will look at the animation of objects as a whole. We will do this through simple linear interpolation between start and end values, and through a more complex curve interpolation. We will also look at more complex animations through keyframed animation.

This article by Sean James, author of 3D Graphics with XNA Game Studio 4.0, covers:
  • Object animation
  • Keyframed animation
  • Curve interpolation
Object animation
We will first look at the animation of objects as a whole. The most common ways to animate an object are rotation and translation (movement). We will begin by creating a class that will interpolate a position and rotation value between two extremes over a given amount of time. We could also have it interpolate between two scaling values, but it is very uncommon for an object to change size in a smooth manner during gameplay, so we will leave it out for simplicity's sake.

The ObjectAnimation class has a number of parameters—starting and ending position and rotation values, a duration to interpolate during those values, and a Boolean indicating whether or not the animation should loop or just remain at the end value after the duration has passed:

public class ObjectAnimation
{
  Vector3 startPosition, endPosition, startRotation, endRotation;
  TimeSpan duration;
  bool loop;
}
We will also store the amount of time that has elapsed since the animation began, and the current position and rotation values:

TimeSpan elapsedTime = TimeSpan.FromSeconds(0);

public Vector3 Position { get; private set; }
public Vector3 Rotation { get; private set; }
The constructor will initialize these values:

public ObjectAnimation(Vector3 StartPosition, Vector3 EndPosition,
  Vector3 StartRotation, Vector3 EndRotation, TimeSpan Duration,
  bool Loop)
{
  this.startPosition = StartPosition;
  this.endPosition = EndPosition;
  this.startRotation = StartRotation;
  this.endRotation = EndRotation;
  this.duration = Duration;
  this.loop = Loop;
  Position = startPosition;
  Rotation = startRotation;
}
Finally, the Update() function takes the amount of time that has elapsed since the last update and updates the position and rotation values accordingly:

public void Update(TimeSpan Elapsed)
{
  // Update the time
  this.elapsedTime += Elapsed;

  // Determine how far along the duration value we are (0 to 1)
  float amt = (float)elapsedTime.TotalSeconds / (float)duration.
TotalSeconds;

  if (loop)
	 while (amt > 1) // Wrap the time if we are looping
		   amt -= 1;
  else // Clamp to the end value if we are not
	 amt = MathHelper.Clamp(amt, 0, 1);

  // Update the current position and rotation
  Position = Vector3.Lerp(startPosition, endPosition, amt);
  Rotation = Vector3.Lerp(startRotation, endRotation, amt);
}
As a simple example, we'll create an animation (in the Game1 class) that rotates our spaceship in a circle over a few seconds:


Posted Image

We'll also have it move the model up and down for demonstration's sake:

ObjectAnimation anim;
We initialize it in the constructor:

models.Add(new CModel(Content.Load("ship"),
  Vector3.Zero, Vector3.Zero, new Vector3(0.25f), GraphicsDevice));

anim = new ObjectAnimation(new Vector3(0, -150, 0),
	   new Vector3(0, 150, 0),
	   Vector3.Zero, new Vector3(0, -MathHelper.TwoPi, 0),
	   TimeSpan.FromSeconds(10), true);
We update it as follows:

anim.Update(gameTime.ElapsedGameTime);

models[0].Position = anim.Position;
models[0].Rotation = anim.Rotation;
Keyframed animation
Our ObjectAnimation class allows us to create simple linear animations, but we can't create anything more complex. For example, we can't make our spaceship move in a circle with this class. To achieve more complex animations, we will use what is called keyframed animation. In this method, we specify "key" frames where we want the object to be in a specific position and orientation. We then rely on the code to interpolate between those values to fill in the frames between the key frames.

The following screenshot shows our spaceship at the keyframed positions along a path, and the black line shows the path that would be taken by interpolating between keyframes:

Posted Image

Keyframed animation is useful because it is a fast way to create somewhat complex animations without having to animate each frame. For example, birds flying through the air, soldiers on patrol, or even a camera flying through a scene, can all be animated through keyframes. This is probably the easiest way to move the camera during a cutscene, for example. We represent a key frame with the ObjectAnimationFrame class. Like the previous class, it contains position and rotation values. It also, however, contains a time value, marking this frame's time offset from the beginning of the animation.

public class ObjectAnimationFrame
{
  public Vector3 Position { get; private set; }
  public Vector3 Rotation { get; private set; }
  public TimeSpan Time { get; private set; }

  public ObjectAnimationFrame(Vector3 Position, Vector3 Rotation,
   TimeSpan Time)
  {
	this.Position = Position;
	this.Rotation = Rotation;
	this.Time = Time;
  }
}
We can now create a new animation class that uses key frames:

public class KeyframedObjectAnimation
{
List frames = new List();
bool loop;
TimeSpan elapsedTime = TimeSpan.FromSeconds(0);

public Vector3 Position { get; private set; }
public Vector3 Rotation { get; private set; }

public KeyframedObjectAnimation(List Frames,
  bool Loop)
{
  this.frames = Frames;
   this.loop = Loop;
  Position = Frames[0].Position;
  Rotation = Frames[0].Rotation;
}
}
Finally, the Update() function figures out which frame we are on and interpolates between its values and the next frame's values, based on how far between them we are:

public void Update(TimeSpan Elapsed)
{
  // Update the time
  this.elapsedTime += Elapsed;

  TimeSpan totalTime = elapsedTime;
  TimeSpan end = frames[frames.Count - 1].Time;

  if (loop) // Loop around the total time if necessary
	while (totalTime > end)
	   totalTime -= end;
  else // Otherwise, clamp to the end values
  {
	Position = frames[frames.Count - 1].Position;
	Rotation = frames[frames.Count - 1].Rotation;
	return;
  }

  int i = 0;
  // Find the index of the current frame
  while(frames[i + 1].Time	  i++;
  // Find the time since the beginning of this frame
  totalTime -= frames[i].Time;

  // Find how far we are between the current and next frame (0 to 1)
  float amt = (float)((totalTime.TotalSeconds) /
	 (frames[i + 1].Time - frames[i].Time).TotalSeconds);

  // Interpolate position and rotation values between frames
  Position = Vector3.Lerp(frames[i].Position, frames[i + 1].Position,
   amt);
  Rotation = Vector3.Lerp(frames[i].Rotation, frames[i + 1].Rotation,
   amt);
}
For example, we can now create a new animation to move our spaceship in a square:

KeyframedObjectAnimation anim;
We set it up as follows:

List frames = new List();

frames.Add(new ObjectAnimationFrame(new Vector3(-1000, 100, -1000),
  new Vector3(0, MathHelper.ToRadians(-90), 0),
  TimeSpan.FromSeconds(0)));
frames.Add(new ObjectAnimationFrame(new Vector3(1000, 100, -1000),
  new Vector3(0, MathHelper.ToRadians(-90), 0),
  TimeSpan.FromSeconds(3)));
frames.Add(new ObjectAnimationFrame(new Vector3(1000, 100, -1000),
  new Vector3(0, MathHelper.ToRadians(-180), 0),
  TimeSpan.FromSeconds(6)));
frames.Add(new ObjectAnimationFrame(new Vector3(1000, 100, 1000),
  new Vector3(0, MathHelper.ToRadians(-180), 0),
  TimeSpan.FromSeconds(9)));
frames.Add(new ObjectAnimationFrame(new Vector3(1000, 100, 1000),
  new Vector3(0, MathHelper.ToRadians(-270), 0),
  TimeSpan.FromSeconds(12)));
frames.Add(new ObjectAnimationFrame(new Vector3(-1000, 100, 1000),
  new Vector3(0, MathHelper.ToRadians(-270), 0),
  TimeSpan.FromSeconds(15)));
frames.Add(new ObjectAnimationFrame(new Vector3(-1000, 100, 1000),
  new Vector3(0, MathHelper.ToRadians(-360), 0),
  TimeSpan.FromSeconds(18)));
frames.Add(new ObjectAnimationFrame(new Vector3(-1000, 100, -1000),
  new Vector3(0, MathHelper.ToRadians(-360), 0),
  TimeSpan.FromSeconds(21)));
frames.Add(new ObjectAnimationFrame(new Vector3(-1000, 100, -1000),
  new Vector3(0, MathHelper.ToRadians(-450), 0),
  TimeSpan.FromSeconds(24)));

anim = new KeyframedObjectAnimation(frames, true);

Posted Image

The Update code remains the same. Running the game, you will see the spaceship move from corner to corner of a box, turning towards the next corner at each stop.

Curve interpolation
We now have the ability to make animations with multiple key frames, which allows us to create more complex animations. However, we are still interpolating linearly between those key frames. This looks good for rotations, for example, but it would not look good for an object following a path, as the object would abruptly change direction after reaching a key frame in its animation. Instead, we want to be able to have our objects follow a smooth curve through the positions defined in the key frames. We will do this with what is called Catmull-Rom interpolation. This is a process that will create a curve through our key frame positions, allowing for much smoother object animation:

Posted Image

Let's modify the KeyframedObjectAnimation class to use Catmull-Rom interpolation for the position value. XNA has a built-in function to calculate an interpolated position between the second and third points in a set of four points using Catmull-rom interpolation. However, it works only in one dimension, so we'll need to create a function that will interpolate between a set of instances of Vector3:

Vector3 catmullRom3D(Vector3 v1, Vector3 v2, Vector3 v3,
Vector3 v4, float amt)
{
return new Vector3(
  MathHelper.CatmullRom(v1.X, v2.X, v3.X, v4.X, amt),
  MathHelper.CatmullRom(v1.Y, v2.Y, v3.Y, v4.Y, amt),
  MathHelper.CatmullRom(v1.Z, v2.Z, v3.Z, v4.Z, amt));
}
The amt argument specifies how far (0 to 1) between the second and third vectors the new position should be. We can now modify the position calculation to use this new function:

// Interpolate position and rotation values between frames
Position = catmullRom3D(frames[wrap(i - 1, frames.Count - 1)].
Position,
  frames[wrap(i, frames.Count - 1)].Position,
  frames[wrap(i + 1, frames.Count - 1)].Position,
  frames[wrap(i + 2, frames.Count - 1)].Position, amt);
The wrap() function wraps the value that it is given around a certain interval—in this case [0, frames.Count – 1]. This means that we will not have to worry about our indices going out of range when finding the last point, next point, and so on, but it does mean that this type of interpolation will work best with a closed curve—a circle, for example:

// Wraps the "value" argument around [0, max]
int wrap(int value, int max)
{
  while (value > max)
	value -= max;

  while (value	 value += max;
  return value;
}
We could now create the following keyframed animation with a curved path to demonstrate our new interpolation method:

List frames = new List();

frames.Add(new ObjectAnimationFrame(new Vector3(-500, 100, 1000),
  new Vector3(0, MathHelper.ToRadians(0), 0),
  TimeSpan.FromSeconds(0)));
frames.Add(new ObjectAnimationFrame(new Vector3(500, 100, 500),
  new Vector3(0, MathHelper.ToRadians(0), 0),
  TimeSpan.FromSeconds(3)));
frames.Add(new ObjectAnimationFrame(new Vector3(-500, 100, 0),
  new Vector3(0, MathHelper.ToRadians(0), 0),
  TimeSpan.FromSeconds(6)));
frames.Add(new ObjectAnimationFrame(new Vector3(500, 100, -500),
  new Vector3(0, MathHelper.ToRadians(0), 0),
  TimeSpan.FromSeconds(9)));
frames.Add(new ObjectAnimationFrame(new Vector3(-500, 100, -1000),
  new Vector3(0, MathHelper.ToRadians(180), 0),
  TimeSpan.FromSeconds(12)));
frames.Add(new ObjectAnimationFrame(new Vector3(-500, 100, 1000),
  new Vector3(0, MathHelper.ToRadians(180), 0),
  TimeSpan.FromSeconds(15)));
frames.Add(new ObjectAnimationFrame(new Vector3(-500, 100, 1000),
  new Vector3(0, MathHelper.ToRadians(360), 0),
  TimeSpan.FromSeconds(18)));

anim = new KeyframedObjectAnimation(frames, true);

Posted Image

Summary
In this article we have covered the following concepts:
  • Object animation
  • Keyframed animation
  • Curve interpolation





Comments
EndlessSporadic
May 08 2012 06:36 PM
What do the following two lines mean? They are incorrect syntax.

1) while(frames[i + 1].Time i++;
2) while (value value += max;
while( value )
{
// some code here
}

checks if the value is true (boolean) and executes the code between the brackets while this is the case.

It constantly does: check - execute - check - execute - check - execute -... until value turns false

I haven't implemented it yet, but is seems excellent!

 

Good Job!

I've implemented it successfully!

 

Great job! Again!


Note: Please offer only positive, constructive comments - we are looking to promote a positive atmosphere where collaboration is valued above all else.




PARTNERS