Animación de personajes

En esta lección final, utilizaremos las herramientas de animación incorporadas de Godot para hacer que nuestros personajes floten y aleteen. Aprenderás a diseñar animaciones en el editor y a utilizar código para que tu juego cobre vida.

image0

Vamos a empezar con una Introducción al uso del editor de animación.

Usando el editor de animación

El motor viene con herramientas para crear animaciones en el editor. Luego puedes usar el código para reproducirlas y controlarlas en tiempo de ejecución.

Abre la escena del jugador, selecciona el nodo del jugador y agrega un nodo AnimationPlayer.

Aparece el panel inferior de Animación.

|image1|

Cuenta con una barra de herramientas y un menú desplegable de animación en la parte superior, un editor de pistas en el medio que actualmente está vacío, y opciones de filtro, ajuste y zoom en la parte inferior.

Vamos a crear una animación. Haz clic en Animación -> Nueva.

image2

Nombra la animación "float".

|image3|

Una vez que hayas creado la animación, aparecerá la línea de tiempo con números que representan el tiempo en segundos.

image4

Queremos que la animación comience a reproducirse automáticamente al inicio del juego. Además, debe repetirse en bucle.

Para hacerlo, puedes hacer clic en el botón con un icono de "A+" en la barra de herramientas de animación y en las flechas de repetición, respectivamente.

image5

También puedes fijar el editor de animación haciendo clic en el icono de pin en la parte superior derecha. Esto evita que se pliegue cuando haces clic en el viewport y deseleccionas los nodos.

image6

Establece la duración de la animación en 1.2 segundos en la parte superior derecha del panel.

image7

Deberías ver que la cinta gris se ensancha un poco. Esto te muestra el inicio y el final de tu animación, y la línea vertical azul es tu cursor de tiempo.

image8

Puedes hacer clic y arrastrar el deslizador en la parte inferior derecha para hacer zoom dentro y fuera de la línea de tiempo.

image9

La animación de flotar

Con el nodo AnimationPlayer, puedes animar la mayoría de las propiedades en tantos nodos como necesites. Observa el icono de llave junto a las propiedades en el Inspector. Puedes hacer clic en cualquiera de ellos para crear un fotograma clave, un par de tiempo y valor para la propiedad correspondiente. El fotograma clave se inserta donde se encuentra el cursor de tiempo en la línea de tiempo.

Vamos a insertar nuestras primeras teclas. Aquí, animaremos tanto la traducción como la rotación del nodo Character.

Selecciona el nodo Character y haz clic en el icono de la llave junto a Translation en el Inspector. Haz lo mismo para Rotation Degrees.

image10

Aparecerán dos pistas en el editor con un icono de diamante que representa cada fotograma clave.

image11

Puedes hacer clic y arrastrar los diamantes para moverlos en el tiempo. Mueve la tecla de traducción a 0.2 segundos y la tecla de rotación a 0.1 segundos.

image12

Mueve el cursor de tiempo a 0.5 segundos haciendo clic y arrastrando en la línea de tiempo gris. En el Inspector, establece el eje Y de Translation a aproximadamente 0.65 metros y el eje X de Rotation Degrees a 8.

image13

Crea un fotograma clave para ambas propiedades y desplaza la tecla de traducción a 0.7 segundos arrastrándola en la línea de tiempo.

image14

Nota

Una conferencia sobre los principios de la animación está más allá del alcance de este tutorial. Solo ten en cuenta que no quieres que todo esté cronometrado y espaciado de manera uniforme. En su lugar, los animadores juegan con el tiempo y el espacio, dos principios fundamentales de la animación. Quieres crear desfases y contrastes en el movimiento de tu personaje para que se sienta vivo.

Mueve el cursor de tiempo al final de la animación, en 1.2 segundos. Establece la traducción en el eje Y a aproximadamente 0.35 y la rotación en el eje X a -9 grados. Una vez más, crea una tecla para ambas propiedades.

Puedes previsualizar el resultado haciendo clic en el botón de reproducción o presionando Shift + D. Haz clic en el botón de detener o presiona S para detener la reproducción.

image15

Puedes ver que el motor interpola entre tus fotogramas clave para producir una animación continua. Sin embargo, en este momento, el movimiento se siente muy robótico. Esto se debe a que la interpolación predeterminada es lineal, lo que provoca transiciones constantes, a diferencia de cómo se mueven los seres vivos en el mundo real.

Podemos controlar la transición entre los fotogramas clave utilizando curvas de interpolación.

Haz clic y arrastra alrededor de los dos primeros fotogramas clave en la línea de tiempo para seleccionarlos en un recuadro.

image16

Puedes editar las propiedades de ambos fotogramas clave simultáneamente en el Inspector, donde puedes ver una propiedad de Easing (interpolación).

image17

Haz clic y arrastra en la curva, tirándola hacia la izquierda. Esto la hará suavizar, es decir, transicionar rápidamente al principio y luego disminuir la velocidad a medida que el cursor de tiempo alcance el siguiente fotograma clave.

image18

Reproduce la animación nuevamente para ver la diferencia. La primera mitad debería sentirse un poco más animada.

Aplica un ease-out al segundo fotograma clave en la pista de rotación.

image19

Haz lo contrario para el segundo fotograma clave de traducción, arrastrándolo hacia la derecha.

image20

Tu animación debería lucir algo así.

image21

Nota

Las animaciones actualizan las propiedades de los nodos animados en cada fotograma, sobrescribiendo los valores iniciales. Si animáramos directamente el nodo Player, nos impediría moverlo en el código. Es aquí donde el nodo Pivot resulta útil: aunque animemos el nodo Character, aún podemos mover y rotar el Pivot y aplicar cambios adicionales en la capa superior de la animación mediante un script.

Si juegas el juego, ¡la criatura del jugador ahora flotará!

Si la criatura está un poco demasiado cerca del suelo, puedes mover el Pivot hacia arriba para compensarlo.

Controlando la animación en código

Podemos usar código para controlar la reproducción de la animación según la entrada del jugador. Cambiemos la velocidad de la animación cuando el personaje se esté moviendo.

Abre el script del Player haciendo clic en el ícono de script que está junto a él.

image22

En _physics_process(), después de la línea donde verificamos el vector direction, agrega el siguiente código.

func _physics_process(delta):
    #...
    #if direction != Vector3.ZERO:
        #...
        $AnimationPlayer.playback_speed = 4
    else:
        $AnimationPlayer.playback_speed = 1

Este código hace que cuando el jugador se mueve, multipliquemos la velocidad de reproducción por 4. Cuando se detiene, la restablecemos a su valor normal.

Mencionamos que el punto de pivote puede aplicar transformaciones adicionales sobre la animación. Podemos hacer que el personaje se arquee al saltar utilizando la siguiente línea de código. Agrégala al final de _physics_process().

func _physics_process(delta):
    #...
    $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse

Animando los enemigos

Aquí tienes otro truco útil con las animaciones en Godot: siempre y cuando utilices una estructura de nodos similar, puedes copiarlas en diferentes escenas.

Por ejemplo, tanto las escenas Mob como Player tienen un nodo Pivot y un nodo Character, por lo que podemos reutilizar animaciones entre ellos.

Abre la escena del Player, selecciona el nodo de animación y abre la animación "float". A continuación, haz clic en Animación > Copiar. Luego abre el archivo Mob.tscn y abre su nodo de animación. Haz clic en Animación > Pegar. ¡Eso es todo! Ahora todos los monstruos reproducirán la animación "float".

Podemos cambiar la velocidad de reproducción según la variable random_speed de la criatura. Abre el script de Mob y al final de la función initialize(), agrega la siguiente línea.

func initialize(start_position, player_position):
    #...
    $AnimationPlayer.playback_speed = random_speed / min_speed

Y con eso, has terminado de programar tu primer juego completo en 3D.

Felicitaciones!

En la próxima parte, haremos un breve resumen de lo que aprendiste y te proporcionaremos algunos enlaces para que puedas seguir aprendiendo más. Pero por ahora, aquí tienes los scripts completos de Player.gd y Mob.gd para que puedas comparar tu código con ellos.

Aqui está el script Player.

extends KinematicBody

# Emitted when the player was hit by a mob.
signal hit

# How fast the player moves in meters per second.
export var speed = 14
# The downward acceleration when in the air, in meters per second per second.
export var fall_acceleration = 75
# Vertical impulse applied to the character upon jumping in meters per second.
export var jump_impulse = 20
# Vertical impulse applied to the character upon bouncing over a mob in meters per second.
export var bounce_impulse = 16

var velocity = Vector3.ZERO


func _physics_process(delta):
    var direction = Vector3.ZERO

    if Input.is_action_pressed("move_right"):
        direction.x += 1
    if Input.is_action_pressed("move_left"):
        direction.x -= 1
    if Input.is_action_pressed("move_back"):
        direction.z += 1
    if Input.is_action_pressed("move_forward"):
        direction.z -= 1

    if direction != Vector3.ZERO:
        direction = direction.normalized()
        $Pivot.look_at(translation + direction, Vector3.UP)
        $AnimationPlayer.playback_speed = 4
    else:
        $AnimationPlayer.playback_speed = 1

    velocity.x = direction.x * speed
    velocity.z = direction.z * speed

    # Jumping
    if is_on_floor() and Input.is_action_just_pressed("jump"):
        velocity.y += jump_impulse

    velocity.y -= fall_acceleration * delta
    velocity = move_and_slide(velocity, Vector3.UP)

    for index in range(get_slide_count()):
        var collision = get_slide_collision(index)
        if collision.collider.is_in_group("mob"):
            var mob = collision.collider
            if Vector3.UP.dot(collision.normal) > 0.1:
                mob.squash()
                velocity.y = bounce_impulse

    $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse


func die():
    emit_signal("hit")
    queue_free()


func _on_MobDetector_body_entered(_body):
    die()

Y el script Mob.

extends KinematicBody

# Emitted when the player jumped on the mob.
signal squashed

# Minimum speed of the mob in meters per second.
export var min_speed = 10
# Maximum speed of the mob in meters per second.
export var max_speed = 18

var velocity = Vector3.ZERO


func _physics_process(_delta):
    move_and_slide(velocity)


func initialize(start_position, player_position):
    look_at_from_position(start_position, player_position, Vector3.UP)
    rotate_y(rand_range(-PI / 4, PI / 4))

    var random_speed = rand_range(min_speed, max_speed)
    velocity = Vector3.FORWARD * random_speed
    velocity = velocity.rotated(Vector3.UP, rotation.y)

    $AnimationPlayer.playback_speed = random_speed / min_speed


 func squash():
    emit_signal("squashed")
    queue_free()


func _on_VisibilityNotifier_screen_exited():
    queue_free()