🎮 UnityDocs

Movimiento 3D en primera persona (FPS)

Receta completa de un controlador en primera persona con CharacterController: movimiento relativo a la cámara, gravedad, salto y mirar con el ratón. Con la diferencia clave frente a Rigidbody.

🍳 RecetaIntermedio✅ RevisadoUnity 6.0 LTSActualizado 2026-07-15
DificultadConceptual Código Matemáticas
Tiempo📖 Lectura 7 min · ⌨️ Práctica 30 min

CharacterController vs Rigidbody#

Para un personaje que anda con precisión (shooters, plataformas 3D), el CharacterController suele ir mejor que un Rigidbody: da control directo y sin "resbalones", pero la gravedad la gestionas tú. Añade el componente Character Controller al jugador (sustituye al collider) y quita cualquier Rigidbody. Ver el porqué en Rigidbody.

Requisitos#

Un objeto "Jugador" con Character Controller, y una Camera como hija de ese objeto, colocada a la altura de los ojos.

El movimiento#

C#
using UnityEngine;

[RequireComponent(typeof(CharacterController))]
public class JugadorFPS : MonoBehaviour
{
    [SerializeField] private float velocidad = 6f;
    [SerializeField] private float fuerzaSalto = 8f;
    [SerializeField] private float gravedad = -20f;

    private CharacterController cc;
    private Vector3 velocidadVertical;   // guarda caída/salto en Y

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

    void Update()
    {
        // Movimiento en el plano, relativo a hacia dónde mira el jugador
        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");
        Vector3 mov = transform.right * x + transform.forward * z;
        cc.Move(mov * velocidad * Time.deltaTime);

        // Gravedad y salto
        if (cc.isGrounded)
        {
            velocidadVertical.y = -1f;   // pequeño valor para "pegarse" al suelo
            if (Input.GetButtonDown("Jump"))
                velocidadVertical.y = fuerzaSalto;
        }
        else
        {
            velocidadVertical.y += gravedad * Time.deltaTime;
        }
        cc.Move(velocidadVertical * Time.deltaTime);
    }
}
Por qué -1 cuando está en el suelo

En vez de 0, ponemos una velocidad vertical negativa pequeña estando en el suelo. Así el CharacterController mantiene contacto y isGrounded responde de forma fiable en rampas y escalones, en vez de parpadear entre "en suelo" y "en aire".

Mirar con el ratón#

La cámara (hija) gira en vertical; el cuerpo del jugador gira en horizontal. Bloqueamos el cursor al centro.

C#
using UnityEngine;

public class MirarConRaton : MonoBehaviour
{
    [SerializeField] private float sensibilidad = 200f;
    [SerializeField] private Transform cuerpo;   // el objeto Jugador (padre)
    private float rotacionX;

    void Start()
    {
        Cursor.lockState = CursorLockMode.Locked;   // cursor al centro, oculto
    }

    void Update()
    {
        float ratonX = Input.GetAxis("Mouse X") * sensibilidad * Time.deltaTime;
        float ratonY = Input.GetAxis("Mouse Y") * sensibilidad * Time.deltaTime;

        // Vertical: se acumula y se limita para no dar la voltereta
        rotacionX = Mathf.Clamp(rotacionX - ratonY, -85f, 85f);
        transform.localRotation = Quaternion.Euler(rotacionX, 0f, 0f);

        // Horizontal: gira todo el cuerpo
        cuerpo.Rotate(Vector3.up * ratonX);
    }
}
Movimiento en Update, no en FixedUpdate

A diferencia del Rigidbody, el CharacterController se mueve con cc.Move en Update (usa Time.deltaTime). No es física de Rigidbody, así que no va en FixedUpdate.

Liberar el cursor#

Con el cursor bloqueado no puedes usar menús. Libéralo (por ejemplo al pausar) así:

C#
if (Input.GetKeyDown(KeyCode.Escape))
{
    Cursor.lockState = CursorLockMode.None;
    Cursor.visible = true;
}

Errores frecuentes#

  • Mezclar Rigidbody y CharacterController en el mismo objeto (elige uno).
  • Mover el CharacterController en FixedUpdate (va en Update).
  • No limitar la rotación vertical y acabar mirando "del revés".
  • Olvidar bloquear el cursor y que la cámara se comporte raro.
  • Poner la cámara como objeto suelto en vez de hija del jugador.

Resumen#

El CharacterController da un control en primera persona preciso: mueve con cc.Move en Update relativo a transform.forward/right, aplica la gravedad tú mismo acumulándola en la Y, y salta comprobando isGrounded. La cámara (hija) mira en vertical con el ratón (limitada con Clamp) y el cuerpo gira en horizontal, con Cursor.lockState bloqueado. Para tercera persona, combínalo con Cinemachine.

Fuentes y para profundizar