As others have noted, the correct formula for deceleration due to dry friction is
velocity += constant * dt * -dir( velocity )
where dir(v)
returns a vector of unit length pointing in the same direction as v
.
(For one-dimensional movement, dir(v) = 1
if v > 0
and dir(v) = -1
if v < 0
.)
One way to calculate it is as
dir(v) = v / abs(v)
where abs(v)
gives the length or magnitude of the vector v
.
To keep objects from jittering after they come to a stop (and to avoid dividing by zero if we try to calculate the direction of a prefectly stationary object), we should also ensure that the change in velocity never exceeds the original magnitude of the velocity — that is, friction should never cause an object to start moving backwards. Putting all this together, a reasonable implementation of friction in a game is:
friction = constant * dt;
speed = abs( velocity );
if ( friction < speed ) {
delta_v = friction * -( velocity / speed );
} else {
delta_v = -velocity; // the object stops, or was stopped already
}
velocity += delta_v;
Note that this works even if the object is moving in more than one dimension, so that velocity
is a vector. (Of course, the actual code for doing this with vectors might look somewhat different, depending on what notation your language uses for vector arithmetic.)
You can add other velocity changes before or after this code; if you add them before, then small enough forces may be completely canceled by friction, which is actually realistic. In fact, for further realism, you may want to implement static friction by having the constant in the friction calculation vary depending on whether or not the velocity of the object was non-zero at the beginning of this timestep.
Also, if you want to be really accurate, you should take acceleration during the timestep into account when updating the object's position. That is, instead of just doing
velocity += delta_v;
position += velocity * dt;
you should do
velocity += delta_v;
position += ( velocity - delta_v / 2 ) * dt;
where velocity - delta_v / 2
is the average of the velocities before and after adding in delta_v
.
This latter approximation to Newton's laws of motion is actually exact as long as acceleration = delta_v / dt
is constant, and in any case is a better approximation than the former even for changing delta_v
. However, for games with a small and constant timestep dt
, the difference is generally not noticeable, at least without side-by-side comparison. The main advantage of the more accurate form is that it makes object trajectories less sensitive to changes in dt
.
I should also point out that (dry) friction is not the only force that can slow down moving objects. For example, objects moving through water or air experience drag, which follows a formula that generally looks something like
delta_v = ( a * speed + b ) * -velocity * dt
where a
and b
are constants that depend on a lot of things such as the density and viscosity of the fluid and the mass, size and shape of the object moving through it. (See the Wikipedia link above for details; a
above corresponds to Newton drag, while b
corresponds to Stokes drag.)
For macroscopic objects moving through fairly inviscid fluids like water or air, b
should be very small or even zero, whereas a viscous fluid like lava or molasses calls for a higher b
. For game purposes, just play around with the values until you get the effect you want.
Note that, unlike with dry friction, drag forces never bring a moving object to a complete stop, so we generally don't need to worry about accidental direction reversals. The exception to this is if (a * speed + b) * dt > 1
, which can happen if dt
is too large or if the object somehow acquires an unusually high velocity; if that might happen, the solution is either to dynamically adjust dt
to be smaller for fast-moving objects or to use a higher-order motion integrator (which really goes beyond the scope of this post).