Note to new readers
This article presents a small class and a technique to create frame-based animations which you can still consider useful. But if you want a more complete solution, I recommend you to read the article Fluent and Imperative Animations[^] as it is more complete and supports both time-based and frame-based animations.
Background
In the article Writing a Multiplayer Game (in WPF) I presented enumerators (using yield return
) as an easy way of doing animations. Seeing how games like BubbleShot are popular I decided to create one myself. It is already more successful at home, as my mom never played the other game but is loving the actual one.
I kept the idea of using enumerators to make animations but this time the animation is not controlled by a server and the HighPrecisionTimer
I used for the other game is too processor intensive and does not give me a good animation at 60 frames per second. Also, as it runs in another thread Invoke
s are required, making things more complicated, so I decided to do it right and use the WPF component made to do frame-based animations.
CompositionTarget
This component is the heart of frame-based animations in WPF and Silverlight. It does not do much. It simply has a Rendering
event that will be triggered everytime a new frame must be processed.
For my first test it worked much better than the HighPrecisionTimer
, did not consume all the CPU and it was already 60 frames per second. So, it was done.
The Game and My Lazyness - 420 Frames Per Second
OK ... it was already 60 frames per second but that was not all. By the way I did my code, the animations were slow. Smooth but slow.
I decided to multiply the speed by 7. The animations then had the correct speed, but sometimes the bubbles simply "passed through" other bubbles, as they were jumping from position 1 to 8 and the collision only happened at positions 4 or 5.
Surely I could use better math and discover that there is a collision in the path, but I did a lazy job. Instead of multiplying speed by 7, I did process 7 frames at each real frame. Considering the game is running at 60 frames per second, that gave me a total of 420 frames per second, even if my monitor still only presents 60 (or less).
7 Frames Per Update Is Not the Same As 420 Frames Per Second
While the code processed 7 frames per update it was not really running at 420 frames per second.
First, it is not guaranteed that the CompositionTarget
will trigger the event 60 times per second. It can very well only trigger 30 frames per second. In this case the entire animation will run at half speed.
It is also possible that it is called more than 60 times a second (updates to the UI trigger the event, and I don't know if there are computer configurations that simply make it run more often). In those cases, the animation will simply run faster than it should. To be really 420 frames per second the game should compensate extra frames or missing ones.
Compensating
As I consider a fixed framerate to program my animations using enumerators, I should compensate slowdowns by calling update more times. If the animation should execute at each millisecond and the ellapsed time from the last execution was 15 milliseconds, then the animation should be updated 15 times. If the next Rendering
comes before the needed time, then not even a single update should be done.
For a game that really works at 60 frames per second this technique may loose an entire frame by a minimal slowdown. But, as I am using 420 frames per second instead of 60, a minimal slowdown may mean processing 8 frames instead of 7 before showing the animation. So, my lazyness is still giving me a really smooth animation.
Putting the Compensation into a class
As I said before, the CompositionTarget
has a simple Rendering
event.
But such event does not tell us how many frames per second are being processed or how many time passed since the last call.
Using the DateTime.Now
is not very precise and can be disastrous if the user changes the computer time while the game runs. So, I used a Stopwatch
to get the ellapsed time. I really thought that I could compile this class without problems in Silverlight, but I was surprised by the fact that Silverlight does not has a Stopwatch
class.
But Silverlight is not the focus now, so what I really did was put the compensation code in an animation manager class. Even if at the moment I only have one kind of animation that animates a single object, the class is prepared to deal with many animations with different framerates.
The CompositionTargetAnimationManager
class is built in the following way:
- It has a
Stopwatch
to measure the exact ellapsed time.
- It has a list of
AnimationInfo
s. Such info contains a reference to the animation itself, the last time it ran, the expected interval (frame-rate) and possible an action to call to invalidate the UI.
- The
AddAnimation
method builds such animation info, puts it into the list of animations and, if it is the first animation, starts the Stopwatch
and registers to the CompositionTarget.Rendering
event.
- When an animation ends it is removed from the list and, if it was the last animation, the
Stopwatch
is stopped and the handler is unregistered from the CompositionTarget.Render
event.
- And when the
Rendering
event is called, each animation is run the necessary number of times to compensate the time that ellapsed. This means that an animation may not run (if the event fired too soon) or that it may run many times. There is only one exception: If more than one second passed, a single frame is processed. This is to avoid an application from hanging after you debug it (after all, if you spend 3 minutes debugging your application, it will be really bad to calculate the missed frames of those 3 minutes).
The Sample
The sample is the BubbleShot game I am still developing. It is full of bad practices at the moment and is incomplete. So you don't need to tell me that I should use MVVM, that there are missing things in the game, that it is buggy or the like. But I consider it is a good example that the CompositionTargetAnimationManager
class works.
Update - Part 1 - Trying to keep things synchronized
The first version of the code did not run the animations in order. If you have 2 animations and it was needed to skip 10 frames, it will run animation 2 ten times, then animation 1 ten times.
The new code will order the animations by their next execution time, even if they have different framerates. That is, if in a normal situation animation 1 will run twice then animation 2 will run, it will do exactly that when compensating.
This way if you need two animations, one that runs at the double speed of the other and they may collide, you can:
- Make the position of one increase by one and the position of the other increase by two, using the same framerate.
- Make both increase the position by only one but make one of the animations have the double framerate than the other.
I will really suggest that you measure time, as a framerate too high may kill the performance of your application or game, but it is still there as an option.
Update - Part 2 - Creating an animation
I said that I use enumerator based animations and if you see the code or my other article you may understand how it works. But it is better to keep things centralized, so, here it is:
To create an animation you should create an enumerator of bool
. C# allows you to use the yield return
when a method returns an IEnumerator<bool>
so you can make a simple animation with something like this:
private static void IEnumerator<bool> Animation()
{
for(int i=0; i<100; i++)
{
button.Width++;
yield return true;
}
for(int i=0; i<100; i++)
{
button.Width--;
yield return false;
}
}
And you run this animation doing:
CompositionTargetAnimationManager.AddAnimation(_Animation, TimeSpan.FromMilliseconds(100));
Some Notes
- The
yield return
tells that the frame ended. The value true
or false
is not important and is ignored.
- The animation runs only once. If you want to run the animation forever, both
for
s must be inside a loop, like a while(true)
.
- The animation is changing an UI property directly, so there is no need for a render delegate. To make animations faster when compensating lost frames it is better to change instance variables and use a render delegate to put those variables into the right properties (the render delegate must be given to the
AddAnimation
method as the last parameter).
- The
AddAnimation
uses TimeSpan
, not FPS, to measure time between frames. If you need FPS, simply divide 1000.0
milliseconds by the number of Frames-Per-Second that you want before calling TimeSpan.FromMilliseconds()
.
Version History
- 29 Mar 2013 - Corrected some formatting in the text;
- 24 Apr 2012 - Made animations run in the right order even when compensating and added a sample in the article;
- 20 Apr 2012 - Initial Version.