Keep your place in this quest

Log in or sign up for free to subscribe, follow lesson progress, and access more learning content.

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

Чтобы сделать это практичным, мы будем использовать стандартный шаблон Player из стартовых проектов Cave в качестве основного ориентира. Если вы создаете Third Person Game или Top Down Game, Cave генерирует игрока, который уже перемещается, имеет видимого анимированного персонажа и воспроизводит базовые анимации локомоции.

image.png

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

Вы узнаете:

  • Как построен стартовый шаблон Player.
  • Почему визуальная сетка является дочерним объектом вместо того, чтобы находиться на корневом объекте игрока.
  • Что делает Компонент Анимации.
  • Как получить аниматор из Python (аналогичная логика применяется для Логических Блоков).
  • Как воспроизводить анимации по имени.
  • Как работают смешивание, слои и фильтры костей.
  • Как обратные вызовы анимации и сокеты вписываются в систему.

Структура Шаблона Игрока

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

Структура примерно такая:

image.png

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

Стартовый враг использует очень похожую идею. Корневая сущность врага имеет физику персонажа и поведение, а ее дочерняя сущность Mesh содержит видимую сетку и Компонент Анимации.

Корневая Сущность Игрока

Корневая сущность Player представляет игрового персонажа. Обычно она содержит:

  • Компонент Transform.
  • Компонент Character.
  • Компоненты Python для движения игрока, UI, помощников анимации и другой игровой логики.
  • Свойства, такие как здоровье или необязательные настройки поведения.
  • Дочерние элементы, используемые для камеры, UI, визуальной сетки и вспомогательных объектов.

Компонент Character особенно важен, потому что он обрабатывает физику в стиле персонажа: ходьба, прыжки, столкновение, наклоны и движение относительно мира. Это означает, что коренной Player в основном отвечает за игровой процесс и физику.

Дочерняя Сущность Mesh

Внутри шаблона игрока есть дочерняя сущность под названием Mesh. Эта дочерняя сущность обычно содержит:

  • Компонент Transform.
  • Один или несколько Компонентов Mesh.
  • Компонент Animation.

Компонент Mesh придает персонажу его видимую 3D-модель и материал. Если персонаж использует несколько материалов, Cave может представить это с помощью нескольких компонентов Mesh, следуя тем же правилам многоматериальности, объясненным в уроке по импорту.

Компонент Анимации — это то, что оценивает каркас и воспроизводит анимации.

Таким образом, простым образом:

Сущность Основная Ответственность
Player Физика, движение, игровая логика, свойства.
Player -> Mesh Видимая модель, материал, анимация каркаса.

Это разделение является намеренным.

Почему Mesh Является Дочерней Сущностью

Вы можете задаться вопросом, почему сетка не помещается непосредственно на корневую сущность Player.

Причина в том, что физическому персонажу и видимому анимированному персонажу часто нужны разные трансформации.

Корневая трансформация Player представляет тело игрового процесса. Это то, что перемещается по миру, сталкивается с стенами и несет Компонент Character. Дочерняя трансформация Mesh представляет, как выглядит персонаж.

Это разделение полезно в многих ситуациях:

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

Например, представьте, что игрок движется влево. Если у вас есть правильная анимация "идти влево", вы можете хотеть, чтобы сетка персонажа продолжала смотреть вперед, в то время как анимация обрабатывает боковое движение. Но если у вас нет анимации вбок, вы можете захотеть, чтобы сетка повернулась налево, чтобы персонаж визуально шел в этом направлении.

Обе ситуации действительны.

Им требуются отдельные трансформации:

Трансформация Управляет
Корневая трансформация Player Физическая позиция, тело игрового процесса, движение по миру.
Дочерняя трансформация Mesh Визуальная ориентация, масштаб, смещения, представление анимации.

Вот почему стартовый игрок и враг держат анимированную сетку в качестве дочерней сущности.

Что Делает Компонент Анимации

Компонент Animation — это основной компонент, используемый для воспроизведения скелетных анимаций на сущности.

Он требует:

  • Каркас Armature.
  • Стандартную Animation.
  • Действительный Компонент Mesh на той же сущности (или несколько).

Каркас определяет скелет. Анимация определяет, как этот скелет движется со временем. Компонент Анимации оценивает окончательную анимированную позу и применяет ее к видимому персонажу.

В стандартном стартовом игроке дочерний элемент Mesh использует:

  • Proto Mesh
  • Proto Mat
  • Proto Armature
  • p-idle в качестве стандартной анимации (анимация ожидания)

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

Каркасы, Анимации и Переназначение

  • Каркас Armature — это скелет персонажа. Он содержит кости, которые следуют за сеткой.
  • Анимация Animation хранит движение во времени для костей в каркасе.

Компонент Анимации связывает эти элементы:

Элемент Значение
Mesh Видимая модель персонажа.
Armature Скелет внутри персонажа.
Animation Движение, такое как ожидание, ходьба, бег или падение.
Компонент Анимации Компонент, который воспроизводит анимацию на сущности.

Cave также может переназначать анимации, когда это возможно. Если воспроизводимая анимация принадлежит другому совместимому каркасу, Компонент Анимации может использовать переназначение, чтобы применить это движение к текущему каркасу.

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


Получение Аниматора Из Python

Теперь давайте начнем исследовать, как мы можем анимировать персонажей через логику.

В проектах Cave общепринятая практика заключается в том, чтобы хранить игровую логику игрока на корневой сущности Player, а затем получать дочерний элемент Mesh из этого кода. Стандартные стартовые скрипты следуют этой идее.

Вот основной шаблон:

import cave

class PlayerAnimationExample(cave.Component):
    def start(self, scene: cave.Scene):
        self.mesh : cave.Entity = self.entity.getChild("Mesh")
        self.animator : cave.AnimationComponent = self.mesh.get("Animation")

    def update(self):
        # Пример: воспроизведение анимации ожидания.
        self.animator.playByName("p-idle", blend=0.2, loop=True)

В этом примере:

  • self.entity — это корневая сущность Player.
  • getChild("Mesh") находит визуальную дочернюю сущность.
  • self.mesh.get("Animation") получает Компонент Анимации.
  • Переменная называется animator, что является распространенной практикой именования в скриптах Cave.
  • playByName(...) воспроизводит анимационный актив по имени.

Это основное соединение, которое вам нужно перед тем, как управлять анимациями через код.

Воспроизведение Анимаций По Имени

Наиболее распространенный метод, который вы будете использовать — это playByName.

Его базовое использование выглядит так:

self.animator.playByName("p-walk", blend=0.2, loop=True)

Важные параметры:

Параметр Значение
anim Имя анимационного актива, который нужно воспроизвести.
blend Как долго, в секундах, Cave должен смешивать новую анимацию.
loop Должна ли анимация повторяться.
layer Какой слой анимации должен воспроизводить анимацию.

Например:

self.animator.playByName("p-idle", blend=0.2, loop=True)
self.animator.playByName("p-walk", blend=0.2, loop=True)
self.animator.playByName("p-run", blend=0.2, loop=True)
self.animator.playByName("p-fall", blend=0.2, loop=True)

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

Простой Пример Локомоции

Распространенная настройка анимации игрока:

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

Вот упрощенный пример:

import cave

class SimplePlayerAnimator(cave.Component):
    def start(self, scene: cave.Scene):
        self.character : cave.CharacterComponent = self.entity.get("Character")

        self.mesh : cave.Entity = self.entity.getChild("Mesh")
        self.meshTransform : cave.TransformComponent = self.mesh.getTransform() if self.mesh else None
        self.animator      : cave.AnimationComponent = self.mesh.get("Animation") if self.mesh else None

    def update(self):
        if self.animator is None or self.character is None:
            return

        direction = self.character.getWalkDirection()
        isMoving = direction.length() > 0
        isRunning = False # Замените это своим собственным вводом или игровым условием.

if self.character.onGround():
            if isMoving:
                if isRunning:
                    self.animator.playByName("p-run", blend=0.2, loop=True)
                else:
                    self.animator.playByName("p-walk", blend=0.2, loop=True)
            else:
                self.animator.playByName("p-idle", blend=0.2, loop=True)
        else:
            self.animator.playByName("p-fall", blend=0.2, loop=True)

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

Поворот сетки в сторону движения

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

Начальный игрок делает это, когда персонаж движется:

if direction.length() > 0 and self.meshTransform:
    self.meshTransform.lookAtSmooth(
        self.entity.getTransform().transformDirection(-direction),
        6.0 * cave.getDeltaTime()
    )

Важная идея заключается не в точной математике. Важная идея заключается в том, что игровое тело и визуальная сетка могут контролироваться отдельно.

Это позволяет вам выбрать, как персонаж должен смотреть:

  • Смотреть в сторону движения.
  • Продолжать смотреть вперёд при страйфе.
  • Плавно поворачиваться в сторону желаемого направления.
  • Использовать разные правила визуальной ориентации для разных игровых режимов.

Это одна из основных причин, по которой персонаж-начальник имеет дочернюю Mesh сущность.

Смешивание анимаций

Смешивание анимаций делает переходы более плавными.

Без смешивания переход от одной анимации к другой может произойти мгновенно. С смешиванием Cave может плавно переходить между анимациями на одном слое.

Например:

# От этого:
self.animator.playByName("p-idle", blend=0.2, loop=True)

# К этому:
self.animator.playByName("p-walk", blend=0.2, loop=True)

Здесь blend=0.2 означает, что Cave будет смешивать новую анимацию в течение 0.2 секунд.

Это особенно важно для персонажей. Игрок может легко заметить резкие всплески анимации, даже в прототипе.

Хорошие значения для начинающих обычно невелики:

  • 0.1 для очень быстрых переходов.
  • 0.2 для нормальных переходов локомоции.
  • 0.4 или больше для медленных, более тяжёлых переходов.

Вы должны настроить это по своему усмотрению.

Слои анимации

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

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

Например:

self.animator.playByName("p-walk", blend=0.2, layer=0, loop=True)

Если у вас есть анимация для верхней части тела, такая как атака, прицеливание или перезарядка, вы можете воспроизвести её на другом слое:

self.animator.playByName("Attack", blend=0.1, layer=1, loop=False)

Идея такова:

Слой Обычное назначение
Слой 0 Полное тело локомоции, такие как стоять, ходить, бегать, прыгать, падать.
Слой 1 Действия верхней части тела, такие как атака, прицеливание, перезарядка, держать оружие.

Это позволяет персонажу продолжать двигаться, пока другой слой добавляет действие сверху.

Вес слоев

Каждый слой имеет вес, который управляет количеством влияния этого слоя.

Вы можете изменить его из Python:

self.animator.setLayerWeight(1, 1.0)

Вы также можете его считывать:

weight = self.animator.getLayerWeight(1)

Вес 1.0 означает, что слой полностью активен. Вес 0.0 означает, что он не оказывает видимого влияния.

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

Стоит отметить, что параметр смешивания работает только между анимациями на одном и том же слое. И мы не будем смешивать анимации, воспроизводимые на разных слоях. Так что если у вас есть анимация, воспроизводимая на слое 0, а затем внезапно запускается анимация на слое 1, независимо от значения смешивания, она не будет смешиваться между ними. Это будет смешиваться только если уже была выполнена другая анимация на слое 1. Если вы хотите смешивать между слоями, вам нужно разработать индивидуальную логику, чтобы использовать вес слоя для этого.

Фильтры костей

Фильтры костей контролируют, какие кости затрагиваются слоем и насколько сильно каждая кость влияет.

Это позволяет выполнять анимацию верхней части тела.

Например, вы можете захотеть:

  • Слой 0 контролировать всё тело.
  • Слой 1 контролировать только позвоночник, руки, кисти и кости оружия.

В Python вы можете создать фильтр для слоя и назначить влияние на кость и её потомков:

def setupUpperBodyLayer(self):
    armature = self.animator.armature.get()
    spine = armature.getBone("mixamorig:Spine")

    upperBody = self.animator.createLayerFilter(1)
    upperBody.defaultBlend = 0.0
    upperBody.setToBone(spine, 1.0, recursive=True)

В этом примере:

  • defaultBlend = 0.0 означает, что слой по умолчанию не затрагивает никакие кости.
  • setToBone(spine, 1.0, recursive=True) означает, что позвоночник и его потомки затрагиваются полностью.
  • Слой 1 теперь можно использовать для анимаций верхней части тела.

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

Практический пример слоя

Представьте персонажа от третьего лица, который может ходить и атаковать одновременно.

Одно из возможных настроек:

Слой Что он обрабатывает
Слой 0 Стоять, ходить, бегать, падать.
Слой 1 Атака верхней части тела.

Код может выглядеть так:

import cave

class CombatAnimator(cave.Component):
    def start(self, scene: cave.Scene):
        self.mesh = self.entity.getChild("Mesh")
        self.animator : cave.AnimationComponent = self.mesh.get("Animation") if self.mesh else None

        if self.animator:
            armature = self.animator.armature.get()
            spine = armature.getBone("mixamorig:Spine")

            upperBody = self.animator.createLayerFilter(1)
            upperBody.defaultBlend = 0.0
            upperBody.setToBone(spine, 1.0, recursive=True)
            self.animator.setLayerWeight(1, 1.0)

    def playWalk(self):
        self.animator.playByName("p-walk", blend=0.2, layer=0, loop=True)

    def playAttack(self):
        self.animator.playByName("Attack", blend=0.1, layer=1, loop=False)

Этот пример предполагает, что у вас есть анимационный ресурс с названием Attack. Если ваш проект использует другое имя анимации, используйте это имя вместо.

Важная идея состоит в том, что локомоция остаётся на слое 0, в то время как атака воспроизводится на слое 1 и затрагивает только отфильтрованные кости.

Обработчики анимаций

Анимации могут выполнять код Python в определенное время.

Cave поддерживает обратные вызовы, такие как:

  • В начале анимации.
  • В конце анимации.
  • На определенном кадре анимации.

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

Примеры:

  • Воспроизвести звук шага.
  • Создать пыль при шаге.
  • Нанести урон на кадре атаки.
  • Начать след оружия.
  • Запустить эффект частиц.
  • Уведомить логику о завершении анимации.

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

Пример обратного вызова шага

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

Идея проста:

  1. Анимация знает, когда нога касается земли.
  2. Обратный вызов размещается на этом кадре.
  3. Обратный вызов воспроизводит звук или создает небольшой эффект.

Внутри обратного вызова анимации Cave предоставляет полезные переменные, такие как:

  • entity, владеющая сущность.
  • animator, компонент анимации.
  • handle, слой/обработчик анимации, выполняемого.

Очень небольшой обратный вызов шага может выглядеть так:

cave.playSound("Footstep Grass", volume=0.5)

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

Переиспользуемые обратные вызовы анимации

Обратные вызовы относятся к анимационному ресурсу.

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

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

Анимация хранит время. Сущность предоставляет контекст.

Расширенные обратные вызовы позы

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

Это расширенная функция, но она может использоваться для:

  • Обратной кинематики.
  • Нацеливания головы.
  • Нацеливания оружия.
  • Установки ног.
  • Финальных корректировок позы.

Стартовый Player Toolkit включает пример IK ног, который использует эту концепцию. Он получает дочерний элемент Mesh, получает компонент Animation, а затем регистрирует метод с:

self.animator.addPostEvaluationCallback(self.postEvaluation)

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

Компонент сокета анимации

Компонент Animation Socket позволяет дочерней сущности следовать за костью родительской анимированной сущности.

Дочерняя сущность должна находиться под родителем, у которого есть компонент анимации. Затем сокет может выбрать кость и копировать положение, вращение и, при необходимости, масштаб с этой кости, с учётом смещений.

  • Меч в руке.
  • Пистолет в руке.
  • Щит на руке.
  • Шлем на голове.
  • Рюкзак на позвоночнике.
  • Реквизит прикреплён к руке.

Например, если персонаж держит меч, меч может быть дочерним объектом с компонентом Animation Socket, который следует за костью руки. Когда персонаж атакует, бегает или стоит на месте, меч остаётся прикреплённым к правильной анимированной позиции.

Что стоит помнить

  • Стартовый игрок — это шаблон с корневым объектом Player и дочерним объектом Mesh.
  • Корневой объект Player обычно управляет физикой, движением, свойствами и игровой логикой.
  • Дочерний объект Mesh обычно отвечает за видимую модель, материал, скелет и компонент анимации.
  • Это разделение даёт физическому телу и визуальному персонажу отдельные трансформации.
  • Компонент анимации воспроизводит скелетные анимации с использованием скелета и анимационных ассетов.
  • В Python обычно называют компонент анимации animator.
  • playByName воспроизводит анимационный ассет по имени и поддерживает смешивание, повторение и слои.
  • Слои позволяют множественным анимациям накладываться друг на друга.
  • Фильтры костей позволяют слою воздействовать только на часть скелета.
  • Обратные вызовы анимации связывают тайминг анимации с игровыми событиями.
  • Анимационные сокеты прикрепляют реквизит к анимированным костям.

Когда вы исследуете стартовый шаблон Player или Enemy, используйте эту структуру как вашу схему: корневой объект для игрового процесса, дочерний объект Mesh для визуализации и анимации.