Unity 2D Character Movement Implementation Guide

Overview

This guide demonstrates how to implement a robust 2D character movement system in Unity using a state machine architecture. We'll cover animation setup, physics configuration, input handling, and directional flipping to create responsive character controls suitable for platformer games.

Animation System Setup

Preparing the Sprite Assets

Begin by importing your sprite sheet into the Unity project. For this implementation, we'll assume you have a pre-sliced sprite sheet containing character frames. Create a child GameObject under your player prefab named "SpriteRenderer" and position it at the parent object's pivot point (typically X: 0.4 for centered alignment).

Add an Animator component to this child object. Create a new Animator Controller (Character_AC.controller) and assign it to the component. We'll need two animation states:

  • Idle: Frames 0-5 at 10 samples per second
  • Run: Frames 6-13 at 10 samples per second

Create these animations using the Animation window, ensuring you enable "Show Sample Rate" from the panel's options menu to adjust the timing.

Configuring State Transitions

In the Animator Controller, create two Boolean parameters:

  • IsIdle
  • IsRunning

Establish transitions between states with the following conditions:

  • Idle → Run: Trigger when IsRunning is true
  • Run → Idle: Trigger when IsRunning is false

For both transitions, disable "Has Exit Time" and set "Transition Duration" to 0 for instant responsiveness.

Physics Environment Configuration

Create a platform GameObject (2D Object > Sprites > Square) and scale it appropriately. Both the platform and character require colliders for collision detection:

Platform: Add BoxCollider2D

Character: Add CapsuleCollider2D and adjust its bounds to match the sprite dimensions using the edit collider tool.

Add a Rigidbody2D to the character with these critical settings:

  • Constraints: Freeze Z-axis rotation to prevent tipping
  • Interpolate: Set to "Interpolate" for smooth visual movement
  • Collision Detecsion: Set to "Continuous" to prevent tunneling

State Machine Implementation

Base State Class

We'll create a base class that all character states inherit from, handling common functionality like animation parameters and input detection.

public abstract class CharacterState
{
    protected CharacterStateMachine stateMachine;
    protected CharacterController controller;
    protected Rigidbody2D rigidbody;
    
    protected float horizontalInput;
    private string stateParameter;

    public CharacterState(CharacterStateMachine _stateMachine, CharacterController _controller, string _animParam)
    {
        stateMachine = _stateMachine;
        controller = _controller;
        stateParameter = _animParam;
    }

    public virtual void OnEnter()
    {
        controller.Animator.SetBool(stateParameter, true);
        rigidbody = controller.Rigidbody;
    }

    public virtual void OnUpdate()
    {
        horizontalInput = Input.GetAxisRaw("Horizontal");
    }

    public virtual void OnExit()
    {
        controller.Animator.SetBool(stateParameter, false);
    }
}

Idle State

The idle state monitors for horizontal input to transition to movement.

public class IdleState : CharacterState
{
    public IdleState(CharacterStateMachine _sm, CharacterController _ctrl, string _param) 
        : base(_sm, _ctrl, _param) { }

    public override void OnUpdate()
    {
        base.OnUpdate();
        
        if (horizontalInput != 0)
        {
            stateMachine.ChangeState(controller.runState);
        }
    }
}

Run State

The run state applies velocity and returns to idle when input ceases.

public class RunState : CharacterState
{
    public RunState(CharacterStateMachine _sm, CharacterController _ctrl, string _param) 
        : base(_sm, _ctrl, _param) { }

    public override void OnUpdate()
    {
        base.OnUpdate();
        
        controller.ApplyMovement(horizontalInput * controller.runSpeed, rigidbody.velocity.y);
        
        if (horizontalInput == 0)
        {
            stateMachine.ChangeState(controller.idleState);
        }
    }
}

Character Controller

The main controller orchestrates compoennts and provides movement/rotation utilities.

public class CharacterController : MonoBehaviour
{
    [Header("Movement Properties")]
    public float runSpeed = 8f;
    public int FacingDirection { get; private set; } = 1;
    private bool isFacingRight = true;

    public Animator Animator { get; private set; }
    public Rigidbody2D Rigidbody { get; private set; }
    
    public CharacterStateMachine StateMachine { get; private set; }
    public IdleState idleState { get; private set; }
    public RunState runState { get; private set; }

    private void Awake()
    {
        StateMachine = new CharacterStateMachine();
        
        idleState = new IdleState(StateMachine, this, "IsIdle");
        runState = new RunState(StateMachine, this, "IsRunning");
        
        Animator = GetComponentInChildren<Animator>();
        Rigidbody = GetComponent<Rigidbody2D>();
    }

    private void Start()
    {
        StateMachine.Initialize(idleState);
    }

    private void Update()
    {
        StateMachine.currentState.OnUpdate();
    }

    public void ApplyMovement(float xVelocity, float yVelocity)
    {
        Rigidbody.velocity = new Vector2(xVelocity, yVelocity);
        UpdateFacingDirection(xVelocity);
    }

    public void FlipCharacter()
    {
        FacingDirection *= -1;
        isFacingRight = !isFacingRight;
        transform.Rotate(0f, 180f, 0f);
    }

    private void UpdateFacingDirection(float xVelocity)
    {
        if (xVelocity > 0f && !isFacingRight)
            FlipCharacter();
        else if (xVelocity < 0f && isFacingRight)
            FlipCharacter();
    }
}

Directional Flipping Logic

The flipping mechanism ensures the character sprite faces its movement direction. We track facing direction using FacingDirection (1 for right, -1 for left) and a boolean flag. The FlipCharacter() method inverts these values and rotates the transform 180° around the Y-axis.

Crucially, we use the raw input value rather than the rigidbody's velocity to determine flip timing. This prevents unwanted flipping when the character decelerates, as the rigidbody may retain small residual velocity values even after input stops.

Final Integration

With all components in place, the system operates as follows:

  1. The CharacterStateMachine initializes in the IdleState
  2. Each frame, the active state's OnUpdate() polls horizontal input
  3. When input is detected, the state machine transitions to RunState
  4. RunState applies velocity and triggers the running animation
  5. The controller continuously updates the character's facing direction based on movement
  6. When input stops, the system returns to idle state

This architecture provides a scalable foundation for adding jump, attack, and other mechanics through additional state classes.

Tags: unity 2D Game Development Character Movement State Machine animation

Posted on Sat, 09 May 2026 03:13:04 +0000 by bsamson