Clase 030 — Movimiento de personaje 2D: velocidad, aceleración y control

Parte: 1 — Motores 2D y tu primer juego jugable · Fuente: Documentación de Godot 4 (CharacterBody2D, 2D movement) ⏱️ Duración estimada: 95 min · Nivel: Intermedio


🎯 Objetivo

Construir un controlador de plataformas 2D con buen tacto (game feel) usando CharacterBody2D en Godot 4. Pasaremos de un movimiento rígido a uno con aceleración y desaceleración mediante move_toward, gravedad basada en delta, salto con velocity.y negativa y refinamientos como el salto variable, el coyote time y el jump buffer.

Al terminar tendrás un personaje que corre y salta con sensación profesional, listo para recibir animaciones en la clase 031. Todo el código está tipado, comentado y usa la API correcta de Godot 4 (velocity, move_and_slide() sin argumentos, is_on_floor()).

📚 Resultados de aprendizaje

Al finalizar, el alumno podrá:

  1. Configurar un CharacterBody2D con colisionador para un plataformas 2D.
  2. Aplicar aceleración y desaceleración horizontales con move_toward.
  3. Implementar gravedad y salto usando velocity y move_and_slide().
  4. Programar salto variable cortando el impulso al soltar el botón.
  5. Añadir coyote time y jump buffer para mejorar el tacto del salto.

🗺️ Temas

# Tema Por qué importa
1 CharacterBody2D Cuerpo pensado para control manual del personaje.
2 velocity y move_and_slide() Núcleo del movimiento en Godot 4.
3 Aceleración con move_toward Da peso y control fino al personaje.
4 Gravedad con delta Caída estable e independiente de FPS.
5 Salto con velocity.y Impulso vertical controlado.
6 Salto variable Altura según cuánto mantienes el botón.
7 Coyote time Perdona saltos justo tras salir del borde.
8 Jump buffer Recuerda el salto pulsado un instante antes.

📖 Definiciones y características

🧰 Herramientas y preparación

Sigue en PlataformasCurso con las acciones de input de la clase 029 ya creadas (move_left, move_right, jump). Necesitarás un suelo para probar: crearemos un StaticBody2D simple. Reutiliza el sprite del jugador como marcador.

Apóyate en la guía de movimiento 2D de Godot: https://docs.godotengine.org/en/stable/tutorials/physics/using_character_body_2d.html. La gravedad la tomaremos de ProjectSettings (physics/2d/default_gravity, por defecto 980).

🧪 Laboratorio guiado

Convertiremos al jugador en un CharacterBody2D con tacto profesional y le daremos un suelo.

  1. Convierte la raíz del jugador. Abre escenas/jugador.tscn, haz clic derecho sobre el nodo raíz JugadorChange TypeCharacterBody2D. Mantén el Sprite2D hijo.

  2. Añade el colisionador. Con Jugador seleccionado, agrega un hijo CollisionShape2D. En el Inspector, en Shape, crea un RectangleShape2D y ajústalo para cubrir el sprite. Guarda con Ctrl+S.

  3. Crea el suelo en el mundo. Abre escenas/mundo.tscn, añade un StaticBody2D llamado Suelo, con un hijo CollisionShape2D (un RectangleShape2D ancho y bajo) colocado en la parte inferior de la pantalla. Añádele también un Sprite2D o un ColorRect para verlo.

  4. Coloca la instancia Jugador por encima del suelo para que caiga sobre él al arrancar.

  5. Reemplaza escenas/jugador.gd con el controlador completo comentado:

extends CharacterBody2D
class_name Jugador

# --- Parametros de tacto (ajustables desde el Inspector) ---
@export var velocidad_max: float = 300.0      # velocidad horizontal objetivo
@export var aceleracion: float = 2000.0       # cuan rapido alcanza la velocidad
@export var friccion: float = 2500.0          # cuan rapido frena al soltar
@export var fuerza_salto: float = 520.0       # impulso vertical (positivo, se aplica negativo)
@export var corte_salto: float = 0.45         # factor al soltar el boton (salto variable)
@export var coyote_max: float = 0.10          # ventana de coyote time en segundos
@export var buffer_max: float = 0.10          # ventana de jump buffer en segundos

# Gravedad del proyecto (por defecto 980). Independiente de la resolucion.
var gravedad: float = ProjectSettings.get_setting("physics/2d/default_gravity")

var coyote: float = 0.0   # tiempo restante de coyote
var buffer: float = 0.0   # tiempo restante de buffer de salto

func _physics_process(delta: float) -> void:
    _aplicar_gravedad(delta)
    _temporizadores(delta)
    _mover_horizontal(delta)
    _gestionar_salto()
    # En Godot 4 move_and_slide usa 'velocity' y NO recibe argumentos.
    move_and_slide()

func _aplicar_gravedad(delta: float) -> void:
    if not is_on_floor():
        velocity.y += gravedad * delta

func _temporizadores(delta: float) -> void:
    # Coyote time: se recarga en el suelo y decrece en el aire.
    if is_on_floor():
        coyote = coyote_max
    else:
        coyote = max(coyote - delta, 0.0)

    # Jump buffer: recuerda la pulsacion durante un instante.
    if Input.is_action_just_pressed("jump"):
        buffer = buffer_max
    else:
        buffer = max(buffer - delta, 0.0)

func _mover_horizontal(delta: float) -> void:
    var eje_x: float = Input.get_axis("move_left", "move_right")
    if eje_x != 0.0:
        # Acelera hacia la velocidad objetivo (da peso al arranque).
        velocity.x = move_toward(velocity.x, eje_x * velocidad_max, aceleracion * delta)
        # Voltea el sprite segun la direccion.
        $Sprite2D.flip_h = eje_x < 0.0
    else:
        # Sin input: frena suavemente hasta cero.
        velocity.x = move_toward(velocity.x, 0.0, friccion * delta)

func _gestionar_salto() -> void:
    # Salta si hay salto en buffer y aun queda coyote (en suelo o recien salido).
    if buffer > 0.0 and coyote > 0.0:
        velocity.y = -fuerza_salto
        buffer = 0.0
        coyote = 0.0

    # Salto variable: al soltar el boton mientras subimos, cortamos el impulso.
    if Input.is_action_just_released("jump") and velocity.y < 0.0:
        velocity.y *= corte_salto
  1. Ejecuta con F5. El jugador cae por gravedad, aterriza en el suelo, corre con aceleración y salta con la barra espaciadora. Prueba el salto variable: un toque corto da un salto bajo; mantener pulsado da un salto alto.

  2. Prueba el coyote time: camina hasta el borde del suelo y salta un instante después de dejarlo; el personaje aún salta. Prueba el jump buffer: pulsa saltar justo antes de aterrizar y el salto se ejecuta al tocar suelo.

  3. Ajusta los valores exportados en el Inspector (aceleracion, friccion, fuerza_salto) y prueba en vivo hasta que el tacto te convenza. El game feel se logra iterando.

Ya tienes un controlador de plataformas con tacto profesional; en la clase 031 le daremos animaciones de correr y saltar.

✍️ Ejercicios

  1. Añade una velocidad de correr con la acción run que suba velocidad_max temporalmente.
  2. Limita la velocidad de caída (velocidad terminal) con un min sobre velocity.y.
  3. Reduce el control horizontal en el aire multiplicando la aceleración cuando not is_on_floor().
  4. Emite una señal aterrizo cuando el jugador pasa de aire a suelo e imprímela.
  5. Añade un doble salto contando saltos disponibles que se reinician al tocar suelo.
  6. Expón el coyote y buffer en una Label de depuración para verlos decrecer.

📝 Reto verificable

Ajusta el controlador para que se sienta bien: define valores de aceleración, fricción y salto que permitan cruzar una plataforma y saltar un hueco con precisión. Añade doble salto y una velocidad terminal de caída. Documenta en comentarios por qué elegiste cada valor.

Criterio de aceptación: el personaje corre, frena y salta con control fino; el salto variable, el coyote time y el jump buffer funcionan; el doble salto se reinicia al aterrizar; y no atraviesa el suelo ni las paredes.

⚠️ Errores comunes

Síntoma / mensaje Causa y cómo arreglar
"Too many arguments for move_and_slide()" Sintaxis de Godot 3. En Godot 4 se llama move_and_slide() sin argumentos, tras fijar velocity.
El personaje atraviesa el suelo Falta el CollisionShape2D en el jugador o en el suelo, o su forma es nula.
is_on_floor() siempre es false Llamas a move_and_slide() antes de comprobarlo, o el suelo no es un cuerpo de colisión.
El salto no funciona La acción jump no existe en el Input Map o la fuerza es demasiado baja frente a la gravedad.
El movimiento se siente resbaladizo o brusco Aceleración/fricción mal calibradas. Ajústalas desde el Inspector iterando.
Cae demasiado rápido o flota Gravedad del proyecto o fuerza_salto desequilibradas. Revisa default_gravity.

❓ Preguntas frecuentes

❓ ¿Por qué move_and_slide() no recibe la velocidad como en tutoriales viejos? Esos usan Godot 3. En Godot 4 fijas la propiedad velocity del cuerpo y llamas al método sin argumentos.

❓ ¿Qué gana el juego con coyote time y jump buffer? Perdonan errores de milisegundos del jugador, haciendo que el salto se sienta justo y responsivo en lugar de frustrante.

❓ ¿Por qué usar move_toward en vez de asignar la velocidad directa? Porque interpola gradualmente hacia el objetivo, dando peso al arranque y suavidad al frenado, clave del buen tacto.

❓ ¿De dónde sale el valor de gravedad? Lo leemos de ProjectSettings (physics/2d/default_gravity, 980 por defecto), así respeta la configuración global del proyecto.

🔗 Referencias

⬅️ Clase anterior

Clase 029 - Input: teclado, ratón, gamepad y mapeo de acciones

➡️ Siguiente clase

Clase 031 - Sprites, animación por frames y AnimationPlayer