U
14 / 22 · 9 min
FAQ

Jumping, and the ground check that makes it work

the short answer

How do I make the character jump, and stop it jumping in mid-air?

Jumping is one line; the hard part is knowing when the feet are on something. Check a small sphere just below the character against a Ground layer, and only allow the jump when that check returns true. Never test whether vertical velocity is zero — it is zero at the top of the arc too.

Making a character jump is one line. Making it jump only when it is standing on something is the part that takes an afternoon, and the reason is that Unity does not tell you what your feet are touching — you have to go and ask.

animated diagram

The sphere sits below the feet and answers every frame. The two windows beside it are what make the controls feel fair rather than strict.

The jump itself

The one line, for each of the two movement systems
// CharacterController: you own gravity, so you own the jump too.
verticalSpeed = Mathf.Sqrt(jumpHeight * -2f * gravity);

// Rigidbody: hand it an impulse and let physics do the arc.
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);

The ground check

The wrong way is to test whether vertical velocity is near zero. It is near zero at the top of every jump, so the character gets a second jump exactly at the apex — the classic floaty double jump nobody asked for.

  1. 1

    Make a Ground layer: Layers▸Add Layer, then put the floor and every platform on it.

  2. 2

    Create an empty child of the character, name it GroundCheck, and move it to just below the feet — a few centimetres, not at the feet exactly.

  3. 3

    Check a small sphere at that point against the Ground layer every frame. Standing on something means the sphere overlaps something.

Jump.cs — the whole thing
using UnityEngine;

[RequireComponent(typeof(CharacterController))]
public class Jump : MonoBehaviour
{
    public Transform groundCheck;
    public LayerMask groundLayer;
    public float checkRadius = 0.25f;
    public float jumpHeight = 2f;
    public float gravity = -20f;

    // A jump the player asked for a moment too early still counts.
    public float bufferTime = 0.12f;
    // So does one asked for a moment after walking off an edge.
    public float coyoteTime = 0.12f;

    CharacterController cc;
    float verticalSpeed, lastGrounded, lastPressed;

    void Awake() => cc = GetComponent<CharacterController>();

    void Update()
    {
        bool grounded = Physics.CheckSphere(
            groundCheck.position, checkRadius, groundLayer, QueryTriggerInteraction.Ignore);

        if (grounded) lastGrounded = Time.time;
        if (Input.GetButtonDown("Jump")) lastPressed = Time.time;

        if (grounded && verticalSpeed < 0f) verticalSpeed = -2f;

        if (Time.time - lastPressed < bufferTime && Time.time - lastGrounded < coyoteTime)
        {
            verticalSpeed = Mathf.Sqrt(jumpHeight * -2f * gravity);
            lastPressed = lastGrounded = -99f;   // spend both, so it fires once
        }

        verticalSpeed += gravity * Time.deltaTime;
        cc.Move(Vector3.up * verticalSpeed * Time.deltaTime);
    }

    // Draw the check in the Scene view, so you can see what it is testing.
    void OnDrawGizmosSelected()
    {
        if (groundCheck == null) return;
        Gizmos.color = Color.green;
        Gizmos.DrawWireSphere(groundCheck.position, checkRadius);
    }
}

The two timers are not optional polish

  • Coyote time: the player who presses Jump two frames after walking off a ledge meant to jump. Without it, the game feels like it is ignoring you.

  • Jump buffer: the player who presses Jump just before landing meant to jump on landing. Without it, the press is thrown away and they blame the controls.

  • Both are around 0.1 seconds. Neither is noticeable, and together they are most of the difference between controls that feel tight and controls that feel broken.

the lesson that builds this

Movement, jumping and landing on blocks

This post is a standalone recipe. In the course, the same thing is built as part of the one project that runs through all five tabs — Character & Combat, lesson 02.