Framerate independent games are games that run at the same speed, no matter the framerate. For example, a game might run at 30 FPS (Frames Per Second) on a slow computer, and 60 FPS on a fast one. A framerate independent game progresses at the same speed on both computers (objects appear to move at the same speed). On the other hand, a framerate dependent game progresses at half the speed on the slow computer, in a sort of slow-motion effect.
Making framerate independent games is important to make sure your game is enjoyable and playable for everyone, no matter what kind of computer they have. Games which slow down when the framerate dips can severely affect gameplay, making players get frustrated and quit! Additionally some systems have displays with different refresh rates like 120 Hz, so framerate independence is important to make sure the game doesn't speed up and become impossibly fast on those devices.
This tutorial describes how you can make your game framerate independent. The same technique also enables time scaling, which allows for deliberate slow-motion effects and easy pausing.
The dt system expression
The key to framerate independence is the
dt system expression.
dt stands for delta-time. Delta means a change in a quantity, so delta-time means the change in time. It is the time, in seconds, since the last tick.
For example, at 100 fps
dt will be 0.01 (one hundredth of a second), and at 10 fps
dt will be 0.1 (one tenth of a second). In practice,
dt varies tick-by-tick, so it is unlikely to be exactly the same value for long periods of time.
Notice that if you add
dt to a variable every tick, it adds 1 every second, because the time between all the ticks over a period of 1 second must add up to 1! Here's an example showing just that.
How to use dt
Typically, framerate dependent motion is done with an event like this:
Every tick (once per frame), the object moves one pixel to the right. Notice that at 30 FPS this means 30 pixels per second, and at 60 FPS this means 60 pixels per second. Those are different speeds, depending on the framerate.
Remember from the example above that
dt always adds up to 1 every second. So if we change the event to the following:
...the object will move to the right at 60 pixels per second at any framerate. Since
dt adds up to 1 every second,
60 * dt adds to up to 60 every second. This means at both 30 FPS and 60 FPS our object moves to the right 60 pixels every second - the same speed, regardless of framerate.
Use dt everywhere
Any time you move an object at a steady speed, you need to use
dt in that way to achieve framerate independence. For example, Sprite's Move forward action takes a number of pixels to move the object forward. If you constantly move the object forwards, you could move it forward
60 * dt pixels to move it at 60 pixels per second at its current angle.
Behaviors already use dt
All of Construct's behaviors use
dt in their internal movement calculations. That means anything moved by behaviors like Platform, 8 Direction and Physics don't need any special treatment - they do this already!
A really cool feature in Construct is timescaling. This allows you to change the rate time passes at in the game, also known as the time scale. You can set the time scale with the system Set Time Scale action. A time scale of 1 means normal speed, 0.5 means half as fast, 2.0 means twice as fast etc. If you set your game's time scale to 0.1, it's going ten times slower but still smoothly - a nice slow-motion effect!
Timescaling works by changing the value returned by
dt. This means behaviors are affected, and any movement using
dt. If you don't use
dt in your movement calculations (like the first event above) the motion is not affected by the time scale! So to use time scaling, you simply have to use
dt properly in all movement.
You can set the time scale to 0. This stops all motion. It's an easy way to pause the game. Set it back to 1 and the game will resume.
You might notice you can still do things like shoot using the game controls. You can get around that by putting your main game events in a group, and activating/deactivating that group as you pause and unpause.
It's also a good way to test you have used
dt correctly. If you have used it correctly, setting the time scale to 0 will stop everything in the game. If you have not used it correctly, some objects might continue to move, even though the game is supposed to be paused! In that case you can check how those objects are moved, and make sure you are using
Other kinds of movement
It's important to realise that
dt must be used for all kinds of motion. This includes rotation and acceleration.
Similar to before, the following event rotates the piggy 1 degree every tick:
This is 30 degrees per second at 30 FPS, and 60 degrees per second at 60 FPS - again, different speeds for different framerates. Using
dt in the same way solves the problem again. This way the piggy rotates at 60 degrees per second regardless of the framerate:
Acceleration is also fairly straightforward. Usually this only applies when you are making a custom movement via events.
If you have a speed variable, your object will be moving at
Object.Speed * dt pixels per tick. Your object's speed variable therefore contains a speed in pixels per second.
Suppose you want to accelerate the object by 100 pixels per second over one second. You simply need to add
100 * dt to the object's speed variable every tick, and it will accelerate in a framerate independent way. In other words, you use
dt both to adjust the object's position, and to adjust the object's speed.
If lerping from two fixed positions, simply ensure the factor (
lerp(a, b, x)) increases using
dt like any other moving value. A more complicated case is when lerping from the last result of lerp, e.g.
Set X to
lerp(Self.X, TargetX, factor)
In this case the correct form is to use:
lerp(a, b, 1 - f ^ dt)
f is between 0 and 1, e.g. 0.5. Commonly
lerp(a, b, f * dt) is used instead, but this is not perfectly accurate and has some other pitfalls. The maths involved is explained in more detail in the blog post Using lerp with delta-time.
dt in the Every X seconds condition! An event like Every 1 second will run every 1 second regardless of the framerate, thus is already framerate independent. The condition measures time, not frames. If you make an event like Every 60 * dt seconds, you have inadvertently made the condition framerate dependent - the opposite of what you want to achieve! Such a condition will run every 6 seconds at 10 FPS (when dt = 0.1), or every 0.6 seconds at 100 FPS (when dt = 0.01); if you just use Every 6 seconds, it already runs every 6 seconds regardless of the framerate.
At very low framerates,
dt can become very large. For example, at 5 FPS,
dt is 0.2. An object moving at 500 pixels per second is therefore moving 100 pixels per tick. This can cause it to "teleport" through walls or miss collisions with other objects.
Games are usually unplayable at such low framerates, but it is even worse if they become unstable like that. To help the game stay reliable even at very low framerates, Construct does not let
dt get larger than 1/30 (about 0.033). In other words, below 30 FPS,
dt stays at 0.033. This does also mean below 30 FPS the game starts going in to a slow-motion effect (described earlier as one of the issues of framerate dependent games), however this is usually a better result than the "teleporting objects" problem.
If you want to set a different limit to 1/30, you can use the system action Set minimum framerate.
As mentioned with physics,
dt usually has small and random variations, usually due to imperfect timers in the computer. Like with physics, this can also cause slight random variations in your game. However, usually the effect is negligable and much less noticable than with physics. It's recommended that you always use
dt in your games unless exact precision is required (which is rare).
Object time scales
You can set a time scale for an individual object, via the system Set object time scale action. This allows you to have, for example, a game running in slow-motion with a time scale of 0.3, but still have the player acting at full speed with a time scale of 1. This is achieved by setting the time scale to 0.3, then using Set object time scale to set the player's time scale. The system expression
dt is only affected by the game time scale. Objects have their own dt expression (e.g.
Player.dt) which you must use instead of the system
dt for all motion relating to that object. This way, there are now two values of dt: one for the game, and one for the player. Since they return different values, different parts of the game can progress at different rates.
In this example, to return the player back to game time, use the system Restore object time scale action.
It's important to design your game from the start using
dt. This improves the gameplay, ensuring the pace never slows down in particularly intense parts of the game. As an extra bonus, you can also use the time scaling feature, easily pause the game, and even control object's individual time scales.
Don't forget behaviors already use
dt. If you use behaviors to control all motion in your game, you don't have to worry about
dt at all! However, most games have some event-controlled motion, and it's important to remember how to make it framerate independent.