Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

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

В цьому останньому уроці ми будемо використовувати вбудовані в Godot інструменти анімації, щоб наші персонажі плавали і підстрибували. Ви навчитеся розробляти анімацію в редакторі та використовувати код, щоб ваша гра виглядала живою.

image0

Почнемо з вступу до використання редактора анімації.

Використання редактора анімації

Рушій поставляється з інструментами для створення анімації в редакторі. Потім ви можете використовувати код для відтворення та керування нею під час виконання.

Відкрийте сцену програвача, виберіть вузол Player і додайте вузол AnimationPlayer.

Внизу з'явиться панель Анімація.

image1

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

Давайте створимо анімацію. Натисніть Анімація -> Новий.

image2

Назвіть анімацію "float".

image3

Після створення анімації з'являється часова шкала з цифрами, що позначають час у секундах.

image4

Ми хочемо, щоб анімація почала відтворюватися автоматично на початку гри. Крім того, вона має бути зациклена.

Щоб зробити це, ви можете натиснути кнопку автовідтворення (Автовідтворення) на панелі інструментів анімації та стрілки циклу, відповідно.

image5

Ви також можете закріпити редактор анімації, натиснувши значок шпильки у верхньому правому куті. Це запобігає його складанню, коли ви натискаєте на вікно перегляду і знімаєте виділення з вузлів.

image6

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

Ви повинні побачити, що сіра полоса трохи розширилася. Вона показує вам початок і кінець вашої анімації, а вертикальна синя позначка - ваш курсор часу.

image7

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

image8

Анімація плавання

За допомогою вузла AnimationPlayer ви можете анімувати більшість властивостей на всіх вузлах, для яких це потрібно. Турайте на значок ключа поруч із властивостями в інспекторі. Ви можете клацнути будь-який з них, щоб створити ключовий кадр, пару з часу та значень для відповідної властивості. Ключовий кадр вставляється там, де курсор часу знаходиться на часовій шкалі.

Вставимо наші перші клавіші. Тут ми анімуватимемо як положення, так і обертання вузла Символ.

Виберіть Персонаж і в Інспекторі розгорніть розділ Трансформація. Натисніть значок ключа поруч із Положення та Обертання.

image9

../../_images/curves.webp

Для цього підручника просто створіть RESET Track(s), який є вибором за замовчуванням

У редакторі з'являться дві доріжки зі значком ромбика, що представляє кожен ключовий кадр.

image10

Ви можете клацнути і перетягнути діаманти, щоб перемістити їх у часі. Перемістіть клавішу положення на 0.3 секунди, а клавішу обертання на 0.1 секунди.

image11

Перемістіть курсор часу на 0,5, клацнувши та перетягнувши сіру часову шкалу або ввівши його в поле введення.

timeline_05_click

У Інспекторі встановіть вісь Y для Положення на 0,65 метра, а вісь Обертання X на 8.

Якщо ви не бачите властивостей на панелі Inspector, спочатку знову клацніть вузол Character у доці Scene.

image12

Створіть ключовий кадр для обох властивостей

second_keys_both

Тепер перемістіть позиційний ключовий кадр на 0,7 секунди, перетягнувши його на шкалі часу.

image13

Примітка

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

Перемістіть курсор часу в кінець анімації, на 1.2 секунду. Встановіть позицію Y приблизно на 0.35, а поворот X на -9 градусів. Знову створіть ключ для обох властивостей.

animation_final_keyframes

Ви можете переглянути результат, натиснувши кнопку відтворення або клавіші Shift + D. Натисніть кнопку зупинки або S, щоб зупинити відтворення.

image14

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

Ми можемо контролювати перехід між ключовими кадрами за допомогою кривих пом'якшення.

Клацніть і обведіть перші два ключа на часовій шкалі, щоб вибрати їх.

image15

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

image16

Клацніть і потягніть криву вліво. Це зробить анімацію легкою, тобто переміщення буде спочатку прискорюватися, а потім сповільнюватися, по мірі наближення курсора часу до наступного ключового кадру.

image17

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

Застосуйте пом'якшення до другого ключового кадру на треку обертання.

image18

Зробіть протилежне для другого позиційного ключового кадру, перетягнувши його праворуч.

image19

Анімація повинна виглядати приблизно так.

image20

Примітка

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

Якщо ви запустите гру персонаж гравця тепер буде плавати!

Якщо істота знаходиться занадто близько до підлоги, ви можете перемістити Pivot вгору, щоб змістити її.

Керування анімацією в коді

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

Відкрийте скрипт Плеєр, натиснувши на іконку скрипта поруч з ним.

image21

В _physics_process(), після рядка, де ми перевіряємо direction вектор, додайте наступний код.

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

Цей код працює таким чином, що коли гравець рухається, ми множимо швидкість відтворення на 4. Коли він зупиняється, оновлюємо її до нормального стану.

Ми згадували, що Pivot може накладати трансформації поверх анімації. Ми можемо зробити дугу символу при стрибку за допомогою наступного рядка коду. Додайте його в кінець _physics_process().

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

Анімація монстрів

Ось ще один приємний трюк з анімацією в Godot: поки ви використовуєте подібну структуру вузлів, ви можете скопіювати її на різні сцени.

Наприклад, обидві сцени Mob і Player мають вузол Pivot і вузол Character, тому ми можемо повторно використовувати анімацію між ними.

Відкрийте сцену Player, виберіть вузол AnimationPlayer і натисніть Animation > Manage Animations.... Натисніть кнопку Копіювати анімацію в буфер обміну (два маленькі квадрати) поряд з анімацією float. Натисніть OK, щоб закрити вікно.

Потім відкрийте mob.tscn, створіть дочірній вузол AnimationPlayer та виберіть його. Натисніть Анімація > Керування анімацією, потім Нова бібліотека. Ви повинні побачити повідомлення "Буде створено глобальну бібліотеку". Залиште текстове поле порожнім і натисніть OK. Натисніть значок Вставити (буфер обміну), і він має з'явитися у вікні. Натисніть OK, щоб закрити вікно.

Далі переконайтеся, що кнопка автовідтворення (Автовідтворення) і стрілки циклу (Animation looping) також увімкнено в редакторі анімації на нижній панелі. Ось і все; тепер усі монстри відтворюватимуть анімацію плавання.

Ми можемо змінити швидкість відтворення на основі швидкості монстра 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()
        # Setting the basis property will affect the rotation of the node.
        $Pivot.basis = Basis.looking_at(direction)
        $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 -45 and +45 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