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.

Анимация персонажей

В этом заключительном уроке мы используем встроенные в Godot инструменты анимации, чтобы заставить наших персонажей плавать и махать руками. Вы научитесь создавать анимацию в редакторе и использовать код, чтобы ваша игра стала живой.

image0

Мы начнем с введения в использование редактора анимации.

Использование редактора анимаций

Движок поставляется с инструментами для создания анимаций в редакторе. Затем вы можете использовать код для воспроизведения и управления ими во время выполнения.

Open the player scene, select the Player node, and add an AnimationPlayer node.

На нижней панели появится вкладка Animation.

image1

В ней есть панель инструментов и раскрывающееся меню анимации в верхней части, редактор дорожек в середине, который в настоящее время пуст, и опции фильтрации, привязки и масштабирования в нижней части.

Давайте создадим анимацию. Щёлкните на Animation -> New.

image2

Назовите анимацию "float".

image3

Once you've created the animation, the timeline appears with numbers representing time in seconds.

image4

Мы хотим, чтобы анимация начинала воспроизводиться автоматически в начале игры. Кроме того, она должна быть зациклена.

Для этого можно нажать кнопку с пиктограммой "A+" на панели инструментов анимации и зацикленные стрелки соответственно.

image5

Вы также можете закрепить редактор анимации, нажав на значок булавки в правом верхнем углу. Это предотвратит его сворачивание при нажатии на область просмотра и отмене выбора узлов.

image6

Установите длительность анимации 1.2 секунды в правом верхнем углу панели.

image7

Вы должны увидеть, как серая лента немного расширится. Она показывает начало и конец вашей анимации, а вертикальная синяя линия - это ваш временной курсор.

image8

Вы можете щелкнуть и перетащить ползунок в правом нижнем углу, чтобы увеличить или уменьшить масштаб временной шкалы.

image9

Плавающая анимация

С помощью узла анимации игрока вы можете анимировать большинство свойств на стольких узлах, сколько вам нужно. Обратите внимание на значок ключа рядом со свойствами в инспекторе. Вы можете щёлкнуть любой из них, чтобы создать ключевой кадр - пару времени и значения для соответствующего свойства. Ключевой кадр будет вставлен туда, где находится курсор времени на временной шкале.

Let's insert our first keys. Here, we will animate both the position and the rotation of the Character node.

Select the Character and in the Inspector expand the Transform section. Click the key icon next to Position, and Rotation.

image10

../../_images/curves.webp

For this tutorial, just create RESET Track(s) which is the default choice

В редакторе появятся две дорожки с ромбовидным значком, обозначающим каждый ключевой кадр.

image11

You can click and drag on the diamonds to move them in time. Move the position key to 0.3 seconds and the rotation key to 0.1 seconds.

image12

Move the time cursor to 0.5 seconds by clicking and dragging on the gray timeline.

../../_images/timeline_05_click.webp

In the Inspector, set the Position's Y axis to 0.65 meters and the Rotation' X axis to 8.

image13

Create a keyframe for both properties

../../_images/second_keys_both.webp

Now, move the position keyframe to 0.7 seconds by dragging it on the timeline.

image14

Примечание

Лекция о принципах анимации выходит за рамки данного руководства. Просто отметим, что вы не хотите равномерно распределять время и пространство. Вместо этого аниматоры играют с таймингом и интервалом - двумя основными принципами анимации. Вы хотите смещать и контрастировать движения персонажа, чтобы сделать их живыми.

Move the time cursor to the end of the animation, at 1.2 seconds. Set the Y position to about 0.35 and the X rotation to -9 degrees. Once again, create a key for both properties.

../../_images/animation_final_keyframes.webp

Вы можете просмотреть результат, нажав на кнопку воспроизведения или нажав Shift + D. Нажмите кнопку "Stop" или нажмите S, чтобы остановить воспроизведение.

image15

Вы можете видеть, что движок интерполируется между вашими ключевыми кадрами для создания непрерывной анимации. Однако на данный момент движение кажется очень роботизированным. Это происходит потому, что интерполяция по умолчанию линейная, что приводит к постоянным переходам, в отличие от того, как живые существа двигаются в реальном мире.

Мы можем управлять переходом между ключевыми кадрами с помощью кривых смягчения.

Щёлкните и перетащите вокруг первых двух ключей на временной шкале, чтобы выделить их.

image16

Вы можете редактировать свойства обеих ключей одновременно в инспекторе, где вы можете увидеть свойство Easing.

image17

Щёлкните и перетащите кривую, потянув её влево. Это заставит её ослабнуть, то есть сначала переход будет быстрым, а затем замедлится, когда курсор времени достигнет следующего ключевого кадра.

image18

Воспроизведите анимацию ещё раз, чтобы увидеть разницу. Первая половина уже должна казаться немного более подпрыгивающей.

Примените ослабление ко второму ключевому кадру на дорожке вращения.

|изображение19|

Do the opposite for the second position keyframe, dragging it to the right.

|изображение20|

Ваша анимация должна выглядеть примерно так.

|изображение21|

Примечание

Анимации обновляют свойства анимируемых узлов каждый кадр, переопределяя начальные значения. Если бы мы напрямую анимировали узел Player, это не позволило бы нам перемещать его в коде. Именно здесь пригодится узел Pivot: даже если мы анимировали Character, мы все равно можем перемещать и вращать Pivot и накладывать изменения поверх анимации в сценарии.

Если вы играете в игру, то существо игрока теперь будет плавать!

If the creature is a little too close to the floor, you can move the Pivot up to offset it.

Управление анимацией в коде

Мы можем использовать код для управления воспроизведением анимации на основе ввода данных игроком. Давайте изменим скорость анимации, когда персонаж движется.

Open the Player's script by clicking the script icon next to it.

|изображение22|

В _physics_process(), после строки, где мы проверяем вектор direction, добавьте следующий код.

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

Этот код делает так, что когда игрок движется, мы умножаем скорость воспроизведения на 4. Когда они останавливаются, мы восстанавливаем нормальное значение.

We mentioned that the Pivot could layer transforms on top of the animation. We can make the character arc when jumping using the following line of code. Add it at the end of _physics_process().

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

Анимация мобов

Вот ещё один приятный приём с анимациями в Godot: пока вы используете похожую структуру узлов, вы можете копировать их в разные сцены.

For example, both the Mob and the Player scenes have a Pivot and a Character node, so we can reuse animations between them.

Open the Player scene, select the AnimationPlayer node and open the "float" animation. Next, click on Animation > Copy. Then open mob.tscn, create an AnimationPlayer child node and select it. Click Animation > Paste and make sure that the button with an "A+" icon (Autoplay on Load) and the looping arrows (Animation looping) are also turned on in the animation editor in the bottom panel. That's it; all monsters will now play the float animation.

Мы можем изменить скорость воспроизведения в зависимости от random_speed существа. Откройте скрипт Mob и в конце функции initialize() добавьте следующую строку.

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

И на этом вы закончили написание кода для своей первой полноценной 3D-игры.

Поздравляем!

В следующей части мы быстро подведём итоги того, что вы узнали, и дадим вам несколько ссылок для дальнейшего изучения. Но пока что здесь представлены полные версии Player.gd и Mob.gd, чтобы вы могли сверить с ними свой код.

Вот скрипт 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()

И скрипт 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