U
13 / 22 · 8 min
FAQ

Moving a character: which of the three ways is yours

the short answer

How do I make a character move?

Pick one of three, never two: CharacterController for a player you control directly, Rigidbody for anything physics should push around, and transform.Translate only for things that do not collide. Multiply every speed by Time.deltaTime or the game runs faster on a faster machine.

There are three ways to move something in Unity and they do not mix. Picking the wrong one is why a character sinks into walls, or refuses to move at all, or moves at twice the speed on a 120 Hz phone.

animated diagram

The same wall, met three ways. Only the CharacterController slides along it, only the Rigidbody can shove the crate, and transform.Translate does not know the wall is there.

Pick one — the whole decision

  • CharacterController — a player you drive directly. It slides along walls, climbs steps and never gets pushed around by physics. This is what most third-person and first-person games use.

  • Rigidbody — anything physics should be in charge of: a rolling ball, a crate you shove, a ragdoll. You ask it to move; the physics engine decides what actually happens.

  • transform.Translate — only for things that never collide with anything: a floating pickup, a moving cloud, a UI element. It teleports; it does not push.

CharacterController

Add the component, then move it with Move(). It takes a distance for this frame, not a speed, which is why deltaTime is in there.

PlayerMove.cs — attach to the object that has the CharacterController
using UnityEngine;

[RequireComponent(typeof(CharacterController))]
public class PlayerMove : MonoBehaviour
{
    public float speed = 6f;
    public float gravity = -20f;

    CharacterController cc;
    float verticalSpeed;

    void Awake()
    {
        cc = GetComponent<CharacterController>();
    }

    void Update()
    {
        // Input is a direction, length 0 to 1. Never a distance.
        Vector3 input = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
        if (input.sqrMagnitude > 1f) input.Normalize();

        // Gravity has to be applied by hand: a CharacterController ignores physics.
        if (cc.isGrounded && verticalSpeed < 0f) verticalSpeed = -2f;
        verticalSpeed += gravity * Time.deltaTime;

        Vector3 move = input * speed + Vector3.up * verticalSpeed;
        cc.Move(move * Time.deltaTime);
    }
}

Rigidbody

Two rules and the rest follows: write to the Rigidbody, never to transform, and do it in FixedUpdate, not Update. Physics runs on its own clock, and writing to transform tells the physics engine nothing — it will happily move the object back.

BallMove.cs — attach to the object that has the Rigidbody
using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
public class BallMove : MonoBehaviour
{
    public float speed = 8f;

    Rigidbody rb;
    Vector3 input;

    void Awake()
    {
        rb = GetComponent<Rigidbody>();
    }

    void Update()
    {
        // Read input here: Update runs once per frame, so no press is missed.
        input = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
        if (input.sqrMagnitude > 1f) input.Normalize();
    }

    void FixedUpdate()
    {
        // Move here: FixedUpdate runs on the physics clock.
        rb.MovePosition(rb.position + input * speed * Time.fixedDeltaTime);
    }
}

Why the input is read in Update and used in FixedUpdate

  • FixedUpdate runs 50 times a second by default; your game might render at 120. Reading Input.GetKeyDown in FixedUpdate therefore misses presses that happened between physics ticks.

  • Use Time.fixedDeltaTime inside FixedUpdate and Time.deltaTime inside Update. Unity actually returns the right one either way, but writing the matching name keeps the intent readable.

  • Set the Rigidbody's Interpolate to Interpolate if the camera follows it, or the movement looks like it is vibrating at 50 Hz.

transform.Translate

Drifter.cs — for something with no collider, or one set to trigger
void Update()
{
    transform.Translate(Vector3.forward * speed * Time.deltaTime);
}

the lesson that builds this

Movement, jumping and landing on blocks

This post is a standalone recipe. In the course, the same thing is built as part of the one project that runs through all five tabs — Character & Combat, lesson 02.