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-модель, которая ходит по уровню. В реальной игровой системе он обычно объединяет физику, логику перемещения, видимую меш-модель, армирование, анимации, а также код или Logic Bricks, которые определяют, какая анимация должна воспроизводиться.

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

image.png

В этом уроке вы узнаете, как устроена эта система и как управлять анимациями через Python.

Вы изучите:

  • Как построен стартовый шаблон Player.
  • Почему видимый меш — дочерняя сущность, а не находится в корне игрока.
  • Что делает Animation Component.
  • Как получить аниматор из Python (аналогичная логика применяется и для Logic Bricks).
  • Как воспроизводить анимации по имени.
  • Как работают смешивание, слои и фильтры костей.
  • Как анимационные колбэки и сокеты интегрируются в систему.

Структура шаблона Player

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

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

image.png

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

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

Корневая сущность Player

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

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

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

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

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

  • Transform Component.
  • Один или несколько Mesh Components.
  • Animation Component.

Mesh Component отвечает за видимую 3D-модель персонажа и материал. Если персонаж использует несколько материалов, Cave может представить это через несколько Mesh Components, следуя правилу мультиматериала, описанному в уроке по импорту.

Animation Component отвечает за вычисление армирования и проигрывание анимаций.

Проще говоря:

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

Такое разделение сделано намеренно.

Почему Mesh — дочерняя сущность

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

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

Трансформ корневой сущности Player отражает тело игрового персонажа. Это объект, который движется в мире, сталкивается со стенами и содержит Character Component. Трансформ дочерней сущности Mesh отвечает за визуальное отображение персонажа.

Такое разделение полезно в разных ситуациях:

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

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

Оба варианта корректны.

Они требуют отдельного трансформа:

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

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

Что делает Animation Component

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

Он требует:

  • Арматуру (Armature).
  • Анимацию по умолчанию.
  • Валидный Mesh Component на той же сущности (или нескольких).

Арматура определяет скелет. Анимация задает движение скелета с течением времени. Animation Component вычисляет итоговую позу и применяет ее к видимому персонажу.

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

  • Proto Mesh
  • Proto Mat
  • Proto Armature
  • p-idle как анимацию по умолчанию (анимация стояния)

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

Арматуры, анимации и ретаргетинг

  • Armature — скелет персонажа, содержащий кости, которым следует меш.
  • Animation хранит движение костей во времени для арматуры.

Animation Component объединяет эти части:

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

Cave также поддерживает ретаргетинг анимаций, когда это возможно. Если анимация принадлежит совместимой арматуре, Animation Component может применить движение к текущей арматуре.

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


Получение аниматора из 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") получает Animation Component.
  • Переменная называется 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)

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

Простой пример локомоции

Типичная настройка анимаций игрока:

  • Проигрывать idle, когда персонаж стоит.
  • Проигрывать walk, когда персонаж движется.
  • Проигрывать run, когда персонаж бежит.
  • Проигрывать fall, когда персонаж в воздухе.

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

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)

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

Вращение Меша в Направлении Движения

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

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

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

Главная идея не в точной математике. Главное — что игровое тело и визуальный меш можно контролировать раздельно.

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

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

Это одна из основных причин, почему у стартового персонажа есть дочерняя сущность Mesh.

Плавное Переключение Анимаций (Blending)

Плавное переключение анимаций делает переходы более гладкими.

Без blending переключение с одной анимации на другую происходит мгновенно. С blending 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 секунды.

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

Хорошие начальные значения blending обычно небольшие:

  • 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 Полная локомоция, например idle, walk, run, jump, fall.
Слой 1 Действия верхней части тела: атака, прицеливание, перезарядка, держание оружия.

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

Веса Слоёв

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

Изменить вес можно из Python:

self.animator.setLayerWeight(1, 1.0)

Также можно прочитать:

weight = self.animator.getLayerWeight(1)

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

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

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

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

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

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

Например:

  • Слой 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 Idle, ходьба, бег, падение.
Слой 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, который затрагивает только отфильтрованные кости.

Callback-анимации

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

Cave поддерживает колбэки:

  • При старте анимации.
  • При её окончании.
  • На определённом кадре анимации.

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

Примеры:

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

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

Пример колбэка шага

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

Идея проста:

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

Внутри callback Cave предоставляет полезные переменные:

  • entity — сущность-владелец.
  • animator — Animation Component.
  • handle — слой/хендлер текущей анимации.

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

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

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

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

Колбэки принадлежат анимационному ассету.

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

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

Анимация хранит тайминг. Сущность даёт контекст.

Расширенные Pose-callbacks

Cave также поддерживает post-evaluation callbacks через Python. Они происходят после того, как Animation Component вычисляет позу арматуры.

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

  • Инверсной кинематики.
  • Наведения головы.
  • Наведения оружия.
  • Расстановки стоп.
  • Финальной корректировки позы.

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

self.animator.addPostEvaluationCallback(self.postEvaluation)

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

Animation Socket Component

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

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

Сокеты полезны для прикрепления объектов к анимированным персонажам:

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

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

Что следует помнить

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

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