Dos filosofías para mover un personaje#
Un personaje 3D no se mueve como una caja física normal: quieres control preciso (que responda al instante, suba escalones, no rebote). Hay dos caminos:
- CharacterController: un componente pensado para personajes. No es física real: tú decides cómo se mueve (incluida la gravedad), pero choca con paredes y sube escalones solo. Control total.
- Rigidbody: física real. El personaje obedece a fuerzas y colisiones. Realista, pero más difícil de hacer "responsivo" y propenso a resbalar o volcar.
- ¿Personaje que anda, un FPS/tercera persona, control preciso y responsivo? → CharacterController
- ¿El personaje es física (una bola que rueda, un coche, algo que empujan fuerzas)? → Rigidbody
- ¿Necesitas que otros objetos físicos lo empujen de forma realista? → Rigidbody (el CharacterController no reacciona a fuerzas por sí solo)
CharacterController: lo esencial#
Añade el componente (trae una cápsula de colisión) y muévelo con Move. Clave: la gravedad la aplicas tú.
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
public class MovimientoPersonaje : MonoBehaviour
{
[SerializeField] private float velocidad = 6f;
[SerializeField] private float gravedad = -20f;
[SerializeField] private float fuerzaSalto = 8f;
private CharacterController cc;
private float velocidadY;
void Awake() => cc = GetComponent<CharacterController>();
void Update()
{
// Movimiento horizontal según input
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Vector3 horizontal = (transform.right * x + transform.forward * z) * velocidad;
// Gravedad y salto (la Y la gestionamos nosotros)
if (cc.isGrounded)
{
velocidadY = -1f; // pequeño empuje para "pegarse" al suelo
if (Input.GetButtonDown("Jump")) velocidadY = fuerzaSalto;
}
else velocidadY += gravedad * Time.deltaTime;
Vector3 movimiento = horizontal + Vector3.up * velocidadY;
cc.Move(movimiento * Time.deltaTime); // un solo Move por frame
}
}isGrounded: si toca suelo (para saltar y resetear la caída).Move: mueve respetando colisiones; devuelve info de choques. Llama aMoveuna vez por frame con el vector total.SimpleMove: variante que aplica gravedad automática pero ignora el eje Y manual (no sirve para saltar).
Pendientes y escalones (gratis)#
El CharacterController resuelve solo dos cosas molestas, ajustables en el Inspector:
- Slope Limit: la pendiente máxima que puede subir (por encima, resbala).
- Step Offset: la altura de escalón que sube sin saltar (bordillos, escaleras).
- Skin Width: un margen fino que evita quedarse "pillado" en geometría; no lo pongas a 0.
isGrounded a veces parpadea en falso al bajar cuestas o escalones. El truco del velocidadY = -1f cuando está en suelo mantiene al personaje "pegado" y estabiliza la detección. Para casos exigentes, complementa con un raycast hacia abajo.
Si eliges Rigidbody#
Para un personaje con Rigidbody: congela la rotación (Constraints → Freeze Rotation X/Y/Z para que no vuelque), mueve en FixedUpdate con linearVelocity o MovePosition, y detecta suelo con un raycast. Da física real (rampas, empujones), a cambio de más trabajo para que se sienta preciso.
Errores frecuentes#
- Esperar que el CharacterController caiga solo: no aplica gravedad, la pones tú.
- Llamar a
Movevarias veces por frame (comportamiento raro): acumula en un vector y un soloMove. - Mover el CharacterController con
transform.position(ignora colisiones): usaMove. - Personaje Rigidbody que vuelca: falta congelar la rotación.
- Olvidar
Time.deltaTimeen elMove(velocidad dependiente de FPS).
Ponte a prueba#
Resumen#
Para mover un personaje 3D con control preciso, el CharacterController es la opción por defecto: choca con paredes y sube escalones (Step Offset) y pendientes (Slope Limit) solo, pero tú aplicas la gravedad y lo mueves con un único Move por frame usando isGrounded para el salto. Si el personaje es física (rueda, lo empujan fuerzas), usa un Rigidbody con la rotación congelada y movimiento en FixedUpdate. Combínalo con rotaciones suaves para orientarlo.