Understand, use, and modify our double-jump platformer code.

**You can download this tutorial and all the project files by getting Chipmunk2D for Unity on our store and download page.**

Tweaking jump behavior is the core mechanic of a platformer. And it's pretty important in a lot of other games too. The platformer example includes configurable jump height, speed, air control, number of jumps, and more. Each feature and the corresponding code is explained here.

The first step in determining if the player is able to jump is checking if they are grounded. This simplified grounding check determines if the player is grounded, finds the best ground, and also checks if you are 'well-grounded'. A well-grounded character is standing on a flat enough slope that they will not slip down the edge.

// Check each object we collide with to see if we're standing on something.

body.EachArbiter(delegate(ChipmunkArbiter arbiter){

ChipmunkShape player, ground; arbiter.GetShapes(out player, out ground);

Vector2 n = -arbiter.GetNormal(0);

// Find the best (most upright) normal of something you are standing on.

if(n.y > groundNormal.y){

_grounded = true;

groundNormal = n;

groundBody = ground.body;

}

});

// To be well grounded, the slope you are standing on needs to less than the amount of friction

wellGrounded = (grounded && Mathf.Abs(groundNormal.x/groundNormal.y) < shape.friction);

body.EachArbiter(delegate(ChipmunkArbiter arbiter){

ChipmunkShape player, ground; arbiter.GetShapes(out player, out ground);

Vector2 n = -arbiter.GetNormal(0);

// Find the best (most upright) normal of something you are standing on.

if(n.y > groundNormal.y){

_grounded = true;

groundNormal = n;

groundBody = ground.body;

}

});

// To be well grounded, the slope you are standing on needs to less than the amount of friction

wellGrounded = (grounded && Mathf.Abs(groundNormal.x/groundNormal.y) < shape.friction);

This code is added to the FixedUpdate loop, and now you can read grounded to see if you are touching the ground. Note that wellGrounded dynamically takes into account the friction of whatever the player is standing on. So it will work precisely on dirt or ice without any fiddling.

The basics behind applying a jump are pretty simple:

Vector2 v = body.velocity;

// If the jump key was just pressed this frame, jump!

if(Input.GetButtonDown(jumpButton) && wellGrounded){

float jump_v = Mathf.Sqrt(-2f*jumpHeight*Chipmunk.gravity.y);

// Apply the jump to the velocity.

v.y = v.y + jump_v;

}

body.velocity = v;

// If the jump key was just pressed this frame, jump!

if(Input.GetButtonDown(jumpButton) && wellGrounded){

float jump_v = Mathf.Sqrt(-2f*jumpHeight*Chipmunk.gravity.y);

// Apply the jump to the velocity.

v.y = v.y + jump_v;

}

body.velocity = v;

That's it for a basic jump using Chipmunk, so let's adapt what we have for double jump (and beyond). Start by updating the grounding calculation, to reset the number of air jumps each time you touch the ground.

// To be well grounded, the slope you are standing on needs to less than the amount of friction

wellGrounded = (grounded && Mathf.Abs(groundNormal.x/groundNormal.y) < shape.friction);

if(wellGrounded){

remainingAirJumps = maxAirJumps;

}

wellGrounded = (grounded && Mathf.Abs(groundNormal.x/groundNormal.y) < shape.friction);

if(wellGrounded){

remainingAirJumps = maxAirJumps;

}

Updating the jump function is pretty straightforward. The primary change here is checking if the player has remaining air jumps- if they are, we use the same jumping logic to make sure the air jump feels consistent with the original jump.

We've also got a small additional change for air jumps. Not only is the remaining air jump counter decremented, but we apply a horizontal velocity change as well. This is a good example of the the type of small tweaks that can make a big difference to the feel of a platformer. Adding additional forward velocity helps with the reach of jumps and gives a little air control.

if(Input.GetButtonDown(jumpButton) && (wellGrounded || remainingAirJumps > 0)){

float jump_v = Mathf.Sqrt(-2f*jumpHeight*Chipmunk.gravity.y);

remainingBoost = jumpBoostHeight/jump_v;

// Apply the jump to the velocity.

v.y = recentGroundVelocity.y + jump_v;

// Check if it was an air jump.

if(!wellGrounded) {

remainingAirJumps--;

// Force the jump direction for air jumps.

// Otherwise difficult to air jump to a block directly over you.

v.x = directionInput*walkSpeed;

}

}

float jump_v = Mathf.Sqrt(-2f*jumpHeight*Chipmunk.gravity.y);

remainingBoost = jumpBoostHeight/jump_v;

// Apply the jump to the velocity.

v.y = recentGroundVelocity.y + jump_v;

// Check if it was an air jump.

if(!wellGrounded) {

remainingAirJumps--;

// Force the jump direction for air jumps.

// Otherwise difficult to air jump to a block directly over you.

v.x = directionInput*walkSpeed;

}

}

In addition to those basics, there are a few more features we added to our character controller example.

Jump boost is additional force that is added to the jump as the player hold the jump key down. The longer you hold the button, the higher the jump. We track the additional boost height and both a maximum boost duration. Adjusting the jump boost can make your jumps feel more floaty.

Jump leniency gives you a little bit of extra time to start a jump after you've stepped off the edge of a platform. If you've ever just barely missed a jump after running off the edge of the cliff, you've wished for more jump leniency! A few tenths of a second can really reduce the frustration of players.

Jump leniency gives you a little bit of extra time to start a jump after you've stepped off the edge of a platform. If you've ever just barely missed a jump after running off the edge of the cliff, you've wished for more jump leniency! A few tenths of a second can really reduce the frustration of players.

Ground penetration is calculated as how far the character's feet sink into the ground (along the groundNormal). In our example, we offset the drawing of the character by the ground penetration amount so the player never sees "spongy" collisions like you might see in other engines. (It's a pretty subtle effect in this case).

VirtualControls are implemented to give you on screen buttons on mobile devices. This is just a very simple basic way of abstracting out multiple control schemes, so this demo can be used on multiple platforms.