Unity Character Movement - From Simple Walking to Advanced Controllers 2026

If you are new to Unity, you probably want something simple and true - make a character walk, jump, and stop sliding through walls. If you already ship games, you want something quieter and still true - pick one movement approach, write it down, and stop re-litigating it every sprint.
Character movement, in plain words, is the small system that turns what a person presses into where the character goes - while the ground, walls, and jumps behave the way you intended.
This article stays on that promise. No fake studio war stories. No “ultimate” claims. Where Unity’s docs are clear, we cite them. Where the answer depends on your game, we say so. Getting stuck on movement is common and fixable - it is not a sign you “do not get” game development.
In 2026, Unity 6 still gives you two main approaches people actually use for 3D player locomotion:
- CharacterController - a capsule you move with script calls. Good when you want direct, snappy control and built-in help with slopes and steps (Unity Manual).
- Rigidbody - the physics body. Better when being pushed, sliding, or colliding like a physical object matters to the fantasy.
Unity’s own teaching materials for Unity 6 walk Move / Look / Jump through the Input System (official scripting episode). Older Input.GetAxis code still works in many projects - especially during learning - but for a build you plan to share, wiring keyboard and gamepad to the same Move action is usually kinder to future you.
This guide is a ladder. Beginners can stop after a walking jump. Developers can keep going through Input System wiring, Rigidbody notes, and a short verification list. It sits beside Lesson 4 - Player Controller and Input Architecture and How to Add Steam Input Correctly in Unity 6.
| If you are… | Start here | A good stopping place |
|---|---|---|
| Learning Unity | Glossary → Quick Start → Level 1 | A capsule that walks and jumps, and you can explain why |
| Building a slice | Decision table → Levels 1–2 → checks | One motor choice written down + Input System Move/Jump |
| Searching a straight answer | FAQ | A sentence you can trust |
Time (honest estimates): about 20 minutes for first walk; often 1.5–2 hours for CharacterController + Input System if you are new to the Input Actions UI; another evening if you also try Rigidbody. Take breaks. Movement tuning rewards a clear head.
Why this is worth your evening in 2026
- Unity’s Character Controller docs still teach the same practical truth - this component is for player control that is not driven like a full physics puppet, and knobs like Skin Width, Step Offset, and Slope Limit matter when characters get stuck or refuse stairs (Manual, Scripting API).
- Official Unity 6 Input System videos show third-person Move / Look / Jump with keyboard and gamepad sharing actions - a pattern worth copying once your walk feels okay.
- The internet still argues CharacterController vs Rigidbody because both can be right. Your genre and feel goals decide - not a comment-thread winner.
A soft reminder - movement feel and animation feel are different problems. After the motor behaves, Game Animation Principles - From Sprites to 3D Character Movement helps the walk look alive.
Glossary - kind words for real concepts
| Term | What it means in practice |
|---|---|
| Transform | Position, rotation, scale. Moving only the transform will walk through solid colliders unless something else stops you. |
| Collider | The invisible shape used for “did we touch?” |
| CharacterController | A capsule that moves when you call Move or SimpleMove. It slides on walls and can climb steps/slopes you configure. It does not automatically react to forces the way a Rigidbody does. |
| Rigidbody | Lets the physics engine integrate forces, gravity, and many collisions. |
| Motor (in this article) | Your movement script - the bridge from input to motion. Not an official Unity type name. |
| Grounded | “I am touching something I am allowed to jump from.” isGrounded helps - it is not perfect on every mesh. |
| Input System | Unity’s action-based input package - one Move action can mean WASD and a left stick. |
Time.deltaTime |
Seconds since the last frame. Multiplying movement by it keeps speed steadier when frame rate changes. |
If a later sentence uses a bold word from this table, it is okay to glance back. That is learning, not weakness.
How character movement works in Unity (short and true)
- Read input - keys or a stick become a 2D value (left/right, forward/back).
- Turn that into a direction in the world - often relative to the camera on the flat ground (XZ).
- Apply motion -
CharacterController.Move(...)or change a Rigidbody’s velocity / forces. - Handle up and down - with CharacterController you usually write gravity and jump yourself; with Rigidbody, gravity often comes from physics and you still tune jump.
- Check the result - walls should stop you, intended stairs should work, jump should match your design, keyboard and gamepad should feel fair.
That is the whole shape. The rest of this page is a careful way to build it without pretending every project needs the same ending.
Decision table - choose with your game, not with shame
| If your game needs… | CharacterController often fits | Rigidbody often fits |
|---|---|---|
| Direct, snappy walk (many FPS / adventure feel goals) | Yes | Possible, but you will tune friction and forces |
| Stairs / slopes without writing all the cast logic yourself | Step Offset and Slope Limit help | You usually build more yourself |
| Knockback, ice, explosions as core toys | Possible with extra script work | More natural home |
| Player should shove crates like a physical body | Possible via OnControllerColliderHit |
Often simpler |
| Online prediction | Many teams find kinematic deltas easier to reason about | Doable - usually more moving parts |
| 2D side-scroller / endless runner | Often a different path (Rigidbody2D) | See endless runner tutorial |
A gentle beginner default: start with CharacterController. Switch when a real design need appears - not because a video title said you must.
A gentle team habit: one README line is enough - for example Player motor: CharacterController (we move it in script; we are not using AddForce on the player). That is for clarity, not bureaucracy.
Honest cost note: CharacterController often means fewer early physics-tuning hours. Rigidbody often means more time with friction, interpolation, and FixedUpdate discipline - and fewer custom “please push this crate” hacks later. Neither is morally better.
Beginner Quick Start - walk a capsule (~20 minutes)
What you need
- Unity Hub and a Unity 6.x project (3D template is fine - URP or Built-in).
- Comfort with Hierarchy, Inspector, and Play mode.
- A Plane (or Terrain) and a Capsule.
- About twenty quiet minutes for this section.
If C# still feels foreign, it is okay to pause and skim Beginners Guide to Game Programming in C#, then come back. Order is flexible.
Step 1 - Scene setup
You are done with this step when: Play mode shows a capsule resting on the ground.
- Create an empty GameObject named
Player. - Add a Capsule (as the player, or as a child mesh).
- Place it so the feet sit on the Plane - not floating, not buried.
- Optional but helpful later - put the ground on a Layer named
Ground.
Step 2 - Transform walking (for learning only)
This version is allowed to walk through walls. That is intentional. You are learning direction math without fighting collision yet.
- Add a script
TransformWalker.csonPlayer. - Use:
using UnityEngine;
public class TransformWalker : MonoBehaviour
{
public float speed = 5f;
void Update()
{
float x = Input.GetAxisRaw("Horizontal");
float z = Input.GetAxisRaw("Vertical");
Vector3 direction = new Vector3(x, 0f, z);
if (direction.sqrMagnitude < 0.01f)
return;
direction.Normalize();
transform.Translate(direction * speed * Time.deltaTime, Space.World);
transform.forward = direction;
}
}
- Press Play and use WASD or arrows.
You are done with this step when: the capsule moves and faces where it goes; walking into a Cube passes through it.
What this taught you: input → direction → move a little each frame using Time.deltaTime.
Common snags (you did not fail)
- Forgot
Time.deltaTime- speed changes when FPS changes. - Expected walls to stop you - transform-only movement does not do that.
- Diagonal feel too fast - we normalize so length stays fair when two keys are held.
Small practice: while Play is running, change speed in the Inspector between 2 and 8. Feel the difference. Curiosity is useful here.
When you are ready for walls and stairs, disable TransformWalker and move to Level 1. Keep the same idea of “direction from input.”
Level 1 - CharacterController walk, gravity, and jump
Unity’s docs describe CharacterController as a specialized capsule you tell to move - it handles collision constraints as it goes, and it does not react to forces on its own (Manual). That is why gravity and jump usually live in your script.
Step 3 - Add the component
- If there is a Rigidbody on
Player, remove it for this path - running both without a clear plan causes confusing fights. - Add Component → Character Controller.
- Starting values that match Unity’s human-scale guidance in spirit (always tune for your mesh):
- Height near 2
- Radius near 0.3–0.5
- Skin Width about 10% of Radius (Unity warns that too-small Skin Width is a common “stuck” cause)
- Step Offset often around 0.1–0.4 for a human-sized capsule - and it must stay lower than Height
- Min Move Distance at 0 unless you are deliberately fighting jitter
- Slope Limit - start moderate (many people try 45) and raise only if honest ramps still refuse you
Step 4 - A motor you can understand
Add CharacterMotor.cs:
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
public class CharacterMotor : MonoBehaviour
{
public float moveSpeed = 5f;
public float jumpHeight = 1.2f;
public float gravity = -20f;
CharacterController _controller;
Vector3 _velocity;
Vector2 _moveInput;
void Awake()
{
_controller = GetComponent<CharacterController>();
}
public void SetMoveInput(Vector2 input)
{
_moveInput = input;
}
void Update()
{
// Temporary keyboard poll so you can feel movement before Input System (Level 2).
// This is a learning bridge - replace it when Move/Jump actions are wired.
if (_moveInput.sqrMagnitude < 0.01f)
{
_moveInput = new Vector2(
Input.GetAxisRaw("Horizontal"),
Input.GetAxisRaw("Vertical"));
}
bool grounded = _controller.isGrounded;
if (grounded && _velocity.y < 0f)
_velocity.y = -2f;
Vector3 move = new Vector3(_moveInput.x, 0f, _moveInput.y);
if (move.sqrMagnitude > 1f)
move.Normalize();
_controller.Move(move * moveSpeed * Time.deltaTime);
if (Input.GetButtonDown("Jump"))
TryJump();
_velocity.y += gravity * Time.deltaTime;
_controller.Move(_velocity * Time.deltaTime);
}
public void TryJump()
{
if (!_controller.isGrounded)
return;
// Standard kinematic jump speed from desired height and gravity.
_velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
}
}
You are doing well when
- Walking into a wall slides instead of vanishing through it.
- Reasonable stairs under your Step Offset can be climbed.
- Jump arcs and lands; you can jump again on the ground.
- You remember
isGroundedcan flicker on some meshes - if jump feels unfair, a short downward sphere check later is a normal improvement, not a confession of failure.
Why gravity here is -20, not -9.81: many games choose a stronger pull so jumps feel less floaty. That is a feel choice, not a physics exam. Tune jumpHeight and gravity together until the arc matches a reference you like.
Knobs that honestly matter
| Knob | When it hurts | What people usually try |
|---|---|---|
| Skin Width too small | Stuck in corners | Raise toward ~10% of Radius |
| Step Offset too high | Odd stair behavior / errors | Keep it below Height |
| Slope Limit too low | Soft ramps feel like walls | Raise carefully |
| Gravity too weak | Floaty jump | More negative gravity |
Time.deltaTime applied twice to the same motion |
Weirdly slow movement | Remember Move wants this frame’s displacement |
SimpleMove vs Move (true to the API): SimpleMove moves with a speed and applies gravity for you, but it ignores the Y component of the speed you pass. Once you want a custom jump, Move is the usual path (Scripting API).
Two Move calls in one Update: one for walking, one for vertical velocity, is a common pattern. What you want to avoid is multiplying the same displacement by deltaTime twice by accident.
Level 2 - Input System Move and Jump (when you are ready)
Finishing Level 1 with the temporary keyboard poll is okay. You are allowed to learn in stages.
When you want keyboard and gamepad to share one Move vector - or you are preparing a build others will play - set up Input Actions the way Unity demonstrates in their Unity 6 Input System series.
Step 5 - Create the asset
- Create → Input Actions → name it
PlayerControls. - Add an Action Map called
Player. - Move - Value, Vector2 - WASD composite + left stick.
- Jump - Button - Space + gamepad South.
- Save the asset.
- Add Player Input on
Player, assign the asset, Default MapPlayer. Invoke Unity Events is a friendly starting Behavior for many learners.
Step 6 - Bridge into your motor
using UnityEngine;
using UnityEngine.InputSystem;
public class CharacterMotorInputBridge : MonoBehaviour
{
public CharacterMotor motor;
public void OnMove(InputAction.CallbackContext ctx)
{
motor.SetMoveInput(ctx.ReadValue<Vector2>());
}
public void OnJump(InputAction.CallbackContext ctx)
{
if (ctx.performed)
motor.TryJump();
}
}
Hook Player Input events to these methods, then remove the temporary Input.GetAxisRaw / GetButtonDown poll from CharacterMotor so one path owns input.
Check Project Settings → Player → Active Input Handling. If the Editor moves and a build does not, this setting is a frequent, boring, fixable cause - not a mystery about your worth as a programmer.
When glyphs and Steam Deck prompts matter, continue with Steam Input in Unity 6.
Level 3 - When Rigidbody (and friends) earn their keep
Choose Rigidbody when physics is the toy - knockback, ice, crates that must shove you, a vehicle that hands control back to on-foot movement. If you only want a clean walk, CharacterController remains a fair adult choice.
Camera-relative move (third-person)
World-axis WASD feels wrong as soon as the camera orbits. Flatten camera forward/right onto the ground plane:
Transform cam = Camera.main.transform;
Vector3 forward = cam.forward;
forward.y = 0f;
forward.Normalize();
Vector3 right = cam.right;
right.y = 0f;
right.Normalize();
Vector3 move = forward * _moveInput.y + right * _moveInput.x;
In a finished project, cache the camera transform instead of calling Camera.main every frame if that becomes hot.
Rigidbody sketch (FixedUpdate)
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class RigidbodyMotor : MonoBehaviour
{
public float moveSpeed = 5f;
public float jumpImpulse = 6f;
public LayerMask groundMask;
public Transform groundCheck;
public float groundRadius = 0.2f;
Rigidbody _rb;
Vector2 _moveInput;
bool _jumpQueued;
void Awake()
{
_rb = GetComponent<Rigidbody>();
_rb.constraints = RigidbodyConstraints.FreezeRotation;
_rb.interpolation = RigidbodyInterpolation.Interpolate;
}
public void SetMoveInput(Vector2 input) => _moveInput = input;
public void QueueJump() => _jumpQueued = true;
void FixedUpdate()
{
Vector3 wish = new Vector3(_moveInput.x, 0f, _moveInput.y);
if (wish.sqrMagnitude > 1f)
wish.Normalize();
// Unity 6 projects may expose linearVelocity; older samples use velocity.
// Use the property your installed API offers - both express the same idea here.
Vector3 velocity = _rb.linearVelocity;
velocity.x = wish.x * moveSpeed;
velocity.z = wish.z * moveSpeed;
_rb.linearVelocity = velocity;
bool grounded = Physics.CheckSphere(
groundCheck.position, groundRadius, groundMask);
if (_jumpQueued && grounded)
{
velocity = _rb.linearVelocity;
velocity.y = jumpImpulse;
_rb.linearVelocity = velocity;
}
_jumpQueued = false;
}
}
True and kind rules
- Read jump in Update (or Input callbacks); apply physics changes in FixedUpdate.
- Interpolation helps cameras look less jittery.
- Do not stack CharacterController and a dynamic Rigidbody on the same body without a written plan.
- If
linearVelocitydoes not compile, your package may still usevelocity- check the Scripting API for your Unity version rather than forcing a name from a blog.
Optional advanced ideas (only if you need them)
- Soft acceleration with
Mathf.MoveTowardsso starts and stops feel less binary. - Coyote time and jump buffering (often around a tenth of a second) for kinder platforming.
- Root motion for grounded animation, with CharacterController still owning collisions.
OnControllerColliderHitto nudge Rigidbody props.- A short SphereCast when
isGroundedflickers on uneven meshes.
None of these are mandatory to “count” as a real developer.
Common problems - symptoms first, dignity intact
| What you feel | Likely cause | Calm next try |
|---|---|---|
| Turns but will not move | Root motion or wrong root | Turn off Apply Root Motion, or move the object that owns the controller |
| Stuck in level geo | Skin Width / spawn overlap | Raise Skin Width; unbury the spawn |
| Jump feels floaty or sticky | Gravity / grounded reset | Tune gravity; keep a small downward stick when grounded |
| Editor yes, build no | Active Input Handling | Align Project Settings with how you read input |
| Camera jitters | Rigidbody without interpolate | Enable Interpolate; follow in LateUpdate |
| Climbs walls you hate | Slope Limit / missing checks | Lower slope allowance; stop walking into steep normals |
| Diagonal is too fast | Missing normalize | Normalize when input length is greater than 1 |
A gentle practice path (one evening, with breaks)
| Block | About | Done when |
|---|---|---|
| A - Transform walk | 20 min | Capsule faces where it walks |
| B - CharacterController + jump | 40 min | Walls slide; jump works on flat ground |
| C - Input System | 30 min | Stick and keyboard both move |
| D - Note what you chose | 15–20 min | Short clip + honest checklist below |
If you share a clip with a friend, ask a human question - “does this jump feel floaty to you?” Tuning is craft. It is not evidence you are behind.
Verification checklist (mark only what you actually tested)
Use this as a memory aid, not a badge.
| Check | Question | Mark true only if… |
|---|---|---|
| M1 | Did we write down CharacterController or Rigidbody and why? | The note exists |
| M2 | Does speed feel stable if FPS changes a lot? | You tried a low and high frame-rate situation, or an equivalent test |
| M3 | Walls, corners, stairs, a real ramp? | You walked them yourself |
| M4 | Jump matches the design (no surprise double jump)? | You tried the cases you care about |
| M5 | Keyboard and gamepad both fair at full input? | You compared them |
| M6 | Did we save what we learned? | Notes or JSON updated after the tests |
Receipt template (fill after testing - leave false until then)
{
"schema": "movement_controller_receipt_v1",
"project": "your-game-slug",
"unity_version": "6000.x.y",
"motor": "CharacterController",
"input": "InputSystem.PlayerControls.Player",
"gates": {
"M1_motor_choice_logged": false,
"M2_framerate_independence": false,
"M3_collision_smoke": false,
"M4_jump_honesty": false,
"M5_input_parity": false,
"M6_notes_saved": false
},
"notes": "",
"movement_controller_ok": false,
"verified_at": ""
}
Flip values to true only when the matching check is honestly done. A receipt that lies helps nobody - including future you.
A calm Monday list
- Finish blocks A–C before adding dash or crouch.
- Prefer CharacterController until physics is clearly part of the fun.
- Tune Skin Width and Step Offset before rewriting everything.
- Record twenty to thirty seconds of walk and jump for a second pair of eyes.
- Save what you chose so the next person is not guessing.
Key takeaways
- Character movement is input → direction → apply motion → handle vertical → verify.
- Transform walking is a teaching tool; CharacterController is a common first shippable 3D walk.
- CharacterController does not invent gravity or jump for you when you use
Move- your script does. - Skin Width and Step Offset fix many “stuck” and stair problems without drama.
- The Input System lets one Move action serve keyboard and gamepad - learn it when you are ready.
- Rigidbody when physics is the point; CharacterController when snappy authored control is the point.
- Mark checklist items true only after you test them.
- Animation polish comes after the motor tells the truth - see animation principles.
FAQ
How does character movement work in Unity?
You read planar input, turn it into a world direction (often camera-relative), then apply it with CharacterController.Move or a Rigidbody velocity/force path. With CharacterController and Move, you typically write gravity and jump yourself. With Rigidbody, the physics solver handles much of falling and collision response, and you still tune jump and friction.
How do I make a character walk in Unity as a beginner?
Start with a Capsule and a tiny script that reads Horizontal/Vertical, builds a flat direction, and uses transform.Translate with Time.deltaTime so you can see the math. Then add CharacterController and call Move so walls and steps can matter.
Should I use CharacterController or Rigidbody?
There is no universal winner. CharacterController fits many snappy FPS/adventure goals and helps with steps/slopes. Rigidbody fits when physical reactions are central. Mixing both on one body without a plan usually creates pain.
Why is my CharacterController not moving?
Check that Move runs with a real displacement, that input is actually non-zero, that Active Input Handling matches how you read input, that a Rigidbody is not fighting the controller, that Skin Width is not trapping you, and that root motion is not eating motion on the wrong object.
What is the difference between Move and SimpleMove?
SimpleMove takes a speed, applies gravity for you, and ignores the Y component of that speed. Move takes a full displacement vector - the usual choice once jump or custom vertical motion exists. Confirm details on the CharacterController API for your Unity version.
How do I add gamepad support without rewriting movement?
Bind Move as a Vector2 action to both WASD and the left stick, then feed that vector into the same motor. For Steam glyphs and Deck-oriented QA, see Steam Input in Unity 6.
Is CharacterController “good enough” for multiplayer?
It can be a reasonable motor for predicted kinematic motion, but multiplayer still needs ownership, authority, and reconciliation decisions. Choosing CharacterController does not finish netcode - see real-time multiplayer sync when you go online.
Next steps
- Walk the practice path at your pace and mark only real checks.
- Grow input structure with Lesson 4 when you want architecture, not just a first walk.
- Add store-aware prompts with Steam Input in Unity 6 if you need them.
- Soften the walk visually with animation principles.
- Keep exploring calmly in the Unity guide.
If someone asks why you are not using AddForce on the player, you can answer gently - because you chose a motor that matches the feel you want, and you wrote that choice down. That is enough.