Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / WPF

420 Frames Per Second

Rate me:
Please Sign up or sign in to vote.
4.91/5 (31 votes)
10 May 2013CPOL8 min read 53.5K   2.7K   56   16
This article presents a class to manage enumerator based animations in WPF that can deal with different framerated animations independent of the hardware framerate

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 Invokes 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

Image 1

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 AnimationInfos. 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:

C#
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:

C#
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 fors 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions

 
QuestionCan you help in same task in WPF? Pin
Member 119339726-Apr-16 21:04
Member 119339726-Apr-16 21:04 
AnswerRe: Can you help in same task in WPF? Pin
Paulo Zemek7-Apr-16 0:40
mvaPaulo Zemek7-Apr-16 0:40 
GeneralMy vote of 5 Pin
Prasad Khandekar29-Mar-13 19:12
professionalPrasad Khandekar29-Mar-13 19:12 
GeneralRe: My vote of 5 Pin
Paulo Zemek30-Mar-13 2:43
mvaPaulo Zemek30-Mar-13 2:43 
GeneralMy vote of 5 Pin
jfriedman29-Mar-13 17:29
jfriedman29-Mar-13 17:29 
GeneralRe: My vote of 5 Pin
Paulo Zemek29-Mar-13 19:03
mvaPaulo Zemek29-Mar-13 19:03 
GeneralMy vote of 6 Pin
abdurahman ibn hattab2-May-12 17:05
abdurahman ibn hattab2-May-12 17:05 
GeneralRe: My vote of 6 Pin
Paulo Zemek3-May-12 2:28
mvaPaulo Zemek3-May-12 2:28 
GeneralMy vote of 5 Pin
jfriedman28-Apr-12 2:59
jfriedman28-Apr-12 2:59 
GeneralRe: My vote of 5 Pin
Paulo Zemek28-Apr-12 4:53
mvaPaulo Zemek28-Apr-12 4:53 
GeneralMy vote of 5 Pin
SlingBlade24-Apr-12 19:06
SlingBlade24-Apr-12 19:06 
GeneralRe: My vote of 5 Pin
Paulo Zemek25-Apr-12 3:38
mvaPaulo Zemek25-Apr-12 3:38 
GeneralMy vote of 5 Pin
Reza Ahmadi21-Apr-12 5:32
Reza Ahmadi21-Apr-12 5:32 
GeneralRe: My vote of 5 Pin
Paulo Zemek21-Apr-12 5:38
mvaPaulo Zemek21-Apr-12 5:38 
GeneralMy vote of 5 Pin
Patrick Harris20-Apr-12 12:48
Patrick Harris20-Apr-12 12:48 
GeneralRe: My vote of 5 Pin
Paulo Zemek20-Apr-12 12:59
mvaPaulo Zemek20-Apr-12 12:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.