Monday, 15 June 2026

Unity Character Controller Explained for Complete Beginners

 If you have just opened Unity for the first time and someone told you to "add a Character Controller" to your player, this guide is for you. We are going to start from zero. No assumptions. No skipped steps.

By the end of this article, you will understand exactly what a Character Controller is, how it works under the hood, how to write a working movement script, and how to fix the errors that almost every beginner runs into during their first week with Unity.

This guide focuses on Unity 6 and Unity 2022 LTS, but everything here also applies to Unity 2021 and most older versions, since the Character Controller component has barely changed in years.



What Is a Character Controller in Unity?

The Character Controller is a built-in Unity component that lets you move a character around your scene while automatically handling collisions with walls, floors, and slopes - without using Unity's full physics engine.

Think of it as a capsule-shaped "bumper" attached to your player. When you tell it to move, Unity checks if that capsule would hit anything. If it would, Unity slides the capsule along the surface instead of letting it pass through. That is the entire job of the component, and it does that one job very well.

Technically, a Character Controller is a special kind of collider. It is not a Rigidbody, and it does not use gravity, mass, drag, or forces on its own. Everything related to movement and gravity has to be written by you in a script. This sounds like extra work, but it is actually what makes the Character Controller so popular for beginners - the movement feels predictable because you control every part of it.

Why the Character Controller Matters

Almost every tutorial that teaches "how to move your player in Unity" uses the Character Controller at some point, for a simple reason: it is the fastest way to get a character walking around a level without fighting against physics.

If you have ever tried to move a player using a Rigidbody and noticed that it slides too much, gets stuck on small bumps, or spins out of control when it touches a wall at an angle, you already know the kind of problems the Character Controller avoids by design.

A common mistake beginners make is assuming the Character Controller is "better" than a Rigidbody in every situation. It isn't better - it is different. It trades realistic physics for predictable, scriptable movement. Whether that trade-off is good for your game depends on what kind of game you are making, which is exactly what the next section covers.



When To Use a Character Controller

Use the Character Controller when:

  • You are making a first-person or third-person game where the player walks, runs, and jumps.
  • You want full control over how movement feels (speed, acceleration, jump height) without fighting physics forces.
  • Your character does not need to be pushed around by explosions, wind zones, or other physics objects.
  • You are following beginner tutorials and want something that "just works" for walking and basic collision.
  • You are building a prototype quickly and don't want to tune physics materials and Rigidbody settings yet.

When Not To Use a Character Controller

Avoid the Character Controller when:

  • You need your character to be affected by physics forces, such as being pushed by an explosion or knocked over.
  • You are building a ragdoll system, since ragdolls require Rigidbody and joints on each body part.
  • Your game involves vehicles, since vehicles almost always use Rigidbody with wheel colliders.
  • You need realistic momentum, where the character keeps sliding after you stop pressing a key, based on mass and friction.
  • You are making a game where multiple characters need to physically collide and bounce off each other in a realistic way.

This part usually confuses new users: it is completely possible to mix both. Many games use a Character Controller for the player and Rigidbody for everything else - crates, ragdolls, vehicles, and pickups. The Character Controller does not need to be the only physics-related component in your scene.

Advantages of the Character Controller

Advantage Why It Helps Beginners
Predictable movement Your character moves exactly the distance you tell it to, every frame, with no surprise sliding.
Built-in slope and step handling Walking up small steps and gentle slopes works automatically once you set slopeLimit and stepOffset.
Simple collision detection The isGrounded property tells you instantly if the character is touching the floor.
No physics tuning required You don't need to worry about mass, drag, angular drag, or physics materials to get basic movement working.
Great for tutorials and prototypes Most beginner courses use it because the setup is short and the results are immediate.

Disadvantages of the Character Controller

Disadvantage Why It Matters
No physics interactions It will not be pushed, pulled, or affected by forces unless you write that logic yourself.
You must script gravity manually Without your own gravity code, the character will float or fall through slopes incorrectly.
Can get stuck on small geometry seams Tiny gaps between floor tiles can sometimes catch the capsule if the level is not built cleanly.
Skin width issues A skin width that is too small or too large can cause jitter or the character getting stuck in walls.
Cannot be resized like a normal collider at runtime easily Changing height for crouching, for example, requires extra code to avoid clipping through geometry.

Project Setup: Getting Ready

Before writing any code, let's set up a clean project so you are not fighting your folder structure later.

Unity Version

This tutorial works in Unity 2021 LTS, Unity 2022 LTS, and Unity 6. The Character Controller component itself has not changed in any meaningful way across these versions, so you do not need to worry about which exact version you have installed.

Creating the Project

  1. Open Unity Hub and click New Project.
  2. Choose the 3D (Core) template. Avoid URP or HDRP templates for now - they add extra setup steps that are not related to character movement.
  3. Name your project something simple, like "CharacterControllerDemo".
  4. Click Create Project and wait for Unity to open.

Recommended Folder Structure

Inside the Assets folder, create these folders before you start adding anything:

  • Assets/Scripts - all your C# scripts go here.
  • Assets/Scenes - your test scenes.
  • Assets/Prefabs - your player prefab once it is working.
  • Assets/Materials - any materials for the ground or player.

When I first learned this concept, I skipped folder organization completely and ended up with forty files sitting loose in the Assets root folder. It does not affect whether your code works, but it will slow you down massively once your project grows. Set this up now while it costs nothing.

Building the Test Scene

You need two things in your scene before adding the Character Controller: a floor, and a player object.

  1. Right-click in the Hierarchy window and choose 3D Object > Plane. This will be your floor. Scale it up if you want more room to walk around (try a scale of 5, 1, 5).
  2. Right-click again and choose 3D Object > Capsule. This will represent your player visually.
  3. Rename the capsule to "Player" by double-clicking its name in the Hierarchy.
  4. Position the Player slightly above the floor, for example at position (0, 1, 0), so it does not start clipped into the ground.

Adding the Character Controller Component

Now for the main event.

  1. Select the Player object in the Hierarchy.
  2. In the Inspector, click Add Component.
  3. Type "Character Controller" and select it from the list.

You will notice that Unity automatically removes or disables the default Capsule Collider that came with the capsule mesh. That is expected. The Character Controller has its own built-in collision shape, so you do not need a separate collider for the player's body.

Understanding Each Property

The Inspector will show several fields. Here is what each one actually does, explained without jargon.

Property What It Does Typical Beginner Setting
Slope Limit The steepest angle (in degrees) the character can walk up. Anything steeper is treated like a wall. 45 - 50 degrees
Step Offset The maximum height of a "step" the character can climb without jumping, like stairs or curbs. 0.3 (roughly the height of a stair step)
Skin Width A small buffer zone around the capsule used to prevent the character from getting stuck in geometry due to floating point rounding. 0.08 (about 10% of the radius)
Min Move Distance The smallest distance the controller will actually move per frame. Movements smaller than this are ignored. 0 (leave default)
Center Offsets the capsule shape from the object's pivot point, useful if your model's origin is not at its feet. (0, 1, 0) if pivot is at the feet
Radius How wide the capsule is. This is essentially the character's "shoulder width" for collision purposes. 0.5
Height The total height of the capsule, from the bottom to the top. 2

A quick rule of thumb: Skin Width should usually be around 5-10% of the Radius. If you set Skin Width to 0, you will almost certainly run into jitter where the character vibrates slightly when standing still against a wall or slope. This is one of the most common "invisible" bugs in beginner projects, because the movement looks fine until the camera is perfectly still.



Writing Your First Movement Script

Now we get to the part everyone is waiting for: making the character actually move.

Create a new C# script in Assets/Scripts and name it PlayerMovement. Double-click it to open it in your code editor, delete everything inside, and replace it with the following.

using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    public float moveSpeed = 6f;
    public float gravity = -9.81f;
    public float jumpHeight = 1.5f;

    private CharacterController controller;
    private Vector3 velocity;
    private bool isGrounded;

    void Start()
    {
        controller = GetComponent<CharacterController>();
    }

    void Update()
    {
        isGrounded = controller.isGrounded;

        if (isGrounded && velocity.y < 0f)
        {
            velocity.y = -2f;
        }

        float moveX = Input.GetAxis("Horizontal");
        float moveZ = Input.GetAxis("Vertical");

        Vector3 move = transform.right * moveX + transform.forward * moveZ;
        controller.Move(move * moveSpeed * Time.deltaTime);

        if (Input.GetButtonDown("Jump") && isGrounded)
        {
            velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
        }

        velocity.y += gravity * Time.deltaTime;
        controller.Move(velocity * Time.deltaTime);
    }
}

Save the script, drag it onto the Player object in the Hierarchy (or use Add Component and search for "Player Movement"), and press Play. You should now be able to move the capsule with WASD and jump with the Space bar.

Line By Line Explanation

Let's break this script down piece by piece, so you understand exactly what every line is doing and why it is there.

The Variables at the Top

moveSpeed controls how many units per second the character moves. Because it is public, you can change it directly in the Inspector without editing code - useful for quickly testing what speed feels right.

gravity is set to -9.81, which matches real-world Earth gravity in meters per second squared. The Character Controller does not apply gravity for you, so this value is the entire reason your character falls instead of floating in place.

jumpHeight is the height in meters the character should reach at the top of a jump. We will use this in a formula later.

controller is a reference to the Character Controller component itself, so we can call its Move() function.

velocity is a Vector3 that stores the character's current falling or jumping speed on the Y axis. We keep track of this manually because, again, the Character Controller does not track velocity for us.

The Start Method

controller = GetComponent<CharacterController>(); runs once when the game starts. It finds the Character Controller component attached to this same GameObject and stores it in our variable so we can use it later.

Checking If Grounded

isGrounded = controller.isGrounded; reads a built-in property that becomes true if the character touched the ground during the last Move() call. This is the foundation for jumping and gravity reset logic.

if (isGrounded && velocity.y < 0f) { velocity.y = -2f; } looks unusual at first. Why set velocity to -2 instead of 0 when grounded? This is one of those small details that separates a working controller from a broken one. If you set velocity.y to exactly 0 every time the character is grounded, isGrounded can flicker to false for a single frame due to how the physics check works, which causes jittery, inconsistent ground detection. Keeping a small constant downward force (-2) keeps the character firmly "pressed" against the ground, making isGrounded reliable.

Reading Movement Input

Input.GetAxis("Horizontal") and Input.GetAxis("Vertical") read the A/D and W/S keys (or left stick on a controller) and return a value between -1 and 1. These are part of Unity's older Input Manager, which is still active by default in new projects.

Vector3 move = transform.right * moveX + transform.forward * moveZ; converts that input into a direction relative to the way the player is facing. transform.right is "sideways" from the player's perspective, and transform.forward is "forward" from the player's perspective.

controller.Move(move * moveSpeed * Time.deltaTime); is the actual movement call. Move() takes a Vector3 representing how far to move this frame, and it automatically handles collisions - sliding along walls instead of pushing through them. Multiplying by Time.deltaTime makes the speed consistent regardless of frame rate.

Jumping

if (Input.GetButtonDown("Jump") && isGrounded) checks if the Space bar was pressed this exact frame, and that the character is currently touching the ground (you cannot jump while already in the air with this code).

velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity); looks intimidating, but it comes from a basic physics formula that calculates the exact upward speed needed to reach a target height under a given gravity value. You do not need to memorize this formula - just know that increasing jumpHeight makes the jump go higher, and it will always reach approximately that height regardless of your gravity setting.

Applying Gravity

velocity.y += gravity * Time.deltaTime; reduces the Y velocity a little bit every frame, simulating acceleration due to gravity. The longer the character is in the air, the faster it falls - just like in real life.

controller.Move(velocity * Time.deltaTime); is a second, separate Move() call that applies the vertical movement (falling or jumping). It is kept separate from the horizontal movement call so that horizontal collisions and vertical collisions are resolved independently, which avoids some strange edge cases with slopes.



A Real Example: Walking Across Uneven Ground

Let's look at a simple example that shows why slopeLimit and stepOffset matter in practice.

Imagine your test scene has a flat floor, but you add a small ramp leading up to a higher platform, plus a curb-like ledge about 20cm tall along one edge.

With the default Slope Limit of 45 degrees, your character will walk smoothly up the ramp as long as its angle is 45 degrees or less. If you make the ramp steeper than that - say, 60 degrees - the character will stop at the bottom as if it hit a wall, even though visually it looks like a steep slope rather than a vertical surface.

With a Step Offset of 0.3, the character can walk straight onto the 20cm curb without jumping, because 0.2 is less than 0.3. If you lower Step Offset to 0.1, the same curb will now block the character completely, and they will need to jump to get on top of it.

This is exactly the kind of behavior that confuses beginners during playtesting - "why can my character climb this ramp but not that one?" - and now you know it almost always comes down to these two settings.

Beginner Mistakes

These are the mistakes that show up over and over again in beginner projects.

  • Forgetting to add gravity entirely. Without the velocity and gravity code, your character will either float in the air or fall straight through the floor depending on starting position.
  • Adding both a Rigidbody and relying on physics with a Character Controller. The Character Controller already handles its own movement. Adding a Rigidbody on the same object and expecting physics forces to move it as well leads to conflicting behavior.
  • Setting Skin Width to 0. This almost always causes jittering when the character is against a wall or slope.
  • Using Time.deltaTime inconsistently. If you multiply some movements by Time.deltaTime and forget it on others, your character will move at different speeds depending on frame rate.
  • Calling Move() multiple times per frame with conflicting directions without understanding that each call is resolved independently, which can cause strange sliding behavior on slopes.
  • Placing the player's pivot point at its center instead of its feet without adjusting the Center property, causing the character to spawn half-buried in the floor.
  • Expecting isGrounded to update instantly when the character is standing still with zero velocity - this is why the -2f trick from the line-by-line section exists.

Troubleshooting Guide

Here are the most common problems beginners report, and how to fix each one.

Problem: My character does not move at all.

Likely cause: The script is not attached to the same object as the Character Controller, or Input.GetAxis is returning 0 because the Input Manager axes were renamed or removed.

Fix: Confirm the script and the Character Controller are on the same GameObject. Open Edit > Project Settings > Input Manager and confirm "Horizontal", "Vertical", and "Jump" axes exist with their default bindings.

Problem: My character falls through the floor.

Likely cause: The floor object does not have a collider, or the Character Controller's Center/Radius/Height values place the capsule entirely above the floor's collision surface.

Fix: Make sure your floor has a Mesh Collider or Box Collider (a default Plane already includes one). Then check the Character Controller's Center value matches where the character's feet visually are.

Problem: My character vibrates or jitters when standing still.

Likely cause: Skin Width is too small, or the -2f grounding trick from the gravity code is missing.

Fix: Set Skin Width to roughly 5-10% of the Radius value, and double check your script resets velocity.y to a small negative number (not zero) when grounded.

Problem: My character gets stuck on stairs or small ledges.

Likely cause: Step Offset is too small for the height of your steps.

Fix: Increase Step Offset to slightly more than the height of a single step in your level. If steps are taller than about 0.4-0.5 units, consider redesigning them as a ramp instead, since very tall steps are not a good fit for Character Controller's step climbing.

Problem: My character slides down slopes it should be able to stand on.

Likely cause: The slope angle is close to or above your Slope Limit, or your gravity script is pushing the character downward faster than the slope-climbing logic can compensate.

Fix: Increase Slope Limit slightly, or reduce the gravity value for testing to see if the issue is angle-related or velocity-related.

Problem: Jump height feels inconsistent at different frame rates.

Likely cause: Movement code is running in Update() without Time.deltaTime applied consistently, or frame rate drops are large enough that gravity "steps" become noticeable.

Fix: Confirm every Move() call and every velocity change is multiplied by Time.deltaTime. For most beginner projects this is sufficient; advanced projects sometimes move physics-related code into FixedUpdate() instead.



Character Controller vs Rigidbody: Quick Comparison

Feature Character Controller Rigidbody
Movement method controller.Move() in script Forces, velocity, AddForce()
Gravity Must be scripted manually Applied automatically by Unity's physics engine
Affected by external forces No, unless scripted Yes, automatically
Slopes and steps Built-in Slope Limit and Step Offset Depends on collider shape and physics materials
Best for Player-controlled walking/running characters Vehicles, ragdolls, physics objects, pushable crates
Setup difficulty for beginners Lower - fewer settings to tune for basic movement Higher - requires understanding mass, drag, and physics materials

Optimization Tips for Mobile

If your final target is a mobile device, the Character Controller is generally lightweight, but there are still a few habits worth building early.

  • Avoid calling GetComponent() inside Update(). Cache references (like our controller variable) in Start() once, instead of looking them up every frame.
  • Keep your Move() calls simple. Avoid running expensive raycasts or physics overlap checks every single frame if you only need them occasionally.
  • Limit the number of Character Controllers in a scene. This component is meant for player characters, not for dozens of AI enemies - use simpler movement or NavMesh-based movement for those instead.
  • Test on a real device early. Movement that feels fine in the editor at 60+ FPS can feel different on a phone running at 30 FPS, especially around gravity and jump timing.
  • Consider Unity's newer Input System for mobile if you plan to support touch controls, since it has better built-in support for on-screen joysticks and buttons compared to the legacy Input Manager used in this tutorial.


 

Best Practices

  • Always cache your CharacterController reference in Start() or Awake(), not inside Update().
  • Keep gravity and jump logic in a single, well-commented script while you are learning, rather than spreading movement logic across multiple scripts.
  • Set Skin Width to roughly 5-10% of Radius, and avoid setting it to exactly 0.
  • Test your slope and step settings on an actual ramp and step built in your scene, not just on flat ground.
  • Separate horizontal movement and vertical (gravity/jump) movement into two Move() calls, as shown in this tutorial, to avoid odd slope interactions.
  • Once basic movement works, consider migrating input handling to Unity's Input System package for better controller and mobile support - this is covered in our linked Input System guide below.

Key Takeaways

  • The Character Controller is a capsule-shaped component for moving characters with built-in collision handling, but without physics forces or automatic gravity.
  • You must write your own gravity, jumping, and movement logic using controller.Move().
  • Slope Limit and Step Offset control what slopes and steps your character can walk over.
  • Skin Width should be small but not zero, to avoid jitter.
  • The -2f grounding trick keeps isGrounded reliable between frames.
  • Character Controller and Rigidbody solve different problems and can be used together in the same project for different objects.

Learning Roadmap

Once you are comfortable with everything in this guide, a sensible next path looks like this:

  1. Learn Unity's newer Input System package to replace the legacy Input.GetAxis calls used here.
  2. Add a simple animation system (Animator and Blend Trees) driven by your movement script's speed values.
  3. Build a basic camera controller for third-person or first-person view.
  4. Explore Rigidbody-based objects in the same scene, such as pushable crates, to understand how the two systems coexist.
  5. Look into mobile input and on-screen controls if your target platform includes phones or tablets.

Next Topics To Learn

No comments:

Post a Comment

How to Use ElevenLabs for Game Development: AI Voice Acting, NPC Dialogue, and Sound Design

Most indie games ship without voice acting. Not because developers do not want it, but because hiring voice actors is expensive, scheduling ...