velocity = velocity + gravity*dt position = position + velocity*dtThese 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 end
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".
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 endThe 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/gThanks 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!
Jump height | Gravity | Initial velocity | Time to apex |
---|---|---|---|
Here are a few example values, provided as a reference: |
|||
High jump (Olympics)=2.45 m Standing jump (Guinness record)=1.48 m Michael Jordan (Basketball)=1.22 m |
Jupiter's gravity=24.79 m/s2 Earth's gravity=9.807 m/s2 Mars' gravity=3.711 m/s2 Moon's gravity=1.62 m/s2 |
Escape velocity=11186 m/s Speed of sound=343 m/s Terminal velocity=54 m/s Baseball pitch=47 m/s Soccer kick=36 m/s |
Reaction (visual stimulus)=0.190 s Reaction (auditory)=0.160 s Reaction (touch)=0.150 s * Blinking=0.125 s |
elapsed = 0 wantsToJump = false function love.update(dt) elapsed = elapsed + dt wantsToJump = false if isGrounded then if lastJump and elapsed - lastJump < 0.2 then wantsToJump = true end end ... end lastJump = nil function love.keypressed(key, scancode, isrepeat) if key == "space" then lastJump = elapsed end endIt is also recommended to allow the player to jump for a split second after falling off ledges. The example below allows a 0.1 second grace period for jumping.
elapsed = 0 lastGrounded = 0 function love.update(dt) elapsed = elapsed + dt ... if isGrounded then lastGrounded = elapsed end if wantsToJump and elapsed - lastGrounded < 0.1 then -- jump! end endBoth of these improvements combined can make your game controls feel much more forgiving.
if releasedJumpKey then -- is the player ascending? if player.yv > 0 then player.yv = 0 end endThis 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.
if releasedJumpKey then -- is the player ascending fast enough to allow jump termination? if player.yv > termVelocity then player.yv = termVelocity end endThis 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:
-- 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))
obj.velocity = obj.velocity + obj.gravityScale*gravity*dt obj.position = obj.position + obj.velocity*dtGravity 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 heavy! Let's see how this could be used to affect jumping. We modify the "gravity scale" once, when the player releases the jump button while in the air. The "gravity scale" is reset back to 1 immediately upon landing.
if isGrounded then player.gravityScale = 1 elseif releasedJumpKey then player.gravityScale = 2 endThe results don't look too bad for medium and high jumps. Note that smaller jump are steep and short in duration.