Lesson 4: Player Movement & Controls

Objective

In this lesson, you will implement responsive 2D character movement, jumping, running, and basic controls for your platformer game. You'll learn how to create smooth, professional-feeling movement that responds perfectly to player input.

What You'll Learn

  • How to set up a 2D character controller
  • Implementing responsive movement and jumping
  • Adding running mechanics and speed variations
  • Creating smooth camera following
  • Input handling and control customization

Prerequisites

  • Unity 2D project set up from Lesson 2
  • Character sprite created from Lesson 3
  • Basic understanding of C# scripting

Step 1: Setting Up Your Character Controller

Step 1.1: Create the Player GameObject

  1. In your scene, create a new GameObject and name it "Player"
  2. Add a Sprite Renderer component to display your character sprite
  3. Add a Rigidbody2D component for physics
  4. Add a Collider2D component (Capsule Collider 2D works well for characters)
  5. Set the Gravity Scale to 3 (adjust for your game's feel)

Step 1.2: Configure Physics Settings

  1. Select your Player GameObject
  2. In the Rigidbody2D component:
    • Set Body Type to "Dynamic"
    • Set Gravity Scale to 3
    • Set Freeze Rotation Z to true (prevents character from tipping over)
    • Set Linear Drag to 0.5 (adds air resistance)

Step 2: Creating the Movement Script

Step 2.1: Create the Player Controller Script

  1. Create a new C# script called "PlayerController"
  2. Attach it to your Player GameObject
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    [Header("Movement Settings")]
    public float moveSpeed = 5f;
    public float jumpForce = 12f;
    public float runMultiplier = 1.5f;

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

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

    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
    }

    void Update()
    {
        HandleInput();
        CheckGrounded();
        Move();
    }

    void HandleInput()
    {
        horizontalInput = Input.GetAxis("Horizontal");
        isRunning = Input.GetKey(KeyCode.LeftShift);

        if (Input.GetButtonDown("Jump") && isGrounded)
        {
            Jump();
        }
    }

    void Move()
    {
        float currentSpeed = moveSpeed;

        if (isRunning)
        {
            currentSpeed *= runMultiplier;
        }

        rb.velocity = new Vector2(horizontalInput * currentSpeed, rb.velocity.y);
    }

    void Jump()
    {
        rb.velocity = new Vector2(rb.velocity.x, jumpForce);
    }

    void CheckGrounded()
    {
        isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer);
    }
}

Step 2.2: Set Up Ground Detection

  1. Create an empty GameObject as a child of your Player
  2. Name it "GroundCheck"
  3. Position it at the bottom of your character's feet
  4. Assign this GameObject to the Ground Check field in your script
  5. Create a Ground layer in your project
  6. Assign your ground/platform objects to this layer

Step 3: Advanced Movement Features

Step 3.1: Add Coyote Time (Advanced Jumping)

Coyote time allows players to jump for a brief moment after leaving a platform:

[Header("Advanced Movement")]
public float coyoteTime = 0.2f;
public float jumpBufferTime = 0.2f;

private float coyoteTimeCounter;
private float jumpBufferCounter;

void Update()
{
    HandleInput();
    CheckGrounded();
    HandleCoyoteTime();
    Move();
}

void HandleCoyoteTime()
{
    if (isGrounded)
    {
        coyoteTimeCounter = coyoteTime;
    }
    else
    {
        coyoteTimeCounter -= Time.deltaTime;
    }

    if (Input.GetButtonDown("Jump"))
    {
        jumpBufferCounter = jumpBufferTime;
    }
    else
    {
        jumpBufferCounter -= Time.deltaTime;
    }

    if (jumpBufferCounter > 0f && coyoteTimeCounter > 0f)
    {
        Jump();
        jumpBufferCounter = 0f;
    }
}

Step 3.2: Add Variable Jump Height

Allow players to control jump height by holding the jump button:

[Header("Jump Control")]
public float jumpCutMultiplier = 0.5f;

void Update()
{
    HandleInput();
    CheckGrounded();
    HandleCoyoteTime();
    Move();
    HandleJumpCut();
}

void HandleJumpCut()
{
    if (Input.GetButtonUp("Jump") && rb.velocity.y > 0)
    {
        rb.velocity = new Vector2(rb.velocity.x, rb.velocity.y * jumpCutMultiplier);
    }
}

Step 4: Camera Following

Step 4.1: Set Up Camera Follow Script

  1. Create a new C# script called "CameraFollow"
  2. Attach it to your Main Camera
using UnityEngine;

public class CameraFollow : MonoBehaviour
{
    [Header("Follow Settings")]
    public Transform target;
    public float smoothSpeed = 0.125f;
    public Vector3 offset;

    [Header("Camera Bounds")]
    public bool useBounds = false;
    public float leftBound;
    public float rightBound;
    public float topBound;
    public float bottomBound;

    void LateUpdate()
    {
        if (target == null) return;

        Vector3 desiredPosition = target.position + offset;

        if (useBounds)
        {
            desiredPosition.x = Mathf.Clamp(desiredPosition.x, leftBound, rightBound);
            desiredPosition.y = Mathf.Clamp(desiredPosition.y, bottomBound, topBound);
        }

        Vector3 smoothedPosition = Vector3.Lerp(transform.position, desiredPosition, smoothSpeed);
        transform.position = smoothedPosition;
    }
}

Step 4.2: Configure Camera Settings

  1. Assign your Player to the Target field
  2. Set the Offset to (0, 2, -10) for a good starting position
  3. Adjust Smooth Speed to 0.125 for smooth following
  4. Enable Use Bounds if you want to limit camera movement

Step 5: Input Customization

Step 5.1: Create Custom Input Actions

  1. Go to Edit > Project Settings > Input Manager
  2. Create new input axes for better control:

Horizontal Movement:

  • Name: "Horizontal"
  • Type: Key or Mouse Button
  • Positive Button: "d"
  • Negative Button: "a"
  • Alt Positive Button: "Right"
  • Alt Negative Button: "Left"

Jump:

  • Name: "Jump"
  • Type: Key or Mouse Button
  • Positive Button: "space"
  • Alt Positive Button: "w"

Step 5.2: Add Controller Support

void HandleInput()
{
    // Keyboard input
    horizontalInput = Input.GetAxis("Horizontal");
    isRunning = Input.GetKey(KeyCode.LeftShift);

    // Controller input
    if (Input.GetAxis("ControllerHorizontal") != 0)
    {
        horizontalInput = Input.GetAxis("ControllerHorizontal");
    }

    if (Input.GetButtonDown("Jump") && (isGrounded || coyoteTimeCounter > 0f))
    {
        Jump();
    }
}

Step 6: Movement Polish and Feel

Step 6.1: Add Movement Acceleration

[Header("Movement Polish")]
public float acceleration = 10f;
public float deceleration = 10f;

void Move()
{
    float targetSpeed = horizontalInput * moveSpeed;

    if (isRunning)
    {
        targetSpeed *= runMultiplier;
    }

    float currentSpeed = rb.velocity.x;
    float speedDifference = targetSpeed - currentSpeed;
    float accelerationRate = (Mathf.Abs(targetSpeed) > 0.01f) ? acceleration : deceleration;

    float movement = speedDifference * accelerationRate;
    rb.AddForce(movement * Vector2.right, ForceMode2D.Force);
}

Step 6.2: Add Landing Effects

[Header("Landing Effects")]
public ParticleSystem landingEffect;
public AudioClip landingSound;

void OnCollisionEnter2D(Collision2D collision)
{
    if (collision.gameObject.layer == groundLayer && rb.velocity.y <= 0)
    {
        if (landingEffect != null)
        {
            landingEffect.Play();
        }

        if (landingSound != null)
        {
            AudioSource.PlayClipAtPoint(landingSound, transform.position);
        }
    }
}

Step 7: Testing and Debugging

Step 7.1: Add Debug Visualization

void OnDrawGizmosSelected()
{
    if (groundCheck != null)
    {
        Gizmos.color = isGrounded ? Color.green : Color.red;
        Gizmos.DrawWireSphere(groundCheck.position, groundCheckRadius);
    }
}

Step 7.2: Test Your Movement

  1. Basic Movement: Test left/right movement
  2. Jumping: Test single jumps and multiple jumps
  3. Running: Test the run button functionality
  4. Coyote Time: Test jumping just after leaving a platform
  5. Variable Jump: Test holding vs. tapping jump

Troubleshooting Common Issues

Issue 1: Character Sliding on Slopes

Problem: Character slides down slopes when standing still Solution: Add friction material to your collider or increase linear drag

Issue 2: Jumping Too High/Low

Problem: Jump feels wrong Solution: Adjust the jumpForce value in your script (try values between 8-15)

Issue 3: Movement Feels Sluggish

Problem: Character doesn't respond quickly to input Solution: Increase acceleration value or reduce deceleration

Issue 4: Camera Jitter

Problem: Camera shakes when following player Solution: Increase smoothSpeed value or use FixedUpdate instead of LateUpdate


Pro Tips for Better Movement

Tip 1: Fine-tune Your Values

  • Move Speed: 5-8 for most platformers
  • Jump Force: 10-15 depending on gravity
  • Run Multiplier: 1.5-2.0 for noticeable difference

Tip 2: Add Movement Feedback

  • Use particle effects for landing
  • Add screen shake for impacts
  • Include audio cues for different actions

Tip 3: Test on Different Devices

  • Mobile: Use touch controls
  • Console: Test with gamepad
  • PC: Ensure keyboard feels responsive

Summary & Next Steps

Congratulations! You've successfully implemented responsive 2D character movement with jumping, running, and smooth camera following. Your character now feels professional and responsive to player input.

Key Takeaways:

  • Rigidbody2D provides realistic physics-based movement
  • Ground detection ensures proper jumping mechanics
  • Coyote time and jump buffering improve player experience
  • Smooth camera following enhances gameplay feel
  • Input customization allows for different control schemes

In the next lesson, "Lesson 5: Level Design & Platforms," you'll learn to design and implement platform mechanics, create level layouts, and build engaging obstacle courses for your platformer game.

Mini Challenge: Add a double jump ability to your character. Modify the jump system to allow one additional jump while in the air, and add a visual effect to show when the double jump is available.


Found this lesson helpful? Bookmark it for quick reference and share your movement implementation with the community!