Basic Jump

A simple, fixed-height jump. Press button, go up, come down. The foundation of character movement across all perspectives.

Practice - what you do

The Feel

Basic jump is committed and consistent. Same height every time. The player knows exactly what they'll get.

Compare to Variable Jump: less expressive, but more predictable. Some videogames want that predictability.

Exposed Variables

Variable Type Default What it does
jumpForce float 12.0 Initial upward velocity
gravity float 40.0 Downward acceleration
fallGravityMultiplier float 1.5 Extra gravity when falling
groundCheckRadius float 0.2 Size of ground detection sphere/circle

Tuning Guide

For a floaty feel:

  • Lower gravity (20-30)
  • Lower fallGravityMultiplier (1.0-1.2)
  • Higher jumpForce

Reference: Kirby, early Mario

For a snappy feel:

  • Higher gravity (50-70)
  • Higher fallGravityMultiplier (2.0-3.0)
  • Moderate jumpForce

Reference: Super Meat Boy, modern precision platformers

For a weighty feel:

  • High gravity
  • Low jumpForce
  • Minimal fallGravityMultiplier (1.0)

Reference: Castlevania, simulation-leaning videogames

Perspective Scripts

Prerequisite: All scripts below require the InputReader to be set up in your scene. Complete the Input Setup first.

BasicJump2D.cs

Classic 2D platformer jump. Uses Rigidbody2D and Physics2D.OverlapCircle for ground detection.

Setup:

  1. Add Rigidbody2D to your player (Freeze Rotation Z)
  2. Add a Collider2D (BoxCollider2D or CapsuleCollider2D)
  3. Create an empty child GameObject at the feet, name it "GroundCheck"
  4. Add this script and configure in Inspector
BasicJump2D.cs
using UnityEngine;

/// <summary>
/// BASIC JUMP 2D - Fixed height jump for 2D platformers
/// VG101 Code Bank - Requires InputReader from Input Setup
/// </summary>
[RequireComponent(typeof(Rigidbody2D))]
public class BasicJump2D : MonoBehaviour
{
    [Header("Jump Feel")]
    [Tooltip("How hard we push off the ground. Higher = taller jump.")]
    [Range(5f, 25f)]
    [SerializeField] private float jumpForce = 12f;

    [Tooltip("How fast we fall. Higher = snappier, less floaty.")]
    [Range(20f, 80f)]
    [SerializeField] private float gravity = 40f;

    [Tooltip("Extra gravity when falling. Makes descent faster than ascent.")]
    [Range(1f, 4f)]
    [SerializeField] private float fallGravityMultiplier = 1.5f;

    [Header("Ground Detection")]
    [Tooltip("Empty GameObject at character's feet")]
    [SerializeField] private Transform groundCheckPoint;

    [Tooltip("Size of the ground check circle")]
    [Range(0.05f, 0.5f)]
    [SerializeField] private float groundCheckRadius = 0.2f;

    [Tooltip("Which layers count as ground")]
    [SerializeField] private LayerMask groundLayer;

    [Header("Debug")]
    [SerializeField] private bool showGroundCheck = true;

    private Rigidbody2D rb;
    private float verticalVelocity;
    private bool isGrounded;

    private void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        rb.gravityScale = 0f; // We handle gravity ourselves
    }

    private void OnEnable()
    {
        // Subscribe to InputReader jump event
        if (InputReader.Instance != null)
        {
            InputReader.Instance.OnJumpPressed += HandleJump;
        }
    }

    private void OnDisable()
    {
        if (InputReader.Instance != null)
        {
            InputReader.Instance.OnJumpPressed -= HandleJump;
        }
    }

    private void FixedUpdate()
    {
        CheckGrounded();
        ApplyGravity();
        ApplyVelocity();
    }

    private void CheckGrounded()
    {
        isGrounded = Physics2D.OverlapCircle(
            groundCheckPoint.position,
            groundCheckRadius,
            groundLayer
        );

        if (isGrounded && verticalVelocity < 0)
        {
            verticalVelocity = 0f;
        }
    }

    private void ApplyGravity()
    {
        if (!isGrounded)
        {
            float gravityThisFrame = gravity;
            if (verticalVelocity < 0)
            {
                gravityThisFrame *= fallGravityMultiplier;
            }
            verticalVelocity -= gravityThisFrame * Time.fixedDeltaTime;
        }
    }

    private void HandleJump()
    {
        if (isGrounded)
        {
            verticalVelocity = jumpForce;
        }
    }

    private void ApplyVelocity()
    {
        Vector2 velocity = rb.linearVelocity;
        velocity.y = verticalVelocity;
        rb.linearVelocity = velocity;
    }

    private void OnDrawGizmos()
    {
        if (showGroundCheck && groundCheckPoint != null)
        {
            Gizmos.color = isGrounded ? Color.green : Color.red;
            Gizmos.DrawWireSphere(groundCheckPoint.position, groundCheckRadius);
        }
    }
}

BasicJump3D.cs

Third-person 3D jump. Uses CharacterController for smooth movement integration. Works with camera-relative movement.

Setup:

  1. Add CharacterController to your player
  2. Add this script and configure in Inspector
  3. Set Ground Layer to your environment's layer
BasicJump3DThirdPerson.cs
using UnityEngine;

/// <summary>
/// BASIC JUMP 3D - Fixed height jump for third-person 3D games
/// VG101 Code Bank - Requires InputReader from Input Setup
/// Uses CharacterController for movement integration
/// </summary>
[RequireComponent(typeof(CharacterController))]
public class BasicJump3D : MonoBehaviour
{
    [Header("Jump Feel")]
    [Tooltip("How hard we push off the ground. Higher = taller jump.")]
    [Range(5f, 25f)]
    [SerializeField] private float jumpForce = 10f;

    [Tooltip("How fast we fall. Higher = snappier, less floaty.")]
    [Range(20f, 80f)]
    [SerializeField] private float gravity = 30f;

    [Tooltip("Extra gravity when falling. Makes descent faster than ascent.")]
    [Range(1f, 4f)]
    [SerializeField] private float fallGravityMultiplier = 2f;

    [Header("Ground Detection")]
    [Tooltip("Offset from center for ground check sphere")]
    [SerializeField] private Vector3 groundCheckOffset = new Vector3(0, -0.9f, 0);

    [Tooltip("Size of the ground check sphere")]
    [Range(0.1f, 0.5f)]
    [SerializeField] private float groundCheckRadius = 0.3f;

    [Tooltip("Which layers count as ground")]
    [SerializeField] private LayerMask groundLayer;

    [Header("Movement (Optional)")]
    [Tooltip("Movement speed on ground")]
    [SerializeField] private float moveSpeed = 8f;

    [Tooltip("How much control in the air (0-1)")]
    [Range(0f, 1f)]
    [SerializeField] private float airControl = 0.5f;

    [Header("Debug")]
    [SerializeField] private bool showGroundCheck = true;

    private CharacterController controller;
    private Vector3 velocity;
    private bool isGrounded;
    private Transform cameraTransform;

    private void Awake()
    {
        controller = GetComponent<CharacterController>();
        cameraTransform = Camera.main?.transform;
    }

    private void OnEnable()
    {
        if (InputReader.Instance != null)
        {
            InputReader.Instance.OnJumpPressed += HandleJump;
        }
    }

    private void OnDisable()
    {
        if (InputReader.Instance != null)
        {
            InputReader.Instance.OnJumpPressed -= HandleJump;
        }
    }

    private void Update()
    {
        CheckGrounded();
        HandleMovement();
        ApplyGravity();
        ApplyVelocity();
    }

    private void CheckGrounded()
    {
        Vector3 checkPosition = transform.position + groundCheckOffset;
        isGrounded = Physics.CheckSphere(checkPosition, groundCheckRadius, groundLayer);

        if (isGrounded && velocity.y < 0)
        {
            velocity.y = -2f; // Small downward force to stay grounded
        }
    }

    private void HandleMovement()
    {
        Vector2 input = InputReader.Instance != null ? InputReader.Instance.MoveInput : Vector2.zero;

        // Camera-relative movement
        Vector3 forward = cameraTransform != null ? cameraTransform.forward : transform.forward;
        Vector3 right = cameraTransform != null ? cameraTransform.right : transform.right;
        forward.y = 0f;
        right.y = 0f;
        forward.Normalize();
        right.Normalize();

        Vector3 moveDirection = (forward * input.y + right * input.x);

        float currentSpeed = moveSpeed;
        if (!isGrounded)
        {
            currentSpeed *= airControl;
        }

        velocity.x = moveDirection.x * currentSpeed;
        velocity.z = moveDirection.z * currentSpeed;

        // Rotate to face movement direction
        if (moveDirection.magnitude > 0.1f)
        {
            Quaternion targetRotation = Quaternion.LookRotation(moveDirection);
            transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, 10f * Time.deltaTime);
        }
    }

    private void ApplyGravity()
    {
        if (!isGrounded)
        {
            float gravityThisFrame = gravity;
            if (velocity.y < 0)
            {
                gravityThisFrame *= fallGravityMultiplier;
            }
            velocity.y -= gravityThisFrame * Time.deltaTime;
        }
    }

    private void HandleJump()
    {
        if (isGrounded)
        {
            velocity.y = jumpForce;
        }
    }

    private void ApplyVelocity()
    {
        controller.Move(velocity * Time.deltaTime);
    }

    private void OnDrawGizmos()
    {
        if (showGroundCheck)
        {
            Vector3 checkPosition = transform.position + groundCheckOffset;
            Gizmos.color = isGrounded ? Color.green : Color.red;
            Gizmos.DrawWireSphere(checkPosition, groundCheckRadius);
        }
    }
}

BasicJumpFPS.cs

First-person jump with classic FPS movement. Includes mouse look integration. The camera is the player's viewpoint.

Setup:

  1. Add CharacterController to your player
  2. Create a child Camera (or use Main Camera as child)
  3. Add this script to the player root
  4. Assign the Camera reference in Inspector
BasicJumpFPS.cs
using UnityEngine;

/// <summary>
/// BASIC JUMP FPS - First-person jump with integrated mouselook
/// VG101 Code Bank - Requires InputReader from Input Setup
/// Complete FPS controller with jumping
/// </summary>
[RequireComponent(typeof(CharacterController))]
public class BasicJumpFPS : MonoBehaviour
{
    [Header("Jump Feel")]
    [Tooltip("How hard we push off the ground.")]
    [Range(5f, 25f)]
    [SerializeField] private float jumpForce = 8f;

    [Tooltip("How fast we fall.")]
    [Range(20f, 80f)]
    [SerializeField] private float gravity = 25f;

    [Tooltip("Extra gravity when falling.")]
    [Range(1f, 4f)]
    [SerializeField] private float fallGravityMultiplier = 2f;

    [Header("Ground Detection")]
    [SerializeField] private Vector3 groundCheckOffset = new Vector3(0, -0.9f, 0);
    [SerializeField] private float groundCheckRadius = 0.3f;
    [SerializeField] private LayerMask groundLayer;

    [Header("Movement")]
    [Tooltip("Walking speed")]
    [SerializeField] private float moveSpeed = 6f;

    [Tooltip("Air control multiplier")]
    [Range(0f, 1f)]
    [SerializeField] private float airControl = 0.3f;

    [Header("Mouse Look")]
    [Tooltip("The camera to rotate for vertical look")]
    [SerializeField] private Transform playerCamera;

    [Tooltip("Mouse sensitivity for gamepad")]
    [SerializeField] private float gamepadLookSpeed = 150f;

    [Tooltip("Mouse sensitivity multiplier")]
    [SerializeField] private float mouseSensitivity = 0.15f;

    [Tooltip("Maximum up/down look angle")]
    [Range(45f, 90f)]
    [SerializeField] private float maxLookAngle = 85f;

    [Header("Debug")]
    [SerializeField] private bool showGroundCheck = true;

    private CharacterController controller;
    private Vector3 velocity;
    private bool isGrounded;
    private float cameraPitch;

    private void Awake()
    {
        controller = GetComponent<CharacterController>();

        // Lock and hide cursor for FPS
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
    }

    private void OnEnable()
    {
        if (InputReader.Instance != null)
        {
            InputReader.Instance.OnJumpPressed += HandleJump;
        }
    }

    private void OnDisable()
    {
        if (InputReader.Instance != null)
        {
            InputReader.Instance.OnJumpPressed -= HandleJump;
        }
    }

    private void Update()
    {
        CheckGrounded();
        HandleLook();
        HandleMovement();
        ApplyGravity();
        ApplyVelocity();
    }

    private void CheckGrounded()
    {
        Vector3 checkPosition = transform.position + groundCheckOffset;
        isGrounded = Physics.CheckSphere(checkPosition, groundCheckRadius, groundLayer);

        if (isGrounded && velocity.y < 0)
        {
            velocity.y = -2f;
        }
    }

    private void HandleLook()
    {
        if (InputReader.Instance == null) return;

        Vector2 lookInput = InputReader.Instance.LookInput;

        // Apply different sensitivity for mouse vs gamepad
        float lookSpeedX, lookSpeedY;
        if (InputReader.Instance.IsUsingGamepad)
        {
            lookSpeedX = gamepadLookSpeed * Time.deltaTime;
            lookSpeedY = gamepadLookSpeed * Time.deltaTime;
        }
        else
        {
            // Mouse input is already deltaTime-independent
            lookSpeedX = mouseSensitivity;
            lookSpeedY = mouseSensitivity;
        }

        // Horizontal rotation - rotate the whole player
        transform.Rotate(Vector3.up * lookInput.x * lookSpeedX);

        // Vertical rotation - rotate just the camera
        cameraPitch -= lookInput.y * lookSpeedY;
        cameraPitch = Mathf.Clamp(cameraPitch, -maxLookAngle, maxLookAngle);

        if (playerCamera != null)
        {
            playerCamera.localRotation = Quaternion.Euler(cameraPitch, 0f, 0f);
        }
    }

    private void HandleMovement()
    {
        Vector2 input = InputReader.Instance != null ? InputReader.Instance.MoveInput : Vector2.zero;

        // Movement relative to player facing direction
        Vector3 moveDirection = transform.forward * input.y + transform.right * input.x;

        float currentSpeed = moveSpeed;
        if (!isGrounded)
        {
            currentSpeed *= airControl;
        }

        velocity.x = moveDirection.x * currentSpeed;
        velocity.z = moveDirection.z * currentSpeed;
    }

    private void ApplyGravity()
    {
        if (!isGrounded)
        {
            float gravityThisFrame = gravity;
            if (velocity.y < 0)
            {
                gravityThisFrame *= fallGravityMultiplier;
            }
            velocity.y -= gravityThisFrame * Time.deltaTime;
        }
    }

    private void HandleJump()
    {
        if (isGrounded)
        {
            velocity.y = jumpForce;
        }
    }

    private void ApplyVelocity()
    {
        controller.Move(velocity * Time.deltaTime);
    }

    private void OnDrawGizmos()
    {
        if (showGroundCheck)
        {
            Vector3 checkPosition = transform.position + groundCheckOffset;
            Gizmos.color = isGrounded ? Color.green : Color.red;
            Gizmos.DrawWireSphere(checkPosition, groundCheckRadius);
        }
    }
}

BasicJumpIsometric.cs

Isometric/top-down jump. Movement is world-axis aligned (or rotated 45 degrees for true isometric). Jump adds vertical motion separate from XZ movement.

Setup:

  1. Add CharacterController to your player
  2. Set up isometric camera (typically 45-degree Y rotation, 30-45 degree X tilt)
  3. Add this script and configure in Inspector
  4. Choose rotation mode: World Axis or Isometric (45-degree rotated)
BasicJumpIsometric.cs
using UnityEngine;

/// <summary>
/// BASIC JUMP ISOMETRIC - Jump for isometric/top-down games
/// VG101 Code Bank - Requires InputReader from Input Setup
/// Movement on XZ plane with jump on Y axis
/// </summary>
[RequireComponent(typeof(CharacterController))]
public class BasicJumpIsometric : MonoBehaviour
{
    public enum MovementMode
    {
        WorldAxis,      // Up = +Z, Right = +X (standard top-down)
        Isometric45     // Up = +Z+X, Right = +X-Z (true isometric)
    }

    [Header("Jump Feel")]
    [Tooltip("How hard we push off the ground.")]
    [Range(5f, 25f)]
    [SerializeField] private float jumpForce = 10f;

    [Tooltip("How fast we fall.")]
    [Range(20f, 80f)]
    [SerializeField] private float gravity = 35f;

    [Tooltip("Extra gravity when falling.")]
    [Range(1f, 4f)]
    [SerializeField] private float fallGravityMultiplier = 1.5f;

    [Header("Ground Detection")]
    [SerializeField] private Vector3 groundCheckOffset = new Vector3(0, -0.5f, 0);
    [SerializeField] private float groundCheckRadius = 0.3f;
    [SerializeField] private LayerMask groundLayer;

    [Header("Movement")]
    [Tooltip("How movement input maps to world space")]
    [SerializeField] private MovementMode movementMode = MovementMode.Isometric45;

    [Tooltip("Movement speed on ground")]
    [SerializeField] private float moveSpeed = 7f;

    [Tooltip("Air control multiplier")]
    [Range(0f, 1f)]
    [SerializeField] private float airControl = 0.5f;

    [Tooltip("Rotate character to face movement direction")]
    [SerializeField] private bool rotateToMovement = true;

    [Tooltip("How fast character rotates to face movement")]
    [SerializeField] private float rotationSpeed = 720f;

    [Header("Debug")]
    [SerializeField] private bool showGroundCheck = true;

    private CharacterController controller;
    private Vector3 velocity;
    private bool isGrounded;

    // Isometric conversion matrix (45-degree rotation)
    private static readonly Matrix4x4 isoMatrix = Matrix4x4.Rotate(Quaternion.Euler(0, 45, 0));

    private void Awake()
    {
        controller = GetComponent<CharacterController>();
    }

    private void OnEnable()
    {
        if (InputReader.Instance != null)
        {
            InputReader.Instance.OnJumpPressed += HandleJump;
        }
    }

    private void OnDisable()
    {
        if (InputReader.Instance != null)
        {
            InputReader.Instance.OnJumpPressed -= HandleJump;
        }
    }

    private void Update()
    {
        CheckGrounded();
        HandleMovement();
        ApplyGravity();
        ApplyVelocity();
    }

    private void CheckGrounded()
    {
        Vector3 checkPosition = transform.position + groundCheckOffset;
        isGrounded = Physics.CheckSphere(checkPosition, groundCheckRadius, groundLayer);

        if (isGrounded && velocity.y < 0)
        {
            velocity.y = -2f;
        }
    }

    private void HandleMovement()
    {
        Vector2 input = InputReader.Instance != null ? InputReader.Instance.MoveInput : Vector2.zero;

        // Convert 2D input to 3D movement direction
        Vector3 inputDirection = new Vector3(input.x, 0, input.y);

        // Apply isometric rotation if needed
        Vector3 moveDirection;
        if (movementMode == MovementMode.Isometric45)
        {
            moveDirection = isoMatrix.MultiplyVector(inputDirection);
        }
        else
        {
            moveDirection = inputDirection;
        }

        float currentSpeed = moveSpeed;
        if (!isGrounded)
        {
            currentSpeed *= airControl;
        }

        velocity.x = moveDirection.x * currentSpeed;
        velocity.z = moveDirection.z * currentSpeed;

        // Rotate to face movement direction
        if (rotateToMovement && moveDirection.magnitude > 0.1f)
        {
            Quaternion targetRotation = Quaternion.LookRotation(moveDirection);
            transform.rotation = Quaternion.RotateTowards(
                transform.rotation,
                targetRotation,
                rotationSpeed * Time.deltaTime
            );
        }
    }

    private void ApplyGravity()
    {
        if (!isGrounded)
        {
            float gravityThisFrame = gravity;
            if (velocity.y < 0)
            {
                gravityThisFrame *= fallGravityMultiplier;
            }
            velocity.y -= gravityThisFrame * Time.deltaTime;
        }
    }

    private void HandleJump()
    {
        if (isGrounded)
        {
            velocity.y = jumpForce;
        }
    }

    private void ApplyVelocity()
    {
        controller.Move(velocity * Time.deltaTime);
    }

    private void OnDrawGizmos()
    {
        if (showGroundCheck)
        {
            Vector3 checkPosition = transform.position + groundCheckOffset;
            Gizmos.color = isGrounded ? Color.green : Color.red;
            Gizmos.DrawWireSphere(checkPosition, groundCheckRadius);
        }
    }
}

Common Issues

"It feels floaty"
Increase fallGravityMultiplier. Most good-feeling jumps have faster descent than ascent.

"It's too short/tall"
Adjust jumpForce and gravity together. Higher force + higher gravity = same height, different speed.

"I can jump in the air"
Ground check isn't working. Check groundCheckRadius and your collision layers.

"InputReader.Instance is null"
Make sure you have the InputReader prefab in your scene. See Input Setup.

Combine With

When to Use Basic vs. Variable

Use Basic Jump when... Use Variable Jump when...
Predictability matters (puzzle platformers) Expression matters (action platformers)
Jump height is carefully designed per obstacle Player should adapt jump to situation
Simplicity is a virtue Depth is a virtue
Retro aesthetic Modern platformer aesthetic

Related

Glossary Terms