Why You Should be Using Time-based Animation and How to Implement it
When I wrote my tutorial on how to create a Galaxian style HTML5 game, I wasn’t aware of the importance of using time-based animations. It wasn’t until sometime later that someone pointed out to me how my frame-based animation used in the tutorial was causing some problems.
I decided that I had better learn about this time-based animation thing and why it was important. I had seen it used in other games, but I hadn’t taken the time to understand it. Needless to say, I quickly remedied my ignorance and learned why time-based animation is the only way to go for making games. Hopefully, this article will help you understand why frame-based animation has problems and how time-based animation can remedy those problems.
Frame-based Animation
Frame-based animation is when you use the frame rate to update your animation. For example, if the browser gets 60 FPS, then a game would update 60 times every second. This means that if we were to update a square by 2 pixels every update, after 1 second the square will have moved 120 pixels.
For the most part, frame-based animation works decently well. The only problem is that you have to be able to guarantee that the FPS will never change, ever (which is completely unrealistic in any circumstance). If the frame rate changes, so too do the number of updates.
Going back to the square example, what if we only got 10 FPS instead of the 60 FPS we were expecting? Then the square will only have moved 20 pixels in 1 second instead of 120 pixels. This is where frame-based animation has problems since it’s entirely dependent on the frame rate.
To see this in action, take a look at the following example. Each box uses frame-based animation and has the exact same code; the only difference is the frame rate between them.
You can view this example on jsFiddle.
Notice that the black square (set at 60 FPS) updates well, but the red square (set at 10 FPS) updates agonizingly slow. This is because it only updates 10 times a second whereas the black square updates 60 times a second.
Thus, if we want to have consistent updates regardless of frame rate, we can’t use frame-based animation. Instead, we need to use a frame independent technique for our updates. This is where time-based animation comes into play.
Time-based Animation
Time-based animation is when you use the amount of time that has passed from the last frame to determine how much to update the current frame. This may sound complicated, but you are probably already familiar with this method if you have taken any sort of Physics class.
The equation to update the position of an entity, x = x0 + vt + at2
, is a time-based equation. It measures how much time has passed and calculates how far the entity should move based on that time. Time-based animation works in the same way.
To update the position of our squares, we will use the equation x = x + dx * dt
(instead of just x = x + dx
for a frame-based animation). Before we update our square, we first need to determine how much time has passed since the last update by using the following code:
last = new Date().getTime();
function animationLoop() {
now = new Date().getTime();
dt = now - last;
last = now;
update(dt);
draw();
}
Every frame we calculate the difference from the last time the frame was updated (last
) and the current time (now
). We then use this difference to update the position of our square. You can see the result of using time-based animation below.
You can view this example on jsFiddle.
As you can see, the black square (set at 60 FPS) and the blue square (set at 30 FPS) stay relatively in sync. However, the red square (set at 10 FPS) is completely off and sometimes just disappears entirely. If you watch long enough, you can see that the blue square eventually lags behind the black square more and more, or even very odd results like a square getting stuck on a wall.
So what’s going on? We’re using a time-based animation approach, so each square should be moving the exact same distance regardless of frame-rate. Even though this is true in principle, it doesn’t work so well in application.
There are two problems with our time-based animation approach. The first deals with how far the square moves each update and the second problem is what is called the “spiral of death”.
The first problem is a simple math problem. The black square updates 60 times a second, or in terms of dt
about once every 0.0166 seconds. However, the red square updates 10 times a second, or about once every 0.1 seconds. This means that the red square will travel about 6 times farther each update than the black square, or about 12 pixels (if we were updating the square 2 pixels every update). These 12 pixels update the red square past the wall, causing it to not bounce until afterwards.
The second problem is caused by our updates taking slightly longer than they should. You can read all about this problem in this article by Glenn Fiedler on fixing your timestep.
To fix our time-based code, we will be using Glenn Fiedler’s solution near the end of his article. I take no credit for what follows as I am mostly just summarizing what Glenn talks about in his article. If you want to get a better understanding of what follows, I suggest you go and read his article.
Time-based Animation Improved
To fix our time-based animation, we will implement a fixed dt update (or a constant dt passed to each update). This way each square will only ever go 2 pixels each update and we will eliminate problem one (updating too far).
However, each square still needs to update a different number of times based on the time since the last frame. So we still need to keep track of the time passed since the last frame, as well as calculate how many times to update each square.
Because we are only updating in fixed dt steps, each frame will have some time leftover that was less than our constant dt. This leftover time needs to be saved from one frame to the next so that we can accurately determine how much to update the next frame and eliminate problem two (spiral of death).
The updated code is as follows:
last = new Date().getTime();
dt = 1000 / 60; // constant dt step of 1 frame every 60 seconds
function animationLoop() {
now = new Date().getTime();
passed = now - last;
last = now;
accumulator += passed;
while (accumulator >= dt) {
update(dt);
accumulator -= dt;
}
draw();
}
You can see the result of the code below.
You can view this example on jsFiddle.
As you can see, each square stays in sync no matter what the frame rate is. The only difference between them is how smooth the animation is (higher FPS means smoother animation).
Conclusion
Frame-based animation can only work if you can ensure that your frame rate never changes, which you can’t. Instead, you should be using time-based animation to ensure that the experience stays the same no matter what the frame rate is. However, make sure that you are using a good time-based animation algorithm, otherwise your animations can get off and have unexpected results.