Work in progress

The content of this page was not yet updated for Godot 4.2 and may be outdated. If you know how to improve this page or you can confirm that it's up to date, feel free to open a pull request.

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 Player 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 claves. Aquí, animaremos tanto la posición como la rotación del nodo Character.

Selecciona el nodo Character y en el Inspector expande la sección Transform. Haz clic en el icono de la llave junto a Position y Rotation.

image10

../../_images/curves.webp

Para este tutorial, crea una pista RESET que es la elección por defecto

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 clave de posición a 0.3 segundos y la clave de rotación a 0.1 segundos.

image12

Mueve el cursor a 0.5 segundos haciendo clic y arrastrando en la línea de tiempo gris.

../../_images/timeline_05_click.webp

En el Inspector, establece el eje Y de Position a aproximadamente 0.65 metros y el eje X de Rotation a 8.

image13

Crea claves para ambas propiedades

../../_images/second_keys_both.webp

Ahora, desplaza la clave de posición a 0.7 segundos arrastrándola por 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 posició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 clave para ambas propiedades.

../../_images/animation_final_keyframes.webp

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 la segunda clave de posición, arrastrándola 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.speed_scale = 4
    else:
        $AnimationPlayer.speed_scale = 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 Pivot 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 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 de flotar.

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.speed_scale = 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 CharacterBody3D

signal hit

# How fast the player moves in meters per second.
@export var speed = 14
# The downward acceleration while in the air, in meters per second squared.
@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 target_velocity = Vector3.ZERO


func _physics_process(delta):
    # We create a local variable to store the input direction
    var direction = Vector3.ZERO

    # We check for each move input and update the direction accordingly
    if Input.is_action_pressed("move_right"):
        direction.x = direction.x + 1
    if Input.is_action_pressed("move_left"):
        direction.x = direction.x - 1
    if Input.is_action_pressed("move_back"):
        # Notice how we are working with the vector's x and z axes.
        # In 3D, the XZ plane is the ground plane.
        direction.z = direction.z + 1
    if Input.is_action_pressed("move_forward"):
        direction.z = direction.z - 1

    # Prevent diagonal movement being very fast
    if direction != Vector3.ZERO:
        direction = direction.normalized()
        $Pivot.look_at(position + direction,Vector3.UP)
        $AnimationPlayer.speed_scale = 4
    else:
        $AnimationPlayer.speed_scale = 1

    # Ground Velocity
    target_velocity.x = direction.x * speed
    target_velocity.z = direction.z * speed

    # Vertical Velocity
    if not is_on_floor(): # If in the air, fall towards the floor
        target_velocity.y = target_velocity.y - (fall_acceleration * delta)

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

    # Iterate through all collisions that occurred this frame
    # in C this would be for(int i = 0; i < collisions.Count; i++)
    for index in range(get_slide_collision_count()):
        # We get one of the collisions with the player
        var collision = get_slide_collision(index)

        # If the collision is with ground
        if collision.get_collider() == null:
            continue

        # If the collider is with a mob
        if collision.get_collider().is_in_group("mob"):
            var mob = collision.get_collider()
            # we check that we are hitting it from above.
            if Vector3.UP.dot(collision.get_normal()) > 0.1:
                # If so, we squash it and bounce.
                mob.squash()
                target_velocity.y = bounce_impulse
                # Prevent further duplicate calls.
                break

    # Moving the Character
    velocity = target_velocity
    move_and_slide()

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

# And this function at the bottom.
func die():
    hit.emit()
    queue_free()

func _on_mob_detector_body_entered(body):
    die()

Y el script Mob.

extends CharacterBody3D

# 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

# Emitted when the player jumped on the mob
signal squashed

func _physics_process(_delta):
    move_and_slide()

# This function will be called from the Main scene.
func initialize(start_position, player_position):
    # We position the mob by placing it at start_position
    # and rotate it towards player_position, so it looks at the player.
    look_at_from_position(start_position, player_position, Vector3.UP)
    # Rotate this mob randomly within range of -90 and +90 degrees,
    # so that it doesn't move directly towards the player.
    rotate_y(randf_range(-PI / 4, PI / 4))

    # We calculate a random speed (integer)
    var random_speed = randi_range(min_speed, max_speed)
    # We calculate a forward velocity that represents the speed.
    velocity = Vector3.FORWARD * random_speed
    # We then rotate the velocity vector based on the mob's Y rotation
    # in order to move in the direction the mob is looking.
    velocity = velocity.rotated(Vector3.UP, rotation.y)

    $AnimationPlayer.speed_scale = random_speed / min_speed

func _on_visible_on_screen_notifier_3d_screen_exited():
    queue_free()

func squash():
    squashed.emit()
    queue_free() # Destroy this node