If you do v = v * f
on each game loop, then friction will apply more at 120Hz vs 60Hz — a common error caused by frame rate dependent physics.
This happens because friction is applied every frame, so higher frame rates result in more frequent applications, making objects slow down more quickly. For example, at 120Hz, velocity gets multiplied by f
twice as often as at 60Hz, which leads to different behavior depending on the frame rate.
To fix this, make friction time-dependent by converting it into an exponential decay based on delta time. If f
is the fraction of velocity retained per second (e.g., f = 0.98
means 98% retained each second), apply it like this:
float decay = pow(friction_per_second, delta_time);
velocity *= decay;
The motivation for using a power function here is based on how delta time adds up over the course of a second. Suppose we're running at 120Hz. Then over one second, we will have applied this update 120 times with each frame's delta_time = 1/120
. So the velocity will be updated as:
v = v * f^dt_1 * f^dt_2 * ... * f^dt_120
Because multiplication of exponentiated constants with the same base is equivalent to raising the base to the sum of the exponents, and since:
dt_1 + dt_2 + ... + dt_120 ≈ 1
we get:
v = v * f^1 = v * f
So after one second, exactly one iteration of friction has been applied, regardless of the frame rate. This makes friction frame rate independent.
However, you might notice that everything feels much more "slippery" now. That’s because in the old configuration, friction was being applied 120 times per second (at 120Hz), while in the new configuration it’s effectively applied once per second. To match the old feel, you need to adjust your friction value.
If your old per-frame friction value was f_old
(e.g., 0.998), then over one second at 120Hz you were doing:
v = v * f_old^120
So to preserve the same amount of friction when switching to the new delta-time-based method, compute:
friction_per_second = pow(f_old, 120);
and then use:
velocity *= pow(friction_per_second, delta_time);
When to Use Linear vs. Exponential Decay
-
Use linear decay (i.e.,
value -= rate * delta_time
) when applying a force or rate of change that should remain constant per second, such as gravity or acceleration. -
Use exponential decay (i.e.,
value *= pow(factor, delta_time)
) when applying a percentage-based effect over time, such as friction, damping, or drag that continuously scales down a value.
Choosing the right type of decay ensures that your game's behavior stays consistent across different hardware and frame rates, while also preserving the feel of your physics.