1. Introducción
Godot 4 introdujo grandes cambios en el movimiento de personajes. Los antiguos nodos KinematicBody2D y KinematicBody3D pasaron a llamarse CharacterBody2D y CharacterBody3D, y la API de move_and_slide() se rediseñó por completo. Tanto si migras desde Godot 3 como si empiezas de cero, esta guía cubre todo lo que necesitas saber.
CharacterBody2D y CharacterBody3D son cuerpos físicos pensados para personajes controlados por código. A diferencia de RigidBody, no reaccionan automáticamente a las fuerzas — controlas su movimiento por completo mediante script. Esto los hace ideales para personajes jugables, NPC y cualquier cosa que necesite un movimiento preciso y determinista.
2. Qué cambió respecto a Godot 3
Estos son los cambios más importantes en el movimiento de personajes entre Godot 3 y Godot 4:
-
KinematicBody2Dahora esCharacterBody2D -
KinematicBody3Dahora esCharacterBody3D -
velocityahora es una propiedad incorporada — ya no se la pasas amove_and_slide() -
move_and_slide()no recibe argumentos — lee directamente de la propiedadvelocity -
move_and_slide_with_snap()se eliminó — usa en su lugar la propiedadfloor_snap_length -
is_on_floor(),is_on_wall(),is_on_ceiling()siguen funcionando igual -
get_gravity()se añadió en Godot 4.4 para leer la gravedad predeterminada del proyecto como un Vector — muy cómodo
En Godot 3, move_and_slide() devolvía la velocidad resultante. En Godot 4 devuelve un bool (si se produjo una colisión), y la velocidad se actualiza directamente en la propiedad velocity.
3. Controlador básico de plataformas 2D
Este es el controlador estándar de plataformas de desplazamiento lateral. El personaje puede moverse a izquierda/derecha y saltar cuando está en el suelo. Esta es la plantilla que Godot genera al crear un nuevo script de CharacterBody2D.
extends CharacterBody2D
const SPEED = 300.0
const JUMP_VELOCITY = -400.0
func _physics_process(delta: float) -> void:
# Apply gravity when not on floor
if not is_on_floor():
velocity += get_gravity() * delta
# Jump when on floor and jump pressed
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Horizontal movement
var direction := Input.get_axis("ui_left", "ui_right")
if direction:
velocity.x = direction * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
move_and_slide()
Desglose línea por línea
-
extends CharacterBody2D— El script hereda de CharacterBody2D, lo que nos da acceso avelocity,move_and_slide(),is_on_floor(), etc. -
SPEED = 300.0— Velocidad de movimiento horizontal en píxeles por segundo. -
JUMP_VELOCITY = -400.0— Negativa porque en 2D el eje Y apunta hacia abajo. Un valor negativo mueve al personaje hacia arriba. -
get_gravity() * delta— Aplica la gravedad del proyecto en cada fotograma.get_gravity()devuelve unVector2que apunta hacia abajo (p. ej.Vector2(0, 980)). Multiplicar pordeltalo hace independiente de la tasa de fotogramas. -
Input.get_axis("ui_left", "ui_right")— Devuelve un float de -1.0 a 1.0 según la tecla de dirección pulsada. Admite entrada analógica. -
move_toward(velocity.x, 0, SPEED)— Desacelera suavemente hasta cero cuando no hay entrada. El tercer argumento es el cambio máximo por llamada. -
move_and_slide()— Mueve el cuerpo usando lavelocityactual, gestiona las colisiones, desliza a lo largo de las superficies y actualizavelocityautomáticamente.
Tu CharacterBody2D necesita al menos un nodo hijo CollisionShape2D con una forma asignada (p. ej. RectangleShape2D o CapsuleShape2D). Sin ella, move_and_slide() no detectará ninguna colisión.
4. Controlador básico cenital 2D
Para juegos con vista cenital (RPG, twin-stick shooters, etc.), te mueves en las cuatro direcciones sin gravedad. El código es más sencillo que el de un juego de plataformas.
extends CharacterBody2D
const SPEED = 200.0
func _physics_process(delta: float) -> void:
var input_direction := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
velocity = input_direction * SPEED
move_and_slide()
Cómo funciona
-
Input.get_vector()devuelve unVector2normalizado a partir de cuatro acciones de entrada. Esto gestiona correctamente el movimiento diagonal — la longitud del vector siempre es 1.0 o 0.0, de modo que el movimiento diagonal no es más rápido que el movimiento en los ejes. -
No se aplica gravedad, ya que se trata de una vista cenital. El personaje se detiene de inmediato cuando no hay entrada porque establecemos
velocitydirectamente (sin inercia).
Añadir aceleración y fricción
Para una sensación más suave con aceleración y desaceleración:
extends CharacterBody2D
const SPEED = 200.0
const ACCELERATION = 1200.0
const FRICTION = 1000.0
func _physics_process(delta: float) -> void:
var input_direction := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
if input_direction != Vector2.ZERO:
velocity = velocity.move_toward(input_direction * SPEED, ACCELERATION * delta)
else:
velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)
move_and_slide()
5. Controlador 3D en tercera persona
La versión 3D sigue el mismo patrón. La principal diferencia es trabajar con Vector3 y usar transform.basis para convertir la entrada 2D en movimiento en el espacio 3D del mundo.
extends CharacterBody3D
const SPEED = 5.0
const JUMP_VELOCITY = 4.5
func _physics_process(delta: float) -> void:
# Apply gravity
if not is_on_floor():
velocity += get_gravity() * delta
# Jump
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Get input and convert to 3D direction
var input_dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if direction:
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
move_and_slide()
Diferencias clave con 2D
-
Unidades: En 3D, 1 unidad = 1 metro (por convención). Así,
SPEED = 5.0significa 5 metros por segundo — números mucho más pequeños que los valores en píxeles de 2D. -
La velocidad de salto es positiva: En 3D el eje Y apunta hacia arriba (al contrario que en 2D), por lo que
JUMP_VELOCITY = 4.5es positivo. -
Transformación de dirección:
transform.basis * Vector3(input_dir.x, 0, input_dir.y)convierte la entrada 2D en una dirección 3D relativa a la orientación del personaje. La Y de la entrada se asigna al eje Z (adelante/atrás en 3D). -
Solo se controlan X y Z: Establecemos
velocity.xyvelocity.zpor separado, dejandovelocity.ypara la gravedad y el salto.
El método get_gravity() se añadió en Godot 4.4. Para versiones anteriores, usa Vector2(0, ProjectSettings.get_setting("physics/2d/default_gravity")) para 2D, o Vector3(0, -ProjectSettings.get_setting("physics/3d/default_gravity"), 0) para 3D.
6. Referencia de las propiedades clave
Tanto CharacterBody2D como CharacterBody3D comparten estas propiedades importantes. Ajústalas en el Inspector o mediante código.
| Propiedad | Tipo | Predeterminado | Descripción |
|---|---|---|---|
velocity |
Vector2 / Vector3 | Vector2.ZERO |
La velocidad del personaje. Establécela antes de llamar a move_and_slide(). Tras la llamada se actualiza con la velocidad resultante.
|
floor_snap_length |
float | 1.0 |
Reemplaza a move_and_slide_with_snap(). Distancia con la que el personaje se fija al suelo. Ponla a 0 para desactivarla. Útil para pendientes y escaleras.
|
up_direction |
Vector2 / Vector3 | Vector2.UP |
Define qué dirección es «arriba». Esto determina qué cuenta como suelo, pared o techo. El valor predeterminado es (0, -1) en 2D, (0, 1, 0) en 3D.
|
floor_stop_on_slope |
bool | true |
Cuando es true, el personaje no se desliza cuesta abajo en las pendientes mientras está quieto. Imprescindible para los juegos de plataformas. |
floor_max_angle |
float | 0.785 (45°) |
Ángulo máximo de pendiente transitable en radianes. Las superficies más empinadas que esto se tratan como paredes. Usa deg_to_rad(60) para establecer 60 grados.
|
max_slides |
int | 6 |
Número máximo de iteraciones de colisión por cada llamada a move_and_slide(). Los valores más altos son más precisos pero más lentos.
|
wall_min_slide_angle |
float | 0.262 (15°) |
Ángulo mínimo para el deslizamiento por paredes. Evita que el personaje se quede atascado en paredes casi paralelas. |
platform_on_leave |
PlatformOnLeave | ADD_VELOCITY |
Comportamiento al abandonar una plataforma móvil. ADD_VELOCITY conserva el impulso, ADD_UPWARD_VELOCITY solo añade la componente hacia arriba, DO_NOTHING ignora la velocidad de la plataforma.
|
slide_on_ceiling |
bool | true |
Cuando es true, permite deslizarse por los techos. Cuando es false, detiene la velocidad horizontal al golpear el techo. |
7. Detección de colisiones tras move_and_slide()
Tras llamar a move_and_slide(), puedes inspeccionar las colisiones que se produjeron. Esto es útil para reaccionar a tipos concretos de collider, reproducir efectos de sonido al aterrizar o implementar mecánicas de salto en pared.
func _physics_process(delta: float) -> void:
# ... set velocity here ...
move_and_slide()
# Check all collisions from this frame
for i in get_slide_collision_count():
var collision := get_slide_collision(i)
var collider := collision.get_collider()
print("Collided with: ", collider.name)
print("Normal: ", collision.get_normal())
print("Position: ", collision.get_position())
# Practical example: bounce off enemies
for i in get_slide_collision_count():
var collision := get_slide_collision(i)
if collision.get_collider().is_in_group("enemies"):
velocity = collision.get_normal() * 300
break
Métodos útiles de KinematicCollision
get_collider()— El objeto que fue golpeado.get_normal()— La normal de la superficie de colisión. Apunta en dirección opuesta a la superficie.get_position()— El punto en el espacio del mundo donde se produjo la colisión.get_travel()— Cuánto se desplazó el cuerpo antes de colisionar.get_remainder()— El movimiento restante que no se aplicó.get_collider_velocity()— Velocidad del collider (útil para plataformas móviles).
8. Chuleta de migración
Referencia rápida para convertir código de Godot 3 a Godot 4:
| Godot 3 | Godot 4 |
|---|---|
KinematicBody2D |
CharacterBody2D |
KinematicBody3D |
CharacterBody3D |
velocity = move_and_slide(velocity, Vector2.UP) |
velocity = ... |
move_and_slide_with_snap(vel, snap, up) |
floor_snap_length = 4.0 |
var vel = move_and_slide(...)devuelve velocity |
move_and_slide()la propiedad velocity se actualiza directamente |
var gravity = ProjectSettings.get("physics/2d/default_gravity") |
get_gravity() (4.4+) |
move_and_slide(..., up_direction, ...) |
up_direction = Vector2.UP (establecido como propiedad) |
move_and_slide(..., stop_on_slope, ...) |
floor_stop_on_slope = true (establecido como propiedad) |
Al abrir un proyecto de Godot 3 en Godot 4, el motor ofrece convertir tu proyecto. Renombra los nodos e intenta actualizar los scripts, pero no siempre acierta con las llamadas a move_and_slide(). Probablemente tendrás que corregir manualmente tu código de movimiento.
9. Errores comunes
Error 1: Pasar argumentos a move_and_slide()
# ERROR: move_and_slide() takes no arguments in Godot 4
velocity = move_and_slide(velocity, Vector2.UP)
# Set velocity, then call move_and_slide() with no arguments
velocity.x = direction * SPEED
move_and_slide()
Error 2: Olvidar establecer velocity antes de move_and_slide()
func _physics_process(delta: float) -> void:
move_and_slide() # velocity is Vector2.ZERO — nothing happens
Error 3: Usar _process() en lugar de _physics_process()
move_and_slide() debe llamarse en _physics_process(), que se ejecuta a una tasa fija (60 Hz por defecto). Usar _process() vincula la física a la tasa de fotogramas de renderizado, provocando un comportamiento inconsistente.
Error 4: No configurar la CollisionShape
Un CharacterBody2D/CharacterBody3D sin un hijo CollisionShape atravesará todo. Godot muestra un icono de advertencia en el editor si falta la forma de colisión.
Error 5: Sobrescribir velocity.y con la gravedad en cada fotograma
# This overwrites any jump velocity!
velocity.y = gravity * delta # should be +=, not =
# Gravity accumulates over time
velocity += get_gravity() * delta
Error 6: Aplicar la gravedad estando en el suelo
Envuelve siempre la gravedad con if not is_on_floor(). Sin esta comprobación, la gravedad sigue acumulándose mientras se está en el suelo. Cuando el personaje se sale de un borde, se desplomará a una velocidad extrema en lugar de caer de forma natural.