Physics in platformer games


Movement in platformer games can be reduced to a few equations. Let's say that every object has a position and a velocity which are integrated each frame.
velocity = velocity + gravity*dt
position = position + velocity*dt
These equations work pretty good if you're in a vacuum. In a vacuum, gravity can accelerate a falling object forever and we definitely don't want mushrooms falling at the speed of light! One solution is to clamp the velocity of moving objects to a reasonable threshold.
local v = math.sqrt(velocity.x^2 + velocity.y^2)
if v > maxVelocity then
  local vs = maxVelocity/v
  velocity.x = velocity.x*vs
  velocity.y = velocity.y*vs


Damping is sometimes referred to as "friction" which is strange since it's actually more similar to air resistance. I suppose for overhead games it looks like friction, but I digress. Anyways, one simple equation used for damping is:
velocity = velocity / (1 + damping*dt)
Where "damping" coefficient is a value between 0 to infinity. The greater this coefficient the faster the objects will slow down. To be more specific, a value between 0 to 1 is considered "under-damping", 1 is considered "critical damping" and any value greater than 1 is "over-damping".


Implementing a good jumping mechanic can be difficult due to several factors. Typically, we want to let the player control the height of his jumps... after taking off. Although this is against the laws of physics, it makes the gameplay more controlled. So let's look at the trickery that makes platformer jumping look and feel "right".

We have to start with some assumptions. First, the player will be controlling his jumps using a single button. The longer he holds the button, the higher we want him to jump. This creates the illusion that pressing the button harder makes your character jump higher. A naive approach might be to continually apply an upward force while the player is holding the jump button. Unfortunately, this produces "floaty" jumps and won't feel right unless the character is sporting a jetpack.

Left: jetpack with thrust applied for different periods of time
Right: jumps with variable initial velocity

if pressedJumpKey then
  player.yv = initJumpVelocity
The physics of jumping is similar to that of launching a projectile. We set the initial velocity of the character and let gravity do its thing (ignoring the effects of air resistance). This brings us to the second assumption: regardless of how long the player holds the jump button he always takes off with the same vertical velocity. Knowing the character's maximum jump height we can deduce a number of other constants:
-- what is the gravity that would allow jumping to a given height?
g = (2*jumpHeight)/(timeToApex^2)

-- what is the initial jump velocity? 
initJumpVelocity = math.sqrt(2*g*jumpHeight)

-- how long does it take to reach the maximum height of a jump?
-- note: if "initJumpVelocity" is not a multiple of "g" the maximum height is reached between frames
timeToApex = initJumpVelocity/g
Thanks to the three equations above, you no longer have to set the gravity in your game to some magic value. Nor do you have to tweak and test the character's jump velocity by hand!

In the event that the player releases the jump button quickly, we have to fudge the laws of physics a little bit. The idea is to adjust the player's velocity mid-air so that he doesn't get up to his maximum jump height. Let's call this "jump termination".

First technique: velocity reset
The simplest solution is to set the player's vertical velocity to zero as soon as he releases the jump button:

if releasedJumpKey then
  -- is the player ascending?
  if player.yv > 0 then
    player.yv = 0
This approach is fine and produces sharp jumps like in Super Meat Boy. Unfortunately, it looks a little awkward for small jumps as the player takes off at a high velocity and immediately drops.

Jumps with the same initial velocity, modified by setting the vertical velocity to zero.
Note how sharp the trajectory is for smaller jumps

Second technique: velocity clamp
An alternative approach is to introduce another constant: "minimum jump height". Every time the character jumps, he is guaranteed to reach at least this minimum allowed height. As a consequence, the player can terminate his jumps only for a given amount of time ("termTime") before reaching the apex of his jump. When the player releases the jump button, instead of resetting the vertical velocity to zero, we clamp it to a predefined termination velocity ("termVelocity").

if releasedJumpKey then
  -- is the player ascending fast enough to allow jump termination?
  if player.yv > termVelocity then
    player.yv = termVelocity
This approach make jumps feel like they have momentum. When the player releases the button, he continues to ascend for a little bit before gravity kicks in. The result is pretty good as long as the minimum allowed jump height is not too low. This is how jump termination would look when the minimum jump height is 1/5:

Jumps with the same initial velocity, modified by clamping the vertical velocity.
Note that the trajectory is still not perfectly smooth for medium height jumps

We can deduce the termination velocity ("termVelocity") and time ("termTime") as follows:
-- what is the velocity required to terminate a jump?
-- note: only works when "g" is negative
termVelocity = math.sqrt(initJumpVelocity^2 + 2*g*(jumpHeight - minJumpHeight))

-- how much time is available until a jump can no longer be terminated?
-- note: "minJumpHeight" must be greater than 0
termTime = timeToApex - (2*(jumpHeight - minJumpHeight)/(initJumpVelocity + termVelocity))

Third technique: gravity scale
The third technique I would like to discuss comes from the Box2D physics library by Erin Catto. Basically, we introduce another variable called "gravity scale". Each object in the game could have a "gravity scale" property (it's default value being 1). Setting the "gravity scale" of an object to -1 would invert its gravity.

obj.velocity = obj.velocity + obj.gravityScale*gravity*dt
obj.position = obj.position + obj.velocity*dt
So "gravity scale" is pretty useful if you want to have your character walk on the ceiling. In addition, when we set the "gravity scale" to 2, our character becomes twice as heavier! Let's see how this could be used to affect jumping.
if pressedJumpKey then
  jumpTime = 0
  player.yv = initJumpVelocity
We modify the "gravity scale" once, when the player releases the jump button while in the air.
if releasedJumpKey then
  if player.yv > 0 then
    player.gravityScale = timeToApex/jumpTime
Also, we need to track the elapsed jump time during each frame. The "gravity scale" is reset back to 1 after reaching the jump apex.
if jumpTime then
  jumpTime = jumpTime + dt
  -- reached jump apex?
  if jumpTime >= timeToApex then
    jumpTime = nil
    player.gravityScale = 1
The results don't look too bad for medium and high jumps. Note that smaller jump are steep and short in duration.

Jumps affected by modifying the gravity scale where: gravityScale = timeToApex/jumpTime

Platformer physics 101 and the 3 fundamental equations of platformers by Zachary Burke
Controlling jump height by Micha
Box2D by Erin Catto