Jump Mechanics
Introduction to Jump Mechanics
Jump mechanics are one of the most important elements in many games, especially platformers. Creating a satisfying jump that feels good to players requires understanding both the physics and the game design principles behind jump mechanics.
In this guide, you'll learn:
- How to implement basic jumps in Unity
- Different types of jump mechanics
- Advanced jump techniques (double jump, wall jump)
- How to fine-tune your jumps for better gameplay feel
- Common jump-related problems and solutions
Basic Jump Implementation
The most common approach to jumping in Unity is to apply an upward force or velocity to a Rigidbody when the player presses the jump button.
Basic Jump Implementation (2D):
using UnityEngine;
public class BasicJump2D : MonoBehaviour
{
// Jump settings
public float jumpForce = 10f; // How high the player jumps
public Transform groundCheck; // GameObject checking if we're grounded
public float groundCheckRadius = 0.2f; // Size of the ground check circle
public LayerMask groundLayer; // Which layers count as ground
// References
private Rigidbody2D rb; // Physics component
private bool isGrounded; // Tracks grounded state
void Start()
{
// Get the Rigidbody2D component
rb = GetComponent();
// This component handles the physics simulation
}
void Update()
{
// Check if the player is standing on ground
isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer);
// This creates a circle at groundCheck position and sees if it overlaps with ground
// Jump when the player presses the jump button while grounded
if (Input.GetButtonDown("Jump") && isGrounded)
{
Jump();
// Call our jump method when conditions are met
}
}
void Jump()
{
// Apply an impulse force upward
rb.velocity = new Vector2(rb.velocity.x, 0f); // Zero out current Y velocity
rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
// This makes the character jump by adding an instant upward force
}
// Visual debugging of the ground check radius
void OnDrawGizmosSelected()
{
if (groundCheck == null)
return;
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(groundCheck.position, groundCheckRadius);
// This draws a yellow circle showing the ground detection area in the editor
}
}
Key components of the basic jump:
- Ground check: Detects if the player is standing on ground
- Jump force: Controls the height of the jump
- AddForce: Applies immediate upward force (using ForceMode.Impulse)
- Input check: Only jump when the jump button is pressed
Basic Jump Implementation (3D):
using UnityEngine;
public class BasicJump3D : MonoBehaviour
{
public float jumpForce = 7f; // How high the player jumps
public Transform groundCheck; // GameObject checking if we're grounded
public float groundCheckDistance = 0.4f; // How far to check for ground
public LayerMask groundLayer; // Which layers count as ground
private Rigidbody rb; // Physics component
private bool isGrounded; // Tracks grounded state
void Start()
{
// Get the Rigidbody component
rb = GetComponent();
// This component handles 3D physics simulation
}
void Update()
{
// Check if the player is standing on ground using a raycast
isGrounded = Physics.Raycast(
groundCheck.position, // Start position of ray
Vector3.down, // Direction of ray (downward)
groundCheckDistance, // Length of ray
groundLayer // Which layers to check against
);
// This casts a ray downward and sees if it hits ground
// Jump when the player presses the jump button while grounded
if (Input.GetButtonDown("Jump") && isGrounded)
{
Jump();
// Call our jump method when conditions are met
}
}
void Jump()
{
// Apply an impulse force upward
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
// This makes the character jump by adding an instant upward force
}
// Visual debugging of the ground check
void OnDrawGizmosSelected()
{
if (groundCheck == null)
return;
Gizmos.color = Color.red;
Gizmos.DrawLine(groundCheck.position, groundCheck.position + Vector3.down * groundCheckDistance);
// This draws a red line showing the ground detection in the editor
}
}
Ground Check Methods
Different ways to check if the player is on the ground:
- Raycast: Cast a ray downward from the player
- OverlapCircle/OverlapBox: Check if a shape overlaps with ground
- OnCollisionStay/OnCollisionExit: Track collision events
- Character Controller's isGrounded: Use built-in property if using Character Controller
Each method has pros and cons regarding precision and performance.
Jump Physics and Feel
Creating a jump that feels satisfying requires more than just applying force. Let's explore techniques to make jumps feel better.
Improved Jump Physics:
using UnityEngine;
public class ImprovedJump : MonoBehaviour
{
[Header("Jump Parameters")]
public float jumpForce = 12f; // Initial jump force
public float fallMultiplier = 2.5f; // Increased gravity when falling
public float lowJumpMultiplier = 2f; // Increased gravity when jump button released
[Header("Ground Check")]
public Transform groundCheck;
public float groundCheckRadius = 0.2f;
public LayerMask groundLayer;
// References
private Rigidbody2D rb;
private bool isGrounded;
private bool isJumping;
void Start()
{
rb = GetComponent();
}
void Update()
{
// Ground check
isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer);
// This creates a circle at groundCheck position and checks for ground
// If we're on the ground, we're not jumping
if (isGrounded && rb.velocity.y <= 0)
{
isJumping = false;
}
// Jump input
if (Input.GetButtonDown("Jump") && isGrounded)
{
Jump();
// Initiate jump when jump button is pressed and we're grounded
}
// Apply better jump physics
ApplyJumpPhysics();
// This method applies variable gravity for better jump feel
}
void Jump()
{
// Reset Y velocity then apply jump force
rb.velocity = new Vector2(rb.velocity.x, 0f);
rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
isJumping = true;
// Track that we're now in a jump
}
void ApplyJumpPhysics()
{
// If we're falling, apply the fall multiplier
if (rb.velocity.y < 0)
{
// Apply extra gravity when falling
rb.velocity += Vector2.up * Physics2D.gravity.y * (fallMultiplier - 1) * Time.deltaTime;
// This makes the character fall faster than it rises, creating a more satisfying arc
}
// If we're jumping but the jump button isn't held, apply the low jump multiplier
else if (rb.velocity.y > 0 && !Input.GetButton("Jump"))
{
// Apply extra gravity when ascending but jump button released
rb.velocity += Vector2.up * Physics2D.gravity.y * (lowJumpMultiplier - 1) * Time.deltaTime;
// This allows for variable jump height based on how long the button is pressed
}
}
void OnDrawGizmosSelected()
{
if (groundCheck == null)
return;
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(groundCheck.position, groundCheckRadius);
}
}
What makes this jump feel better:
- Variable gravity: Increased gravity when falling creates a more satisfying arc
- Button sensitivity: Quick-release of jump button creates a shorter jump
- Better grounding: Proper ground detection ensures consistent jumping
- Reset Y velocity: Ensures consistent jump height regardless of previous motion
Jump Feel Tips
- Jump Arc: The path the character follows during a jump should have a nice arc
- Gravity Multipliers: Different gravity for rising/falling creates better feel
- Variable Height: Allow players to control jump height by button press duration
- Coyote Time: Allow jumps slightly after leaving a platform
- Jump Buffering: Allow jumps slightly before landing
- Visual Feedback: Add animations, particles, or sound for feedback
Advanced Jump Techniques
Let's explore more advanced jump mechanics like double jump, wall jump, and variable height jumping.
Double Jump Implementation:
using UnityEngine;
public class DoubleJumpController : MonoBehaviour
{
[Header("Jump Settings")]
public float jumpForce = 10f; // Initial jump force
public float doubleJumpForce = 8f; // Force of the second jump
public int maxJumps = 2; // Maximum number of jumps allowed
[Header("Ground Check")]
public Transform groundCheck;
public float groundCheckRadius = 0.2f;
public LayerMask groundLayer;
// References and state tracking
private Rigidbody2D rb;
private int jumpsRemaining; // How many jumps we have left
private bool isGrounded;
void Start()
{
rb = GetComponent();
jumpsRemaining = maxJumps; // Start with full jumps
}
void Update()
{
// Check if grounded
isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer);
// Reset jumps when touching ground
if (isGrounded && rb.velocity.y <= 0)
{
jumpsRemaining = maxJumps;
// When we land, restore all available jumps
}
// Jump input
if (Input.GetButtonDown("Jump") && jumpsRemaining > 0)
{
// Choose the appropriate jump force
float force = (jumpsRemaining == maxJumps) ? jumpForce : doubleJumpForce;
// Use the primary jump force for the first jump, and double jump force for subsequent jumps
// Reset Y velocity and apply jump force
rb.velocity = new Vector2(rb.velocity.x, 0f);
rb.AddForce(Vector2.up * force, ForceMode2D.Impulse);
// Decrement jumps remaining
jumpsRemaining--;
// Track that we've used up one of our available jumps
}
}
}
Key components of double jump:
- Jump counter: Track how many jumps the player has used
- Reset on ground: Restore jumps when the player touches the ground
- Different forces: Often the second jump is slightly weaker
- Separate input: Each jump requires a new button press
Wall Jump Implementation:
using UnityEngine;
public class WallJumpController : MonoBehaviour
{
[Header("Movement")]
public float moveSpeed = 7f;
public float jumpForce = 12f;
[Header("Wall Jump")]
public float wallSlideSpeed = 2f; // How fast player slides down walls
public float wallJumpForce = 16f; // Force of wall jump
public float wallJumpAngle = 65f; // Angle of wall jump (degrees)
public float wallJumpTime = 0.15f; // Time player can't control movement after wall jump
[Header("Checks")]
public Transform groundCheck;
public Transform wallCheck;
public float groundCheckRadius = 0.2f;
public float wallCheckDistance = 0.5f;
public LayerMask groundLayer;
// References and state
private Rigidbody2D rb;
private bool isGrounded;
private bool isWallSliding;
private bool isTouchingWall;
private float horizontalInput;
private int facingDirection = 1; // 1 for right, -1 for left
private float wallJumpTimer; // Timer for wall jump input lockout
void Start()
{
rb = GetComponent();
}
void Update()
{
// Process inputs and state
CheckInput();
CheckGrounded();
CheckWallSliding();
// Handle wall jump timer
if (wallJumpTimer > 0)
{
wallJumpTimer -= Time.deltaTime;
// Count down the timer that prevents movement after a wall jump
}
// Handle jump input
if (Input.GetButtonDown("Jump"))
{
if (isGrounded)
{
NormalJump();
// If on ground, do a normal jump
}
else if (isWallSliding)
{
WallJump();
// If sliding on a wall, do a wall jump
}
}
}
void FixedUpdate()
{
// Move horizontally if not in wall jump recovery
if (wallJumpTimer <= 0)
{
rb.velocity = new Vector2(horizontalInput * moveSpeed, rb.velocity.y);
// Apply horizontal movement based on input
}
// Apply wall slide if touching wall
if (isWallSliding)
{
// Limit downward velocity while wall sliding
if (rb.velocity.y < -wallSlideSpeed)
{
rb.velocity = new Vector2(rb.velocity.x, -wallSlideSpeed);
// This creates a slower descent when sliding against a wall
}
}
}
void CheckInput()
{
horizontalInput = Input.GetAxisRaw("Horizontal");
// Update facing direction
if (horizontalInput > 0 && facingDirection == -1)
{
Flip();
}
else if (horizontalInput < 0 && facingDirection == 1)
{
Flip();
}
}
void CheckGrounded()
{
isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer);
}
void CheckWallSliding()
{
// Check if touching wall
isTouchingWall = Physics2D.Raycast(
wallCheck.position,
Vector2.right * facingDirection,
wallCheckDistance,
groundLayer
);
// This casts a ray in the facing direction to detect walls
// Wall slide if touching wall, not grounded, and moving toward wall
isWallSliding = isTouchingWall && !isGrounded && horizontalInput * facingDirection > 0;
// Only wall slide when actively pushing toward the wall
}
void NormalJump()
{
rb.velocity = new Vector2(rb.velocity.x, 0f);
rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
}
void WallJump()
{
// Stop sliding
isWallSliding = false;
// Calculate wall jump direction
float wallJumpDirectionX = -facingDirection; // Jump away from wall
Vector2 wallJumpDirection = new Vector2(wallJumpDirectionX, 1f).normalized;
// Convert angle to radians for calculation
float wallJumpAngleRadians = wallJumpAngle * Mathf.Deg2Rad;
Vector2 forceToApply = new Vector2(
-facingDirection * wallJumpForce * Mathf.Cos(wallJumpAngleRadians),
wallJumpForce * Mathf.Sin(wallJumpAngleRadians)
);
// Reset velocity and apply force
rb.velocity = Vector2.zero;
rb.AddForce(forceToApply, ForceMode2D.Impulse);
// Flip character to face jump direction
if (facingDirection * wallJumpDirectionX < 0)
{
Flip();
}
// Set timer to prevent immediate movement control
wallJumpTimer = wallJumpTime;
}
void Flip()
{
// Flip the character's facing direction
facingDirection *= -1;
transform.Rotate(0f, 180f, 0f);
// This rotates the character to face the other way
}
void OnDrawGizmosSelected()
{
// Draw ground check
if (groundCheck != null)
{
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(groundCheck.position, groundCheckRadius);
}
// Draw wall check
if (wallCheck != null)
{
Gizmos.color = Color.blue;
Gizmos.DrawLine(
wallCheck.position,
wallCheck.position + Vector3.right * facingDirection * wallCheckDistance
);
}
}
}
Key components of wall jump:
- Wall detection: Raycasts to check if touching a wall
- Wall sliding: Slows descent when pressed against a wall
- Jump direction: Jumps away from the wall at an angle
- Input lockout: Brief period after wall jump where player can't change direction
- Character flipping: Changes facing direction based on movement
Coyote Time and Jump Buffering:
using UnityEngine;
public class AdvancedJumpController : MonoBehaviour
{
[Header("Jump Parameters")]
public float jumpForce = 12f;
[Header("Coyote Time")]
public float coyoteTime = 0.15f; // Time player can jump after leaving ground
private float coyoteTimeCounter; // Tracks remaining coyote time
[Header("Jump Buffering")]
public float jumpBufferTime = 0.2f; // Time before landing that jump input is remembered
private float jumpBufferCounter; // Tracks remaining buffer time
[Header("Ground Check")]
public Transform groundCheck;
public float groundCheckRadius = 0.2f;
public LayerMask groundLayer;
// References
private Rigidbody2D rb;
private bool isGrounded;
void Start()
{
rb = GetComponent();
}
void Update()
{
// Check if grounded
isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer);
// COYOTE TIME LOGIC
if (isGrounded)
{
// Reset coyote time when grounded
coyoteTimeCounter = coyoteTime;
// This gives the player the full coyote time window when leaving a platform
}
else
{
// Count down coyote time when in air
coyoteTimeCounter -= Time.deltaTime;
// This decreases the remaining time window for jumping after leaving ground
}
// JUMP BUFFER LOGIC
if (Input.GetButtonDown("Jump"))
{
// Start the jump buffer when jump is pressed
jumpBufferCounter = jumpBufferTime;
// This starts the window of time where a jump input is remembered
}
else
{
// Count down jump buffer time
jumpBufferCounter -= Time.deltaTime;
// This decreases the remaining time window for the buffered jump
}
// JUMP EXECUTION
// Jump if we have a buffered jump input AND either grounded or in coyote time
if (jumpBufferCounter > 0f && coyoteTimeCounter > 0f)
{
// Execute jump
rb.velocity = new Vector2(rb.velocity.x, 0f);
rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
// Reset jump buffer to prevent double jumps
jumpBufferCounter = 0f;
// Clear the buffered jump input so we don't jump again
}
// Cancel coyote time if player is falling
if (rb.velocity.y < 0)
{
coyoteTimeCounter = 0f;
// If player is falling (not from a jump), don't allow coyote time
}
}
}
Making jumps feel more responsive:
- Coyote Time: Allows the player to jump shortly after leaving a platform
- Jump Buffering: Remembers jump input shortly before landing
- Combined Effect: These techniques make the controls feel more responsive
- Counter Reset: Carefully resetting counters prevents unintended jumps
Jump Variations and Game Feel
Different games require different jump mechanics. Here are some common variations and how to implement them.
Jump Variations
- Floaty Jump: Low gravity, high jump force, longer hang time
- Snappy Jump: High gravity, high jump force, quick ascent and descent
- Charged Jump: Hold button to increase jump power
- Air Control: How much the player can influence movement while in air
- Multidirectional Jump: Can jump in any direction (common in zero-gravity games)
Charged Jump Implementation:
using UnityEngine;
using UnityEngine.UI; // For the UI elements
public class ChargedJumpController : MonoBehaviour
{
[Header("Jump Settings")]
public float minJumpForce = 5f; // Minimum jump force
public float maxJumpForce = 15f; // Maximum jump force
public float chargeTime = 1.5f; // Time to reach maximum charge
[Header("Charge Indicator")]
public Image chargeBar; // UI element showing charge (optional)
[Header("Ground Check")]
public Transform groundCheck;
public float groundCheckRadius = 0.2f;
public LayerMask groundLayer;
// References and state
private Rigidbody2D rb;
private bool isGrounded;
private bool isCharging = false;
private float chargeAmount = 0f;
void Start()
{
rb = GetComponent();
// Initialize charge bar if assigned
if (chargeBar != null)
{
chargeBar.fillAmount = 0f;
}
}
void Update()
{
// Check if grounded
isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer);
// Start charging when the jump button is pressed while grounded
if (Input.GetButtonDown("Jump") && isGrounded)
{
isCharging = true;
chargeAmount = 0f;
// Start charging the jump
}
// Charge while jump button is held
if (Input.GetButton("Jump") && isCharging)
{
// Increase charge amount over time
chargeAmount += Time.deltaTime / chargeTime;
chargeAmount = Mathf.Clamp01(chargeAmount);
// Increase charge but cap it at 100%
// Update UI if assigned
if (chargeBar != null)
{
chargeBar.fillAmount = chargeAmount;
// Update the visual charge indicator
}
}
// Release jump when button is released
if (Input.GetButtonUp("Jump") && isCharging)
{
// Calculate jump force based on charge amount
float jumpForce = Mathf.Lerp(minJumpForce, maxJumpForce, chargeAmount);
// Apply the jump force
rb.velocity = new Vector2(rb.velocity.x, 0f);
rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
// Reset charging state
isCharging = false;
chargeAmount = 0f;
// Update UI
if (chargeBar != null)
{
chargeBar.fillAmount = chargeAmount;
}
}
// Cancel charging if player leaves the ground
if (!isGrounded && isCharging)
{
isCharging = false;
chargeAmount = 0f;
// Update UI
if (chargeBar != null)
{
chargeBar.fillAmount = chargeAmount;
}
}
}
}
Charged jump components:
- Charge timer: Tracks how long the button is held
- Visual feedback: UI or animation showing charge level
- Variable force: Jump force based on charge amount
- Cancel conditions: How to handle interrupted charging
Common Jump Problems and Solutions
-
Inconsistent Ground Detection
Solution: Use multiple raycasts or a slightly larger collision check area. Consider using a dedicated "feet" collider.
-
Sticking to Walls
Solution: Use a layer mask that excludes walls from ground checks, or implement proper wall detection for wall jumps.
-
Unresponsive Jump Controls
Solution: Implement coyote time and jump buffering as shown earlier.
-
Varying Jump Heights
Solution: Apply extra gravity when the jump button is released to cut the jump short.
-
Jittery Movement
Solution: Apply movement in FixedUpdate() and handle input in Update() for smoother physics.
-
Getting Stuck in Colliders
Solution: Adjust collider sizes and use Physics2D/Physics settings to prevent tunneling.
-
Poor Game Feel
Solution: Add animations, particles, and sound effects. Tweak gravity multipliers for better jump arcs.
Best Practices for Jump Mechanics
- Prioritize feel over realism: Games often have exaggerated physics for better gameplay
- Test extensively: What feels good to you may not feel good to players
- Add visual feedback: Jump animations, dust particles, impact effects
- Add audio feedback: Jump sounds, landing sounds
- Consider accessibility: Options to adjust timing windows or auto-jump features
- Be consistent: Maintain consistent jump behavior throughout your game
- Reference existing games: Study successful platformers to understand what works
- Use Unity's input system: Consider the new Input System for better cross-platform support
- Separate logic: Keep ground detection, input handling, and physics application in separate methods
- Document your code: Make it easy for others (or future you) to understand your jump implementation