Part of these game systems:
2D Platformer Mechanics
Wall jump, wall slide, dash, and double jump mechanics for 2D platformers. Drop-in extension for the 2D Player Controller.
How to Use
1
Attach PlatformerMechanics2D to your player (alongside PlayerController2D)
2
Create a WallCheck child transform at character's side
3
Assign ground and wall check references
4
Enable/disable individual mechanics in inspector
5
Wall slide: hold into wall while falling
6
Wall jump: press Jump while wall sliding
7
Double jump: press Jump in mid-air
8
Dash: press Shift (configurable) for burst speed
Source Code
C#
using UnityEngine;
/// <summary>
/// Advanced 2D platformer mechanics: wall slide, wall jump, dash, and double jump.
/// Works alongside PlayerController2D.
/// </summary>
[RequireComponent(typeof(Rigidbody2D))]
public class PlatformerMechanics2D : MonoBehaviour
{
[Header("Wall Detection")]
[SerializeField] private Transform wallCheck;
[SerializeField] private float wallCheckDistance = 0.3f;
[SerializeField] private LayerMask wallLayer;
[Header("Wall Slide")]
[SerializeField] private bool enableWallSlide = true;
[SerializeField] private float wallSlideSpeed = 2f;
[Header("Wall Jump")]
[SerializeField] private bool enableWallJump = true;
[SerializeField] private Vector2 wallJumpForce = new Vector2(12f, 16f);
[SerializeField] private float wallJumpDuration = 0.15f;
[Header("Double Jump")]
[SerializeField] private bool enableDoubleJump = true;
[SerializeField] private float doubleJumpForce = 14f;
[SerializeField] private int maxAirJumps = 1;
[Header("Dash")]
[SerializeField] private bool enableDash = true;
[SerializeField] private float dashSpeed = 25f;
[SerializeField] private float dashDuration = 0.15f;
[SerializeField] private float dashCooldown = 0.8f;
[SerializeField] private KeyCode dashKey = KeyCode.LeftShift;
[Header("Ground Check")]
[SerializeField] private Transform groundCheck;
[SerializeField] private float groundCheckRadius = 0.2f;
[SerializeField] private LayerMask groundLayer;
private Rigidbody2D rb;
private int airJumpsRemaining;
private bool isWallSliding;
private bool isWallJumping;
private float wallJumpTimer;
private int wallDirection;
private bool isDashing;
private float dashTimer;
private float dashCooldownTimer;
private bool isGrounded;
private float originalGravityScale;
/// <summary>Is the character currently wall sliding?</summary>
public bool IsWallSliding => isWallSliding;
/// <summary>Is the character currently dashing?</summary>
public bool IsDashing => isDashing;
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
originalGravityScale = rb.gravityScale;
}
private void Update()
{
isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer);
if (isGrounded)
airJumpsRemaining = maxAirJumps;
CheckWall();
HandleWallSlide();
HandleWallJump();
HandleDoubleJump();
HandleDash();
}
private void CheckWall()
{
float moveInput = Input.GetAxisRaw("Horizontal");
bool isTouchingWall = false;
if (wallCheck != null && Mathf.Abs(moveInput) > 0.1f)
{
isTouchingWall = Physics2D.Raycast(
wallCheck.position,
Vector2.right * Mathf.Sign(moveInput),
wallCheckDistance,
wallLayer
);
wallDirection = (int)Mathf.Sign(moveInput);
}
isWallSliding = enableWallSlide && isTouchingWall && !isGrounded && rb.linearVelocity.y < 0f;
}
private void HandleWallSlide()
{
if (isWallSliding)
{
rb.linearVelocity = new Vector2(rb.linearVelocity.x, -wallSlideSpeed);
}
}
private void HandleWallJump()
{
if (isWallJumping)
{
wallJumpTimer -= Time.deltaTime;
if (wallJumpTimer <= 0f)
isWallJumping = false;
}
if (enableWallJump && isWallSliding && Input.GetButtonDown("Jump"))
{
isWallJumping = true;
wallJumpTimer = wallJumpDuration;
rb.linearVelocity = new Vector2(
-wallDirection * wallJumpForce.x,
wallJumpForce.y
);
// Flip character away from wall
transform.localScale = new Vector3(-wallDirection, 1f, 1f);
}
}
private void HandleDoubleJump()
{
if (!enableDoubleJump) return;
if (Input.GetButtonDown("Jump") && !isGrounded && !isWallSliding && airJumpsRemaining > 0)
{
rb.linearVelocity = new Vector2(rb.linearVelocity.x, doubleJumpForce);
airJumpsRemaining--;
}
}
private void HandleDash()
{
if (!enableDash) return;
if (dashCooldownTimer > 0f)
dashCooldownTimer -= Time.deltaTime;
if (isDashing)
{
dashTimer -= Time.deltaTime;
if (dashTimer <= 0f)
{
isDashing = false;
rb.gravityScale = originalGravityScale;
}
return;
}
if (Input.GetKeyDown(dashKey) && dashCooldownTimer <= 0f)
{
isDashing = true;
dashTimer = dashDuration;
dashCooldownTimer = dashCooldown;
float direction = transform.localScale.x;
rb.linearVelocity = new Vector2(direction * dashSpeed, 0f);
rb.gravityScale = 0f;
}
}
#if UNITY_EDITOR
private void OnDrawGizmosSelected()
{
if (wallCheck != null)
{
Gizmos.color = Color.blue;
Gizmos.DrawLine(wallCheck.position, wallCheck.position + Vector3.right * wallCheckDistance);
Gizmos.DrawLine(wallCheck.position, wallCheck.position + Vector3.left * wallCheckDistance);
}
}
#endif
}