Tutorial de Godot – Parte 08: Animación 2D Sprite

En este tutorial aprenderemos a usar el nodo AnimatedSprite para animar sprites . En particular, animaremos a nuestro jugador agregando animaciones inactivas, animaciones para caminar y dos tipos de ataques (espada y bola de fuego).

Preparando el nodo del jugador

Abramos el proyecto SimpleRPG y retomemos donde lo dejamos en el último tutorial . Para comenzar, necesitamos importar todos los cuadros de animación al proyecto. Descargue los sprites del jugador presionando el botón a continuación.

Descargar “Marcos de reproductor SimpleRPG”player_frames.zip – 35 KB

En el panel FileSystem , elimine player.png , importe todas las imágenes nuevas y colóquelas en la carpeta Entities/Player .

Selecciona todas las imágenes que acabas de agregar, ve al panel Importar , desactiva Filtro y haz clic en Reimportar .

Vuelva al panel Escena , haga clic con el botón derecho en el nodo Sprite y elija Cambiar tipo .

Seleccione AnimatedSprite y presione el botón Cambiar .

Una vez que se cambia el tipo de nodo, aparece un triángulo amarillo junto a Sprite . Esta advertencia nos dice que necesitamos crear un recurso SpriteFrames para mostrar algo en la pantalla.

SpriteFrames es un recurso (es decir, un contenedor de datos) que contiene todas las texturas que usaremos como marcos, así como la lista de todas las animaciones con sus propiedades (velocidad, reproducción en bucle, etc.)

Para crear un recurso SpriteFrames , vaya al Inspector , haga clic en [vacío] junto a la propiedad Frames y seleccione New SpriteFrames .

Haga clic en el recurso SpriteFrames recién creado . Se abrirá el editor de SpriteFrames .

El editor de SpriteFrames

El panel del editor de SpriteFrames se divide en dos columnas. La columna de la izquierda enumera todas las animaciones, mientras que a la derecha está la lista de los fotogramas que componen la animación seleccionada.

La columna de la izquierda tiene una barra de herramientas con dos botones, uno para agregar una nueva animación y otro para eliminar la animación seleccionada. En la parte inferior de la columna, puede establecer la velocidad de reproducción (en fotogramas por segundo) y decidir si la animación debe reproducirse en bucle o no.

En la barra de herramientas de la columna derecha hay 8 botones:

  • Cargar recurso : para agregar un recurso a los cuadros de animación. Más simplemente, los sprites se pueden arrastrar y soltar desde el panel Sistema de archivos.
  • Copiar : copia el marco seleccionado.
  • Pegar : pega el fotograma copiado anteriormente.
  • Insertar vacío antes : inserta un marco vacío antes del seleccionado.
  • Insertar vacío después : inserta un marco vacío después del seleccionado.
  • Mover antes : mueve el cuadro seleccionado hacia atrás una posición.
  • Mover después : mueve el marco seleccionado una posición hacia adelante.
  • Eliminar : elimina el marco seleccionado.

Para comenzar, cambie el nombre de la animación predeterminada haciendo clic en ella y llámela down_idle . Arrastre los archivos player_down_idle_1.png y player_down_idle_2.png desde el panel FileSystem al lado derecho del editor SpriteFrames y luego configure Speed ​​(FPS) en 1.

Ahora, en el Inspector , habilite la propiedad Playing . Verá el sprite animado con la animación inactiva recién creada.

Con el mismo procedimiento, cree todas las demás animaciones. La siguiente tabla contiene toda la información que necesita para crearlos.

Nombre de la animaciónMarcosVelocidad (FPS)Círculo
ataque_hacia abajojugador_abajo_inactivo_1.png
jugador_abajo_ataque_1.png
jugador_abajo_ataque_2.png
7Apagado
abajo_bola_de_fuegojugador_abajo_idle_1.png
jugador_abajo_bola_de_fuego_1.png
jugador_abajo_bola_de_fuego_2.png
7Apagado
inactivojugador_abajo_inactivo_1.png
jugador_abajo_inactivo_2.png
1Sobre
Abajo_caminarplayer_down_idle_1.png
player_down_walk_1.png
player_down_idle_1.png
player_down_walk_2.png
10Sobre
ataque_izquierdaplayer_left_idle_1.png
player_left_attack_1.png
player_left_attack_2.png
7Apagado
izquierda_bola de fuegoplayer_left_idle_1.png
player_left_fireball_1.png
player_left_fireball_2.png
7Apagado
izquierda_inactivajugador_izquierdo_inactivo_1.png
jugador_izquierdo_inactivo_2.png
1Sobre
izquierda_caminarplayer_left_idle_1.png
player_left_walk_1.png
player_left_idle_1.png
player_left_walk_2.png
10Sobre
derecha_ataquejugador_derecho_inactivo_1.png
jugador_derecho_ataque_1.png
jugador_derecho_ataque_2.png
7Apagado
derecha_bola de fuegojugador_derecho_inactivo_1.png
jugador_derecho_bola_de_fuego_1.png
jugador_derecho_bola_de_fuego_2.png
7Apagado
derecho_inactivoreproductor_derecho_inactivo_1.png
reproductor_derecho_inactivo_2.png
1Sobre
derecho_caminarplayer_right_idle_1.png
player_right_walk_1.png
player_right_idle_1.png
player_right_walk_2.png
10Sobre
up_attackplayer_up_idle_1.png
player_up_attack_1.png
player_up_attack_2.png
7Apagado
up_fireballjugador_arriba_inactivo_1.png
jugador_arriba_bola_de_fuego_1.png
jugador_arriba_bola_de_fuego_2.png
7Apagado
up_idlejugador_arriba_inactivo_1.png
jugador_arriba_inactivo_2.png
1Sobre
subir_caminarplayer_up_idle_1.png
player_up_walk_1.png
player_up_idle_1.png
player_up_walk_2.png
10Sobre

En el Inspector , puede establecer la propiedad Animación en la animación que desea obtener una vista previa. Cuando haya terminado de crear las animaciones, configúrelo en down_idle . Será la animación predeterminada cuando comience el juego.

Reproducción de animaciones con script

Abra el script del reproductor. Para elegir qué animación reproducir, escribiremos una nueva función llamada animates_player( ). Llamaremos a esta función al final de _physics_process() , después de que se haya calculado el movimiento del jugador.

Comencemos escribiendo una versión simplificada de la función animates_player() para familiarizarnos con el funcionamiento de la animación de sprites en 2D.

Copie el siguiente código en el script:

func animates_player(direction: Vector2):
	if direction != Vector2.ZERO:
		# Play walk animation
		$Sprite.play("down_walk")
	else:
		# Play idle animation
		$Sprite.play("down_idle")

La función animate_player() toma un argumento Vector2 que representa la dirección del movimiento del jugador.

La primera línea comprueba si la dirección es diferente de cero ( Vector2.ZERO es una constante equivalente a Vector2(0,0) ). Si es verdadero, el jugador se está moviendo y se llama al método play() del nodo Sprite para reproducir la animación down_walk . Si es falso, significa que el reproductor está quieto, por lo que se realiza la animación down_idle . El operador $ , como vimos en el Tutorial de Godot – Parte 3: Primer proyecto , se usa para obtener la referencia a un nodo secundario.

¡Advertencia! Aunque el nodo se sigue llamando Sprite , les recuerdo que ahora es de tipo AnimatedSprite . ¡No confunda el nombre del nodo con su tipo! play() es un método de AnimatedSprite que no está disponible en los nodos de tipo Sprite .

Ahora, en la parte inferior de la función _physics_process() , agrega esta línea para llamar a animates_player() :

# Animate player based on direction
animates_player(direction)

Si intenta ejecutar el juego, verá que cuando el jugador está quieto, se realiza la animación inactiva, mientras que cuando se está moviendo, la animación de caminar se muestra como se esperaba.

Elegir la animación adecuada

Obviamente, no queremos mostrar al jugador siempre boca abajo, pero queremos elegir qué animación reproducir en función de la dirección en la que mira el jugador.

Para hacer esto, primero debemos declarar una nueva variable al comienzo del script:

var last_direction = Vector2(0, 1)

En esta variable almacenaremos la última dirección en la que se movió el jugador antes de detenerse. Establecemos el valor inicial en Vector2 (0,1) para señalar al jugador hacia abajo. Esta variable nos ayudará a decidir qué animación inactiva mostrar.

Ahora, tenemos que escribir el código que elige si usar las animaciones abajo , arriba , izquierda o derecha , según el vector de dirección. Como usaremos este código en más de una ocasión, lo escribiremos en una nueva función, llamada get_animation_direction() :

func get_animation_direction(direction: Vector2):
	var norm_direction = direction.normalized()
	if norm_direction.y >= 0.707:
		return "down"
	elif norm_direction.y <= -0.707:
		return "up"
	elif norm_direction.x <= -0.707:
		return "left"
	elif norm_direction.x >= 0.707:
		return "right"
	return "down"

¿Cómo funciona este código? Primero, normaliza el vector de dirección para asegurarse de que tenga una longitud de 1 (puede ser menor que 1 cuando se usa una palanca analógica). Si dibujamos este vector normalizado en un plano cartesiano, siempre estará en un punto del círculo unitario (es decir, un círculo con radio 1):

Dividamos el plan en cuatro cuadrantes como en la siguiente figura:

Cada área contiene un cuarto del círculo unitario. Cada uno de estos arcos corresponde a una de las 4 direcciones posibles de las animaciones.

¿Cómo decidimos en cuál de estos arcos está nuestro vector? No los aburriré con cálculos, pero se puede demostrar que todos los puntos del arco inferior tienen la coordenada y mayor que la raíz cuadrada de 2 sobre 2, o alrededor de 0,707, mientras que los del arco superior tienen todos un valor de y menor que -0.707. Lo mismo para izquierda y derecha, pero en ese caso usando la coordenada x.

Por lo tanto, la función simplemente verifica los valores de las coordenadas x e y del vector para decidir en cuál de estos arcos se encuentra. Basado en eso, devuelve el prefijo de cadena de animación.

La declaración de retorno se utiliza para finalizar la ejecución de la llamada a la función y devolver el valor del resultado de la función. Puede salir de una función en cualquier momento. Si no utiliza la palabra clave de retorno , el valor de retorno predeterminado es nulo.

Ahora, podemos editar animates_player() para elegir la dirección de animación correcta:

func animates_player(direction: Vector2):
	if direction != Vector2.ZERO:
		# update last_direction
		last_direction = direction
		
		# Choose walk animation based on movement direction
		var animation = get_animation_direction(last_direction) + "_walk"
		
		# Play the walk animation
		$Sprite.play(animation)
	else:
		# Choose idle animation based on last movement direction and play it
		var animation = get_animation_direction(last_direction) + "_idle"
		$Sprite.play(animation)

Como puede ver, cuando el jugador se está moviendo, guardamos en last_direction el valor actual de direction , para que pueda usarse más tarde para elegir la animación inactiva correcta.

Usamos get_animation_direction() para obtener el prefijo de dirección, al que agregamos, con el operador + , la cadena que indica qué animación queremos ( _walk o _idle ). Luego, como antes, usamos el método play() para reproducir la animación.

Si pruebas el juego ahora, verás que el jugador gira en la dirección en la que camina. Cuando el jugador se detiene, permanece orientado en la última dirección de movimiento.

La velocidad al caminar

Si está utilizando un joypad con palancas analógicas, habrá notado que cuando el jugador se mueve lentamente, la animación de caminar aún se realiza a la velocidad de 10 FPS que configuramos inicialmente. Esto es visualmente desagradable, porque la velocidad de la animación es demasiado alta en comparación con el movimiento real del jugador en el mapa. Por lo tanto, debemos ajustar la animación FPS en función de la velocidad del jugador.

Hacerlo es bastante simple, solo agregue esta nueva línea a animates_player() antes de reproducir la animación de caminar:

$Sprite.frames.set_animation_speed(animation, 2 + 8 * direction.length())

Esta línea accede al recurso SpriteFrame del nodo Sprite y utiliza el método set_animation_speed() para establecer los FPS de la animación indicados por el primer argumento. El segundo argumento es el número de FPS, que calculamos con una fórmula que devuelve un valor de 2 a 10, dependiendo de la longitud del vector de dirección (que puede variar de 0 a 1).

Problema de rebote del stick analógico

Cuando se usa un stick analógico, puede suceder que cuando el jugador camina en una dirección y de repente suelta el stick, termina mirando en la dirección opuesta. Esto puede suceder o no según el controlador y la forma en que el jugador maneja el palo.

Esto se debe al hecho de que, cada vez que sueltas la palanca, vuelve a su posición central. Pero el resorte que causa este movimiento es tan fuerte que el palo sobrepasa el centro, yendo más allá por un corto período de tiempo.

Para solucionar este problema, debemos asegurarnos de que el vector last_direction no se actualice instantáneamente con cada cambio de dirección, sino que el cambio sea gradual para filtrar este “ruido”. Para hacer esto, reemplace la línea que actualiza last_direction con esto:

# gradually update last_direction to counteract the bounce of the analog stick
last_direction = 0.5 * last_direction + 0.5 * direction

Animaciones de ataque y bola de fuego.

El jugador, además de moverse por el mapa, puede atacar a los enemigos con su espada o lanzando bolas de fuego. Como ya hemos creado las animaciones para estas acciones, queremos reproducirlas cuando el jugador presione las teclas de ataque.

En primer lugar, debemos crear acciones de entrada en el panel Mapa de entrada , al que puede acceder haciendo clic en Proyecto → Configuración del proyecto → Mapa de entrada . Crearemos dos acciones:

  • ataque : atacar con la espada; le asignaremos a esta acción la tecla Espacio y el Botón 2 del joypad (que corresponde al botón X del controlador de XBox y al botón Cuadrado del controlador de PlayStation)
  • bola de fuego: lanzar una bola de fuego; le asignaremos a esta acción el botón Control y el Botón 3 del joypad (que corresponde al botón Y del controlador de XBox y al botón Triángulo del controlador de PlayStation).

Para agregar una nueva acción, ingrese el nombre en el campo de texto Acción en la parte superior del panel y presione Agregar . Ya hemos visto el procedimiento para mapear una entrada a una acción en el Tutorial de Godot – Parte 5: Movimiento del jugador , así que consúltalo si no recuerdas cómo hacerlo. El resultado será el siguiente:

Cierre la ventana Configuración del proyecto, vuelva al script y agregue una nueva variable que usaremos para averiguar si hay una animación de ataque en curso:

var attack_playing = false

Necesitamos esta variable porque _physics_process() llama continuamente a animates_player() , por lo que la animación de ataque no tendría tiempo para ejecutarse porque sería reemplazada inmediatamente por una animación de caminar o inactiva. Debido a esto, necesitamos editar el código _physics_process() para llamar a animates_player () solo si attack_playing es falso:

# Animate player based on direction
if not attack_playing:
	animates_player(direction)

Para manejar la entrada de ataque, usaremos el método _input heredado de Node . Esta función se llama cada vez que ocurre cualquier evento de entrada.

Agregue este código al script:

func _input(event):
	if event.is_action_pressed("attack"):
		attack_playing = true
		var animation = get_animation_direction(last_direction) + "_attack"
		$Sprite.play(animation)
	elif event.is_action_pressed("fireball"):
		attack_playing = true
		var animation = get_animation_direction(last_direction) + "_fireball"
		$Sprite.play(animation)

Si siguió el Tutorial de Godot – Parte 5.1: Arrastrar el reproductor con el mouse , entonces ya tiene esta función en el script. Puede agregar este código junto al existente sin ningún problema.

La función funciona así: si se presiona una de las teclas/botones asignados a la acción de ataque , attack_playing se establece en verdadero, para omitir la función animates_player() . Luego, se elige la animación correcta en función de la última dirección del jugador y se realiza con el método play() . Lo mismo ocurre con la acción de la bola de fuego .

Si ejecuta el juego ahora, verá que las animaciones de ataque se realizan correctamente, pero luego el sprite permanece en el último cuadro de la animación y las animaciones de caminar e inactivo ya no se reproducen . Esto sucede porque attack_playing no se restablece en falso en ninguna parte. Luego debemos agregar algún código que restablezca la variable cuando finalice la animación.

Afortunadamente, AnimatedSprite proporciona la señal animation_finished() , que se envía cada vez que se reproduce el último cuadro de una animación. Vincularemos esta señal a una función en nuestro script que restablece attack_playing .

Abra el panel Nodo y, si aún no está seleccionado, haga clic en Señales para ver la lista de señales.

Seleccione animation_finished() y presione Conectar . Se abrirá una ventana para seleccionar el nodo a conectar con la señal. Seleccione Jugador y presione Conectar . El script se abrirá con el cursor posicionado en la función que se acaba de crear. Edítalo así:

func _on_Sprite_animation_finished():
	attack_playing = false

Por último, queremos que la velocidad del jugador disminuya cuando se mueve y ataca simultáneamente. Para hacer esto, simplemente agregue estas líneas a _physics_process() , justo antes de llamar a move_and_collide() :

if attack_playing:
	movement = 0.3 * movement

Conclusiones

En este tutorial, aprendimos cómo usar AnimatedSprite y SpriteFrames para animar sprites y cómo usar scripts para ejecutar animaciones. Además, hemos profundizado nuestro conocimiento del sistema de entrada de Godot.