Parte: 1 — Motores 2D y tu primer juego jugable · Fuente: Documentación de Godot 4 ⏱️ Duración estimada: 90 min · Nivel: Intermedio
Hasta ahora tu jugador es un rectángulo (o un sprite estático) que se mueve y salta. En esta clase le damos vida visual: aprenderás a animar por cuadros (frames) con AnimatedSprite2D y su recurso SpriteFrames, creando ciclos de idle, run y jump, y a sincronizar la animación reproducida con el estado real del cuerpo (velocidad e is_on_floor()).
Además distinguirás las dos herramientas de animación de Godot 4: AnimatedSprite2D (ideal para spritesheets de personajes) y AnimationPlayer (para animar cualquier propiedad por keyframes: escala, posición, modulación). Al final tu personaje corre mirando en la dirección correcta y "aterriza" con un pequeño golpe de escala que se siente jugoso.
Al finalizar, el alumno podrá:
SpriteFrames con animaciones idle, run y jump, ajustando FPS y bucle.flip_h según la dirección de movimiento.AnimationPlayer para animar propiedades (escala, posición) mediante keyframes.animation_finished para encadenar comportamientos al terminar una animación.| # | Tema | Por qué importa |
|---|---|---|
| 1 | AnimatedSprite2D vs Sprite2D |
El primero reproduce secuencias; el segundo muestra una imagen fija |
| 2 | Recurso SpriteFrames |
Contenedor de animaciones (frames, FPS, loop) reutilizable |
| 3 | FPS y bucle por animación | Controlan velocidad y si el ciclo se repite (correr sí, saltar no) |
| 4 | flip_h y dirección |
Reutiliza los mismos frames para mirar izquierda y derecha |
| 5 | Selección de animación por estado | La animación debe reflejar lo que hace el cuerpo, no al revés |
| 6 | AnimationPlayer y keyframes |
Anima escala/posición/color para juice sin dibujar frames |
| 7 | Señal animation_finished |
Permite encadenar lógica cuando termina un ciclo no-loop |
SpriteFrames. Clave: método play("nombre") y propiedad animation.run a 10–12 FPS se ve fluido; idle a 4–6 FPS respira.idle/run con loop; jump normalmente sin loop.AnimatedSprite2D al terminar una animación sin bucle. Clave: útil para transiciones (p. ej. de jump a fall).Necesitas Godot 4.x (recomendado 4.3+) y la escena Player de las clases anteriores, con un CharacterBody2D que ya se mueve y salta. Para los frames puedes usar una spritesheet libre (por ejemplo de Kenney o OpenGameArt) o placeholders. Consulta la documentación de AnimatedSprite2D y de AnimationPlayer.
En tu escena Player (raíz CharacterBody2D), si tienes un Sprite2D, bórralo y añade un AnimatedSprite2D como hijo. Deja también el CollisionShape2D. El árbol queda:
Player (CharacterBody2D)
├── AnimatedSprite2D
├── CollisionShape2D
└── Camera2D (si ya la tienes)
Selecciona el AnimatedSprite2D. En el Inspector, en la propiedad Sprite Frames, elige Nuevo SpriteFrames. Haz clic sobre el recurso para abrir el panel SpriteFrames (abajo). Ahí:
default a idle.idle. Pon FPS = 6 y activa Loop.run (FPS = 10, Loop activado) con sus cuadros.jump (FPS = 8, Loop desactivado).Amplía el script del jugador. La idea central: primero mueves el cuerpo, luego eliges la animación a partir de velocity e is_on_floor().
extends CharacterBody2D
@export var velocidad: float = 220.0
@export var fuerza_salto: float = 430.0
@export var gravedad: float = 1200.0
@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D
func _physics_process(delta: float) -> void:
# --- Gravedad ---
if not is_on_floor():
velocity.y += gravedad * delta
# --- Salto ---
if Input.is_action_just_pressed("saltar") and is_on_floor():
velocity.y = -fuerza_salto
# --- Movimiento horizontal ---
var direccion: float = Input.get_axis("mover_izquierda", "mover_derecha")
velocity.x = direccion * velocidad
move_and_slide()
# --- Animación (después de mover) ---
actualizar_animacion(direccion)
func actualizar_animacion(direccion: float) -> void:
# Voltear solo si hay dirección clara
if direccion > 0.0:
sprite.flip_h = false
elif direccion < 0.0:
sprite.flip_h = true
# Prioridad: aire > correr > quieto
if not is_on_floor():
reproducir("jump")
elif absf(velocity.x) > 5.0:
reproducir("run")
else:
reproducir("idle")
func reproducir(nombre: String) -> void:
# Evita reiniciar la animación en cada frame
if sprite.animation != nombre:
sprite.play(nombre)
Nota:
reproducir()compruebasprite.animation != nombrepara no reiniciar el ciclo en cada frame (si llamasplay()sin parar, el frame nunca avanza).
Añade un nodo AnimationPlayer como hijo del Player. Crea una animación aterrizar de 0.2 s con dos pistas de propiedad sobre AnimatedSprite2D:scale:
scale = (1.3, 0.7) (aplastado)scale = (1.0, 1.0) (normal)Luego dispárala cuando el jugador toque suelo tras estar en el aire:
@onready var anim_player: AnimationPlayer = $AnimationPlayer
var estaba_en_aire: bool = false
func _physics_process(delta: float) -> void:
# ... (código anterior de movimiento) ...
move_and_slide()
# Detectar aterrizaje: veníamos del aire y ahora tocamos suelo
if is_on_floor() and estaba_en_aire:
anim_player.play("aterrizar")
estaba_en_aire = not is_on_floor()
actualizar_animacion(Input.get_axis("mover_izquierda", "mover_derecha"))
Ejecuta: al correr verás run, al saltar jump, y al caer el sprite se aplasta y recupera su forma. Eso es game feel.
fall distinta a jump y reprodúcela cuando velocity.y > 0 en el aire.run para que el ciclo de piernas coincida con la velocidad real de desplazamiento.crouch (agacharse) que se reproduzca al mantener presionada una acción "agacharse".animation_finished para pasar automáticamente de jump a fall.AnimationPlayer, anima la propiedad modulate para que el jugador parpadee en rojo 0.3 s (base de un futuro "recibir daño").@export var fps_correr: int y aplícalo con sprite.sprite_frames.set_animation_speed("run", fps_correr).Implementa un sistema de animación completo con cuatro estados visuales: idle, run, jump (subiendo) y fall (cayendo), seleccionados exclusivamente a partir de velocity e is_on_floor(). El sprite debe voltearse correctamente y no reiniciar su ciclo cada frame.
Criterio de aceptación: al jugar, cada estado muestra su animación correcta sin parpadeos ni reinicios; el sprite mira siempre hacia donde se mueve; al subir se ve jump y al bajar fall; y al aterrizar tras un salto se dispara un efecto de stretch con AnimationPlayer.
| Síntoma / mensaje | Causa y cómo arreglar |
|---|---|
| La animación se queda en el primer frame | Llamas a play() cada frame; comprueba sprite.animation != nombre antes de reproducir |
Invalid call. Nonexistent function 'play' |
El @onready apunta a un nodo que no es AnimatedSprite2D; revisa la ruta $AnimatedSprite2D |
| El sprite mira siempre a la derecha | Cambias flip_h incluso cuando direccion == 0; voltea solo si hay dirección |
jump se repite en bucle infinito |
Dejaste Loop activado en esa animación; desactívalo en el panel SpriteFrames |
El efecto de AnimationPlayer no se ve |
La pista anima una ruta mal escrita; verifica que sea AnimatedSprite2D:scale relativa al nodo con el AnimationPlayer |
| Animación "corre" aunque estás quieto | Umbral de velocidad muy bajo; compara absf(velocity.x) > 5.0 en vez de != 0 |
❓ ¿Cuándo uso AnimatedSprite2D y cuándo AnimationPlayer? Usa AnimatedSprite2D para animar personajes por cuadros (spritesheets) y AnimationPlayer para animar propiedades (escala, posición, color, incluso llamar funciones). A menudo conviven en el mismo personaje.
❓ ¿Por qué mi ciclo de correr va demasiado rápido o lento? El FPS de la animación es independiente de los FPS del juego. Ajústalo por animación en el panel SpriteFrames o en runtime con set_animation_speed.
❓ ¿Puedo tener frames espejo en lugar de flip_h? Sí, pero flip_h ahorra memoria y trabajo: reutiliza los mismos cuadros. Solo dibuja frames distintos si la asimetría del personaje lo exige.
❓ ¿El AnimationPlayer puede llamar a mis funciones? Sí: con una pista de tipo Call Method puedes invocar funciones del nodo en instantes concretos de la línea de tiempo, útil para sincronizar sonidos o efectos.
Clase 030 - Movimiento de personaje 2D: velocidad, aceleración y control