Моб редко проигрывает всего одну анимацию. Наш рубиновый голем должен стоять и покачиваться, ходить, когда двигается, и поднимать кулаки, когда замечает врага. Контроллер анимаций решает, какая из них играет прямо сейчас.
Этот гайд продолжает Анимации сущностей. Если ты ещё не делал анимацию, сначала прочитай его.
Файлы
RP/
animations/
ruby_golem.animation.json
animation_controllers/
ruby_golem.animation_controllers.json
entity/
ruby_golem.entity.jsonПредставь его как набор комнат
Контроллер - это набор состояний. Представь каждое состояние как комнату:
- Моб всегда находится ровно в одной комнате.
- В каждой комнате есть список анимаций, которые играют, пока моб внутри.
- В каждой комнате есть двери (переходы). На двери висит проверка на Molang. Когда проверка истинна, моб проходит в другую комнату.
Для начала у нашего голема две комнаты: idle и walking.
Шаг 1: покой и ходьба
Файлы контроллеров лежат в папке animation_controllers пакета ресурсов:
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.ruby_golem.move": {
"initial_state": "idle",
"states": {
"idle": {
"animations": ["idle"],
"transitions": [
{ "walking": "query.modified_move_speed > 0.1" }
],
"blend_transition": 0.2
},
"walking": {
"animations": ["walk"],
"transitions": [
{ "idle": "query.modified_move_speed < 0.05" }
],
"blend_transition": 0.2
}
}
}
}
}Читается это так:
- Голем начинает в
idleи проигрывает анимациюidle. - Как только его скорость превышает 0.1, он переходит в
walkingи вместо этого играетwalk. - Когда он замедляется ниже 0.05, он возвращается в
idle. - Каждое переключение плавно длится 0.2 секунды, поэтому ноги не дёргаются.
Названия внутри animations - это короткие имена из клиентской сущности, а не длинные имена с animation.. Подробнее об этом в шаге 2.
Два разных числа - это специально
Вход (> 0.1) и выход (< 0.05) используют разные числа. Если бы они были одинаковыми, голем, который движется ровно с этой скоростью, мог бы прыгать между комнатами каждый кадр и выглядеть дёргано.
Поля контроллера
| Поле | Что делает |
|---|---|
initial_state | Состояние, в котором моб начинает. Если его не указать, игра ищет состояние с названием default. |
states | Все состояния по названиям. |
Названия контроллеров начинаются с controller.animation.. Удобная схема: controller.animation.<моб>.<задача>, например controller.animation.ruby_golem.move.
Внутри состояния
| Поле | Что делает |
|---|---|
animations | Что играет в этом состоянии. Короткие имена или короткие имена со смешиванием. |
transitions | Двери из этого состояния, проверяются по порядку. |
blend_transition | Сколько секунд длится плавный переход из этого состояния в следующее. |
blend_via_shortest_path | true заставляет кости при переходе поворачиваться коротким путём. |
on_entry | Что выполнить один раз, когда моб входит в состояние. |
on_exit | Что выполнить один раз, когда моб выходит из состояния. |
variables | Значения, которые вычисляются для этого состояния, смотри ниже. |
particle_effects | Частицы, которые показываются в этом состоянии. |
sound_effects | Звуки, которые играют при входе в это состояние. |
Частицы и звуки описаны в гайде События анимаций.
Переходы
transitions - это список. В каждой записи один ключ (состояние, куда идти) и одна проверка на Molang:
"transitions": [
{ "alert": "query.has_target" },
{ "swimming": "query.is_in_water" },
{ "idle": "query.modified_move_speed < 0.05" }
]- Проверки идут сверху вниз. Побеждает первая истинная.
- За один кадр происходит только один переход.
- Если ни одна не истинна, моб остаётся на месте.
Поэтому самую важную дверь ставь первой. Здесь заметить врага важнее всего остального.
Смешивание внутри состояния
Состояние может проигрывать несколько анимаций сразу. У каждой может быть значение смешивания, как в scripts.animate:
"walking": {
"animations": [
"idle",
{ "walk": "query.modified_move_speed" }
]
}Медленный голем показывает немного ходьбы поверх покачивания. Быстрый - ходьбу целиком.
Шаг 2: подключаем к мобу
Контроллер указывается в карте animations клиентской сущности так же, как анимация. Потом ты запускаешь его из scripts.animate:
"animations": {
"idle": "animation.ruby_golem.idle",
"walk": "animation.ruby_golem.walk",
"fists_up": "animation.ruby_golem.fists_up",
"look_at_target": "animation.common.look_at_target",
"move_controller": "controller.animation.ruby_golem.move"
},
"scripts": {
"animate": [
"look_at_target",
"move_controller"
]
}Заметь, что idle и walk в animate больше нет. Теперь их запускает контроллер. Если их оставить, они будут играть дважды.
Можно запускать несколько контроллеров одновременно. У каждого своё текущее состояние. Частая схема: один контроллер для ног и тела, другой для рук.
Шаг 3: состояние тревоги
Пусть голем поднимает кулаки, пока у него есть цель. Добавь третье состояние и дверь в него из обоих других:
"states": {
"idle": {
"animations": ["idle"],
"transitions": [
{ "alert": "query.has_target" },
{ "walking": "query.modified_move_speed > 0.1" }
],
"blend_transition": 0.2
},
"walking": {
"animations": ["walk"],
"transitions": [
{ "alert": "query.has_target" },
{ "idle": "query.modified_move_speed < 0.05" }
],
"blend_transition": 0.2
},
"alert": {
"animations": [
"fists_up",
{ "walk": "query.modified_move_speed" }
],
"transitions": [
{ "idle": "!query.has_target" }
],
"blend_transition": 0.3
}
}В alert голем держит кулаки поднятыми и всё равно идёт, когда гонится за тобой.
Ждём окончания анимации
Некоторые анимации должны проиграть один раз и до конца, например рёв. Для этого есть два запроса:
| Запрос | Истинен, когда |
|---|---|
query.all_animations_finished | Все анимации в состоянии дошли до конца |
query.any_animation_finished | Хотя бы одна из них дошла до конца |
"roar": {
"animations": ["roar"],
"transitions": [
{ "alert": "query.all_animations_finished" }
]
}Это работает только для анимаций, которые не зациклены. Зацикленная анимация никогда не заканчивается.
on_entry и on_exit
on_entry выполняется один раз, когда моб входит в состояние, а on_exit - один раз, когда выходит. В пакете ресурсов они выполняют Molang, и это удобно, чтобы задавать переменные:
"alert": {
"on_entry": ["variable.times_alerted = (variable.times_alerted ?? 0) + 1;"],
"on_exit": ["variable.calm_since = query.life_time;"],
"animations": ["fists_up"],
"transitions": [{ "idle": "!query.has_target" }]
}В пакете поведения эти же списки могут выполнять ещё и команды и события сущности. Смотри События анимаций.
Переменные состояния
Состояние может вычислять собственные значения через variables. Самое полезное здесь - remap_curve: он переводит один диапазон чисел в другой.
"walking": {
"variables": {
"swing": {
"input": "query.modified_move_speed",
"remap_curve": {
"0.0": 0.3,
"1.0": 1.0
}
}
},
"animations": [{ "walk": "variable.swing" }]
}Когда скорость 0, variable.swing равна 0.3. Когда 1, она равна 1.0. Между ними игра проводит прямую линию. Так что даже медленный голем немного показывает ходьбу.
Если что-то не работает
| Что ты видишь | Скорее всего, причина |
|---|---|
| Ничего не играет | Контроллер есть в animations, но не в scripts.animate |
| Анимация играет в два раза сильнее | Она есть и в scripts.animate, и в состоянии контроллера |
| Моб застревает в состоянии | Ни один переход из него никогда не становится истинным. Проверь Molang на его дверях |
| Одноразовая анимация никогда не заканчивается | У неё стоит "loop": true, поэтому all_animations_finished никогда не истинен |
| Моб мигает между двумя состояниями | Проверки входа и выхода пересекаются. Используй два разных числа |
| Журнал контента пишет, что состояния нет | Переход указывает на название состояния с опечаткой |
Больше помощи в гайде Когда что-то сломалось.
Что дальше
- Выбирай текстуры и скрывай части модели с помощью контроллеров рендера.
- Запускай звуки, частицы и команды через события анимаций.
- Узнай больше приёмов Molang в гайде по Molang.