Are you interested in
creating games for Windows Phone 7 but don't have XNA experience?
Watch the recording of the recent intro webinar delivered by Peter Kuhn '
XNA for Windows Phone 7'.
This article is part 4 of the series "XNA for Silverlight developers".
This article is compatible with Windows Phone "Mango".
Latest update: Aug 1, 2011.
This is the second part about animation for 2D games in XNA. In the last part I've already talked about how I like to break down the topic into two different options and explained the concept of transforms. This time we'll take a deeper look at what most people naturally understand by the term "animation". In practice you'll be using a combination of both in most cases. You almost always have the need to move around objects on the screen using transforms, but you often also want to create a more appealing experience and sophisticated animations using the frame-based approach described in the following.
Theory
Once again, let's start with a very brief look at the theory. The human eye is not able to separate individual images when they are shown in rapid succession. I won't put my finger on any numbers; some sources talk about a threshold as low as ~16-18 frames per seconds, but the truth is this depends on a huge number of factors, including the individual perception, the kind of source material (more precisely the amount of motion in it), the involved technology used for presentation (like the type of display) and effects used for the rendering (motion blur etc.). Fact is that due to the physiology of the human eye individual images presented at higher speeds create the illusion of fluid motion.
The sample
This knowledge can be used to turn a sequence of rendered images you've created in a 2D or 3D animation software like this (note: the following image does not contain all the individual frames to keep the size small)...
...into an animation that looks like this:
Now some of you may wonder about this sample or even consider it a bad choice. After all, if you recall what we've learned about transforms in the last article, you might think that it would be more effective to create such an animation from three individual sprites and use a rotate transform for the wheels.
The truth is that I've selected an animation like this on purpose to demonstrate something very important. Look at the following image that contains three frames: the first frame from the above animation, a second frame taken from the middle of that animation as it is rendered above, and a third frame that shows the same, but this time how it would look like if we would just use rotations:
To make it even more obvious, here is a enlarged version of the left wheel only:
As you can see the rotated version also has rotated the shadows and shading. The rendered version on the other hand dynamically adjusts those for each frame. Once again this gives your animations a more natural look; even if your users cannot give a scientific explanation, they will perceive the rotation of a static image which is accompanied by this false lighting as odd and "somehow wrong". This is by the way significantly different from 3D scenes in games, where you actually set up your light sources and your game objects are shaded and maybe even shadowed properly and dynamically in response to their position and rotation relative to the light. That of course is not possible with 2D image content, which is why you only can pre-render the different views with correct lighting. So unless you don't work with shading or shadows in your sprites at all, it's usually better to use this approach, even for rotations which are usually the most costly in terms of required frames (the animated gif you can see above uses 40 individual frames).
Note: In a later part of this series we will learn other reasons why developers try to avoid rotation (and scaling). For example when it comes to handle collisions especially of complex and large sprites accurately, rotations create an additional level of complexity. Working without them makes this simpler and more performant.
In summary, it can be stated that: a) scaling doesn't work very well at least for large scale factors because sprites are based on bitmaps in XNA, and b) rotation often results in wrong lighting. This is why a combination of translate transforms and the frame-based approach we're about to learn, with no or only little use of other transforms is a very common scenario in 2D game development. Often only special elements (like particles or projectiles) get the extra degree of freedom with rotations and/or scaling.
On to the code
We have talked about "frames per second" in the last paragraphs, but the more interesting value to game developers is the inverse value calculated as 1/fps. This is the time a single frame should be visible on the screen. If you have an animation that should run at 10 fps, then you need to present each frame 1/10 = 0.1 seconds to achieve this frame rate. I use "fps" in normal conversations all the time because it's much easier for people to visualize this number; however, in code you'll base your calculations most likely on the frame time instead.
Silverlight
When people want to achieve an animation like this in Silverlight, their first reflex usually is to use a (dispatcher) timer. So the setup would be something like an image control on your page, a set of images added to your project, and in the timer tick event you'd swap the image source in some sort of way – this even works nicely with data binding.
<Grid x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<Image x:Name="AnimatedImage"
Stretch="None"
Source="{Binding ImageUri}"></Image>
</Grid>
public MainPage()
{
InitializeComponent();
DataContext = this;
_timer = new DispatcherTimer();
_timer.Tick += new EventHandler(Timer_Tick);
_timer.Interval = TimeSpan.FromSeconds(0.05); // =20 fps
_timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
// increase the frame index
// and wrap around if necessary
_currentFrameIndex++;
if (_currentFrameIndex > 39)
{
_currentFrameIndex = 0;
}
// build the new uri
string uri = string.Format("Images/Wagon{0}.png", (_currentFrameIndex + 1).ToString("0000"));
// update the uri property.
// the image control will pick this up through data binding
ImageUri = new Uri(uri, UriKind.Relative);
}
One problem with timers however is that they're not very accurate and reliable. They only guarantee that the tick event will not be raised before the set interval. Due to their nature other work that happens in the UI interferes with those timers and hence the result is that in practice they're always slow and produce an unstable tick interval. In the above code sample, the "Interval" property was set to 0.05, resulting in a frame rate of 20 fps. During some tests though I usually ended up having an actual fps of 17-19 even when nothing else was interfering.
A much better approach therefore is to use the Rendering event of the CompositionTarget class. This actually allows pretty accurate animations in Silverlight too. All of the following logic in the XNA code can be used with this approach, because the concept is similar to using the Update method we know from the game class in XNA. If you are interested in this, I encourage you to download the source code at the end of the article. It contains not only the XNA source code, but also two Silverlight projects with detailed comments in them. One of them shows the usage of the rendering event with some additional tricks similar to the ones below to create accurate animations.
XNA
To give the code a bit more structure, we'll create a separate class for the animation logic. This class has properties for the most important things: FPS and frame time, the source frames and the current frame.
private int _currentIndex = -1;
private double _elapsedSeconds = 0;
/// <summary>
/// Gets or sets the targeted frames per second.
/// </summary>
public double Fps
{
get;
set;
}
/// <summary>
/// Gets the frame time.
/// </summary>
public double FrameTime
{
get
{
return 1.0 / Fps;
}
}
/// <summary>
/// Gets the list of frames.
/// </summary>
public IList<Texture2D> Frames
{
get;
private set;
}
/// <summary>
/// Gets the current frame of the animation.
/// </summary>
public Texture2D CurrentFrame
{
get
{
if (_currentIndex > -1 && _currentIndex < Frames.Count)
{
return Frames[_currentIndex];
}
else
{
return null;
}
}
}
The frame time is calculated dynamically based on the target FPS, and the current frame is determined by the current frame index. The most interesting part of this class is the update logic, which we'll closely inspect now:
/// <summary>
/// Updates the animation depending on the elapsed time.
/// </summary>
/// <param name="gameTime">The game time information.</param>
public void Update(GameTime gameTime)
{
// sum the elapsed time
_elapsedSeconds += gameTime.ElapsedGameTime.TotalSeconds;
// if the sum of elapsed seconds exceeds the frame time
// we need to switch to the next frame
if (_elapsedSeconds >= FrameTime)
{
_currentIndex++;
// catch up if we are behind
_elapsedSeconds = _elapsedSeconds - FrameTime;
}
// if we've reached the end of the animation,
// start over
if (_currentIndex >= Frames.Count)
{
// start over
_currentIndex = 0;
}
}
Each time the Update method is called, we will add the elapsed total seconds since the last call to our elapsed seconds tracking field. As soon as this sum is equal or greater than the target frame time, we need to advance to the next frame in our frame list. We also make sure that we "wrap around" and loop the animation when it has reached the end.
An interesting and important detail is the line that resets the elapsed seconds counter when the current frame index was increased. Instead of setting it to zero, this code tries to accommodate for the deviation from the theoretical frame time we are aiming at. This deviation depends on the desired animation FPS and the target FPS of your game project. If you recall Part 1 of this series you may remember that in the game class, you can set a property named "TargetElapsedTime" that is by default set to a value that results in 30 FPS. We've learned that XNA tries to adhere to this setting as accurately as possible, which means that the "gameTime" parameter of the update function usually will very closely or exactly report 0.0333 seconds in this case. Now imagine you set the desired FPS of your animation to 20, which equals a frame time of 0.05. In the first call to the update method the frame index will not be increased (0.0333 >= 0.05 is false), but in the second call the condition will evaluate to true (0.0666 >= 0.05 is true). If you simply reset the elapsed seconds counter to zero, you will de facto ignore the 0.0666 - 0.05 = 0.0166 difference. Since this will happen equally every time the frame index is increased your actual frame rate will be reduced from 20 to 15 (= average frame time will be 0.0666 seconds)!
The above code tries to correct that by initializing the elapsed second counter with the difference of the targeted and actual frame time instead of zero as basis for the next iteration. The drawback of this method is that it introduces jitter – the first frame will be switched after 0.0666 seconds, the second one after 0.0333 etc. In the long run this will result in the desired average frame rate of 20 FPS (= average frame time of 0.05 seconds), but the individual frames are not all visible the same time. For animations with a high frame rate this should be negligible though. Another solution to the problem would be to optimize the ratio of your game's target FPS and the animation FPS to minimize the deviation.
That's almost all the animation class has. I've added a Start method that resets the elapsed seconds counter and sets the frame index to zero. You can probably think of a lot of useful additional features the class could be extended by, like making the loop behavior configurable or add properties to let the animation run backwards, to pause and resume it etc. For now, the class is sufficient for our small demo project though.
To use the animation class, we simply add all the individual images of the animation to the content project of our XNA game and then set up the animation in the "LoadContent" method of the game.
// create animation
_animation = new Animation();
_animation.Fps = 20.0;
// load all frames for the animation
for (int i = 0; i < 40; i++)
{
// build the asset name and load it
var frameName = string.Format("Wagon{0}", (i + 1).ToString("0000"));
var frame = Content.Load<Texture2D>(frameName);
// add the frame to the animation
_animation.Frames.Add(frame);
}
// start animation
_animation.Start();
This is pretty straight forward. We set the desired frame rate and add all the loaded frames to the animation (the frames are named "Wagon00nn" where "nn" is a number between 1 and 40). When everything is done, we start the animation.
In the "Update" method of the game, we simply update the animation and pass through the game time object the game class receives itself, and in the "Draw" method we use the current frame property of the animation to output the frame that should be presented.
// in the "Update" method:
_animation.Update(gameTime);
// [...]
// and in the "Draw" method:
// (_position simply centers the animation on the screen)
_spriteBatch.Begin();
_spriteBatch.Draw(_animation.CurrentFrame, _position, Color.White);
_spriteBatch.End();
And that's basically it. When you run the game project on your phone or in the emulator, you'll see the same animation we've seen in this article as animated gif above:
Summary
This article demonstrates how easy it is to use a sequence of images to create nice looking frame-based animations in both XNA and Silverlight, and completes the animation topic. In the next part we will finally start to learn about the different options for user input on the phone so we can make more use of what we've learned and animate objects depending on user input. As always you can download the full source code here. The zip file contains both Silverlight and XNA projects you can use to experiment. The projects also come with some additional smaller features like computations of the actual animation frame rate you can use to analyze the efficiency and accuracy of the different methods.
Download source code
If you have any feedback on the article or requests for future parts, please feel free to add your comments below or contact me directly.
About the author
Peter Kuhn aka "Mister Goodcat" has been working in the IT industry for more than ten years. After being a project lead and technical director for several years, developing database and device controlling applications in the field of medical software and bioinformatics as well as for knowledge management solutions, he founded his own business in 2010. Starting in 2001 he jumped onto the .NET wagon very early, and has also used Silverlight for business application development since version 2. Today he uses Silverlight, .NET and ASP.NET as his preferred development platforms and offers training and consulting around these technologies and general software design and development process questions.
He created his first game at the age of 11 on the Commodore 64 of his father and has finished several hobby and also commercial game projects since then. His last commercial project was the development of Painkiller:Resurrection, a game published in late 2009, which he supervised and contributed to as technical director in his free time. You can find his tech blog here: http://www.pitorque.de/MisterGoodcat/