2D Platformer Ready Controller in Unity

The Ultimate Responsive Controller in Unity (with Animations & Parallax!)

To give you the perfect starting point for your next side-scroller, this complete project package comes production-ready with a seamless, looping parallax background, fully configured environment tilemaps, and pre-mapped character animation states out of the box!

Responsive, crisp movement is the foundation of any great 2D platformer. If your character feels floaty, slides around like they’re on ice, or has sluggish animation transitions, players will instantly feel disconnected from the action.

To help you build a solid foundation, we are covering how to write a physics-backed 2D player controller script using Rigidbody2D. This system handles left/right movement, reliable physics-based jumping, automatic sprite flipping, and clean animation states—complete with structured slots where you can drop in your setup screenshots!

🛠️ Step 1: Configuring Your Ground and Tilemap Assets

Before diving into code, we need to set up our game environment so the character physics interact with the world flawlessly.

  1. Create Your Level Tilemap: Design your level using Unity’s 2D Tilemap system. Ensure your floor grid has a Tilemap Collider 2D attached to it so the player doesn’t fall through the void.
  2. Assign the Ground Layer: In the top-right corner of the Inspector window, click the Layer dropdown and add a new layer exactly named Ground. Assign this Ground layer to your main level tilemap.

🛠️ Step 2: The Ground Check Setup & Better Gravity Tricks

To make jumping feel tight and crisp, we use a small invisible physics overlap circle located right at the player’s feet. This completely eliminates the infamous “infinite mid-air jumping” glitch.

  1. Positioning the Check: Right-click your main Player GameObject and select Create Empty to add a child object. Name it GroundCheck. Move this child down until it sits perfectly at the bottom edge of your character’s body collider boundary.
  1. The Pro-Tip for Punchy Physics Gravity: By default, Unity’s gravity can feel like you are walking on the moon. To make falling feel fast, weighty, and satisfying, navigate to Edit > Project Settings > Physics 2D and change the Gravity Y vector from -9.81 to -40. This simple tweak makes jumps feel incredibly precise and professional!

📋 The Full Production Script: PlayerMovement.cs

Create a new C# script inside your project workspace, name it exactly PlayerMovement, and copy-paste the entire architecture below. It is fully production-ready and optimized:

C#

using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class PlayerMovement : MonoBehaviour
{
    [Header("Movement Settings")]
    [SerializeField] private float moveSpeed = 8f;
    [SerializeField] private float jumpForce = 15f;

    [Header("Ground Check System")]
    [SerializeField] private Transform groundCheckPoint;
    [SerializeField] private float checkRadius = 0.2f;
    [SerializeField] private LayerMask groundLayer;

    [Header("Visual Settings")]
    [SerializeField] private SpriteRenderer playerSprite;

    private Rigidbody2D rb;
    private float horizontalInput;
    private bool isGrounded;
    private bool jumpRequested;

    private Animator anim;

    private void Start()
    {
        // Automatically fetch the SpriteRenderer from children if left empty
        if (playerSprite == null)
        {
            playerSprite = GetComponentInChildren<SpriteRenderer>();
        }

        rb = GetComponent<Rigidbody2D>();
        anim = GetComponent<Animator>();
    }

    private void Update()
    {
        // 1. Gather horizontal input (A/D, Left/Right arrows)
        horizontalInput = Input.GetAxisRaw("Horizontal");

        // 2. Perform ground check using a small physics overlap circle
        isGrounded = Physics2D.OverlapCircle(groundCheckPoint.position, checkRadius, groundLayer);
        anim.SetBool("IsGrounded", isGrounded);

        // 3. Listen for jump input inside Update so button taps are never missed
        if (Input.GetButtonDown("Jump") && isGrounded)
        {
            jumpRequested = true;
        }

        // 4. Clean animation state routing rules
        if (isGrounded && horizontalInput != 0f)
        {
            anim.SetBool("IsWalking", true);
        }
        else
        {
            anim.SetBool("IsWalking", false);
        }

        FlipSprite();
    }

    private void FixedUpdate()
    {
        // 5. Apply horizontal velocity (left/right physics movement)
        rb.linearVelocity = new Vector2(horizontalInput * moveSpeed, rb.linearVelocity.y);

        // 6. Apply vertical velocity if a jump was requested
        if (jumpRequested)
        {
            rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce);
            jumpRequested = false; // Reset the request flag
        }
    }

    private void FlipSprite()
    {
        // If moving right, do not flip the sprite texture
        if (horizontalInput > 0f)
        {
            playerSprite.flipX = false;
        }
        // If moving left, flip the sprite texture horizontally
        else if (horizontalInput < 0f)
        {
            playerSprite.flipX = true;
        }
    }

    // Optional: Visualizes the ground check circle inside the Unity Editor Scene view
    private void OnDrawGizmosSelected()
    {
        if (groundCheckPoint != null)
        {
            Gizmos.color = Color.green;
            Gizmos.DrawWireSphere(groundCheckPoint.position, checkRadius);
        }
    }
}

🏃 Step 3: Wiring Up Your Animation States

Our script natively drives parameters to keep your animator controller perfectly synced up with your physical inputs. Open your Unity Animator window and create the following structure:

  1. Add Your Parameters: Create two Booleans inside your animator parameters panel named exactly IsWalking and IsGrounded.
  2. Map the States: Lay out your 3 essential character animation clips: Idle, Walk, and Jump.
  3. Configure the Transitions:
    • Idle ➡️ Walk: Set IsWalking to true.
    • Walk ➡️ Idle: Set IsWalking to false.
    • Any State ➡️ Jump: Set IsGrounded to false to instantly switch to air animations whenever your character leaves a platform ledger.

⚠️ Common Pitfalls to Avoid

  • The Jittery Display (Update vs FixedUpdate): Notice that input capturing and animation parameter routing happen inside Update(), while changes to rb.linearVelocity take place inside FixedUpdate(). Mixing these up will either cause skipped jump commands or visible character stuttering during movement.
  • Wall Friction Sticking: If your character temporarily clings to vertical walls when you push against them mid-air, create a new Physics Material 2D, change its Friction property to 0, and drop it into the Material slot on your player’s primary Collider2D component.

Wrap Up

With your tilemaps assigned, gravity scaled to a punchy -40, and your inputs feeding your state machine cleanly, you have a rock-solid foundation ready for any 2D adventure game design! Copy the code, bind your child sprite references in the inspector slots, and start creating levels!

👉 Download The Complete 2D Platformer Project For Free Here!