So you got into Web Based Animations or Games (using that HTML5 thingie) and chances are high that you’re relying on setInterval
, setTimeout
or even better – requestAnimationFrame to reflow and repaint your frames (fancy terms for rendering each animation frame). Precisely, you’re basing your animations or game mechanics on the frame rate. There’s nothing wrong with that, but let me show you a more sophisticated approach which can actually enhance all sorts of user experience – time based animations.
Frame based animations
I’m assuming you already know about frames. Every animation occurs over a set of frames per second creating the illusion of motion.
When I say frame based animations, what I basically mean is that for instance, you might be animating your objects based on frames, i.e., whenever a frame is rendered, the object’s position is changed by some value without taking anything else into consideration. By “anything else” I am mostly refering to the delay between each frames or simply the frame rate. For example, you might be moving an object by 5px
to the right in each frame. Take a look at this piece of code and output:
What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.
function move() { ctx.clearRect(0, 0, window_width, window_height); var c = circle; // Drawing circle and increment its x-position in each frame c.draw(); c.x += 5; // Put the circle back at starting point if it goes out // of the viewport if(c.x - c.radius > window_width) c.x = -c.radius; }
A circle moving 5 pixels to the right per frame regardless of the frame rate. So if your frame rate is 60, circle moves by 60×5 = 300 pixels. If your CPU is busy or you view the animation on a low end mobile where the FPS is 30 then the circle moves by 150pixels. For some crazy reason if the FPS is 1, the movement observed is 5px. I’m sure you’ve started to think of the issues by now.
Look how this animation plays on my 1GHz single core processor phone and my laptop.
Not cool! This issue causes our games/animations to work in slow motion on low end devices (or just low FPS) which can be a super frustrating experience. It’s worse on “reflex” based games as users will get more time to react leading to a lesser challenging game, hence lesser fun.
How do we avoid this ? Let’s try to comprehend a new concept where we animate based on time.
Time based animations
These are animations dependant on time. If we consider our previous animation, then with this concept we’ll be basically deciding our object’s next position based on the time elapsed since the last frame. So if we want to move the object by 300px in 1 second and the time elapsed since last frame is about 33.33ms (30 FPS – low end devices or busy CPU) then the change in position will occur by 30 pixels. While if the time elapsed is 16.66ms (60FPS ideally) then we’ll get our wanted change, i.e., 5 pixels. Hope this makes sense (demo with further explanation later).
So regardless of the FPS our animations are going to run at the same speed across all devices/configurations. While this technique might cause slight jerks (imagine skipping frames) in the animations on lower FPS, it’s still much better than having the game run in slow motion and wait for 2-3 seconds for the bullet to reach your enemy’s head!
Implementing Time based animations
Implementation is not that hard. This is how I converted the previous frame based animation to time based animation.
… and this is how the animation works (on same devices as before).
The animation in mobile is slightly jerky but the speed is same as the laptop which is more important, right?
Coming back to the coding, which is very simple. We essentially calculate how much time it takes to render one frame. It is denoted by delta
here.
var now, delta; var then = new Date().getTime(); function move() { now = new Date().getTime(); delta = now - then; // console.log(delta); ctx.clearRect(0, 0, window_width, window_height); var c = circle; c.draw(); c.x += calcSpeed(delta, 5); if(c.x - c.radius > window_width) c.x = -c.radius; then = now; }
So, we first get the current time at the start of the frame animation in now
variable and then copy it to the then
variable at the end of frame animation. Calculating the difference between these two gives us the time consumed between two consecutive frames. We then use this information to convert the 5
speed of the circle to time based. Here’s the function responsible for that.
var calcSpeed = function(del, speed) { return (speed * del) * (60 / 1000); }
Explanation: In this function, we are just multiplying the speed that we were using in the frame based animation and multiply with delta and frame rate we are aiming (60 in this case, which is converted into frames/ms by dividing 60 by 1000).
Now, your animations will run at the same speed in any device no matter what FPS are they getting.
Let’s see how this works. We wanted to increment our circle by 5px in each frame but since we are not interested in frames anymore, we changed this value based on the time. So now, there are 60 frames in a second which means we want to move our object 5 * 60 = 300
units per second. But, as the FPS varies with device, we multiply our result with the delta
(amount of time required to render each frame) which also varies with device. This will give us the exact speed to match our assumption (300 units per second) for that particular device.
Now as everything is calculated in ms
here, we divide our time by 1000 to get the increment value that we want in ms
on that device (300 per second or 0.3 per millisecond in this case). If we don’t divide the result by 1000, then the circle will be incremented by 300 units per millisecond! Pretty simple, right?
Conclusion
Hopefully by now you have noticed the benefits of basing the inner workings of your animations on time rather than consecutive frames. So start implementing this technique in your games right away!
Good stuff, Kushagra! HTML5 rocks 🙂
If frame rate is too low, which it will be on slow devices, then delta is very high
delta = now – then; //large difference between now and then.
So simulating your physics by high delta amount of time would cause jerky physics, tunneling etc. like ball going through the wall without colliding. So the simulation of the physics should be in smaller fixed sized timesteps multiple times, till it consumes delta amount of time.
Yes, tunneling and jerky physics will occur and that’s why I wrote another post on how to eliminate these problems. Check it out here. Thanks!
When a tab loose focus requestAnimationFrame is not excuted until you focus again the tab, that’s ok because it saves CPU/GPU, but when we are using time based animation this apperently doesn’t work.
Let’s say that your object x position is 100, it calculates correctly the next position in every frame, then you unfocus the tab the requestAnimationFrame pauses, then when you come back let’s say 3 min after the requestAnimationFrame is called and now your delta is too big and your objects move like if they were running all this time, did I make myself clear?
My question is: is there a way to prevent this for happening? how?
P.S. Great blog and articles about canvas gamedev.
Regards,
Isaac Zepeda
After writing this comment I thought that the solution is to set a max_delta value, if the calculated delta is greater than the max_delta then I set the delta = max_delta. Is this a correct solution?
Regards,
Isaac Zepeda
Hi Isaac,
I understand your problem and max_delta might be one of the solutions. But I think the most popular and preferred solution is to “fix” the timesteps (this is what I’ve been doing too and also plan to write a post on it).
In simplest terms, by “fixing” what I mean is initially you’ll have to fix your physics FPS for the game/animation, like say 40 FPS. So, based on the time deltas do not update physics (mechanics) more than 40 times every second. So if the delta is 5 seconds, you execute a for loop that iterates
(5*1000)/(1000/40)
(200) times and keeps on updating the physics, while the “rendering” method executes only once (which draws everything). This also means that the rendering method gets executed as many times as the supported FPS (by the system/browser).Now inside the for loop you can place a counter and restrict the iteration count to say 500 times or 1000 times. So the physics won’t be updated more than say 500 times which evaluates to a delta of 12.5 seconds if your fixed FPS is 40.
Hope that makes sense and helps!
Kind of, I would need to see your post, also I’m doing some research.
Thanks 🙂