Want more features? Check out the with advanced 2d character controller with wall jump, wall slide, dash, double jump, ledge grab, and moving platform support.
Part of these game systems:
beginner Movement

2D Player Controller

Complete 2D character controller with smooth movement, variable-height jumping, and coyote time.

Unity 2022.3+ · 3.2 KB · PlayerController2D.cs

How to Use

1

Attach to your player GameObject

2

Add a Rigidbody2D component

3

Create an empty child object at the feet for Ground Check

4

Set the Ground Layer in the inspector

5

Configure movement and jump values to your liking

Features

  • Variable-height jumping with configurable force
  • Coyote time for forgiving edge jumps
  • Jump buffering for responsive input queuing
  • Acceleration and deceleration for smooth movement feel
  • Physics-based ground detection via OverlapCircle
  • Gizmo visualization of ground check radius in Scene view

When to Use This

Perfect for 2D platformers, metroidvanias, and side-scrollers that need tight, responsive movement. Use this when you want physics-based character control with professional-feeling jump mechanics. Great starting point for any 2D game with a player character.

Common Mistakes

Forgetting to assign the groundCheck Transform in the Inspector is the #1 issue — the script will silently fail to detect ground. Make sure your ground objects are on the correct Layer matching the groundLayer mask. Don't set moveSpeed too high without increasing acceleration, or the character will feel floaty.

Source Code

PlayerController2D.cs
C#
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class PlayerController2D : MonoBehaviour
{
    [Header("Movement")]
    [SerializeField] private float moveSpeed = 8f;
    [SerializeField] private float acceleration = 60f;
    [SerializeField] private float deceleration = 50f;

    [Header("Jumping")]
    [SerializeField] private float jumpForce = 16f;
    [SerializeField] private float coyoteTime = 0.15f;
    [SerializeField] private float jumpBufferTime = 0.1f;

    [Header("Ground Check")]
    [SerializeField] private Transform groundCheck;
    [SerializeField] private float groundCheckRadius = 0.2f;
    [SerializeField] private LayerMask groundLayer;

    private Rigidbody2D rb;
    private float moveInput;
    private bool isGrounded;
    private float coyoteTimer;
    private float jumpBufferTimer;

    private void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        rb.freezeRotation = true;
    }

    private void Update()
    {
        moveInput = Input.GetAxisRaw("Horizontal");

        // Ground check
        isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer);

        // Coyote time
        if (isGrounded)
            coyoteTimer = coyoteTime;
        else
            coyoteTimer -= Time.deltaTime;

        // Jump buffer
        if (Input.GetButtonDown("Jump"))
            jumpBufferTimer = jumpBufferTime;
        else
            jumpBufferTimer -= Time.deltaTime;

        // Jump
        if (jumpBufferTimer > 0f && coyoteTimer > 0f)
        {
            rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce);
            jumpBufferTimer = 0f;
            coyoteTimer = 0f;
        }

        // Variable jump height
        if (Input.GetButtonUp("Jump") && rb.linearVelocity.y > 0f)
        {
            rb.linearVelocity = new Vector2(rb.linearVelocity.x, rb.linearVelocity.y * 0.5f);
        }
    }

    private void FixedUpdate()
    {
        float targetSpeed = moveInput * moveSpeed;
        float speedDiff = targetSpeed - rb.linearVelocity.x;
        float accelRate = Mathf.Abs(targetSpeed) > 0.01f ? acceleration : deceleration;
        float movement = speedDiff * accelRate * Time.fixedDeltaTime;

        rb.AddForce(Vector2.right * movement, ForceMode2D.Force);
    }

    private void OnDrawGizmosSelected()
    {
        if (groundCheck != null)
        {
            Gizmos.color = Color.green;
            Gizmos.DrawWireSphere(groundCheck.position, groundCheckRadius);
        }
    }
}
Ready for more? 2D Player Controller Pro Advanced 2D character controller with wall jump, wall slide, dash, double jump, ledge grab, and moving platform support.