Анимации FreeMinecraftModels
FreeMinecraftModels импортирует анимации из файлов .bbmodel и .fmmodel. Эта страница описывает зарезервированные имена, время кадров, интерполяцию, режимы зацикливания и обратную кинематику (IK).
Правила именования костей и остальную часть контракта импорта см. в разделе Заметки по созданию моделей.
Пять анимаций состояний
FreeMinecraftModels привязывает ровно пять имён анимаций в нижнем регистре к автоматическим состояниям времени выполнения. Всё остальное в модели — это пользовательские анимации.
| Имя анимации | Зацикливается | Когда проигрывается |
|---|---|---|
spawn | нет | Один раз, при создании модели. По завершении переходит в idle |
idle | да | При отсутствии горизонтального движения; переходит в walk, если скорость по X или Z ненулевая |
walk | да | При горизонтальном движении; переходит в idle на земле, если скорости по X и Z равны нулю |
attack | нет | При срабатывании; по завершении возвращается в idle |
death | нет | При вызове removeWithDeathAnimation() |
Правила, вытекающие из устройства конечного автомата состояний:
- Начальное состояние —
spawn, если оно есть у модели, иначеidle. У модели, где нет ни того, ни другого, нет текущего состояния, поэтому ничего не анимируется, пока что-нибудь не будет запущено явно. - Состояние получают только те анимации, которые есть в модели. Модель с
walk, но безidleникогда не выйдет изwalkсамостоятельно. - Переключение idle/walk считывает горизонтальную скорость базовой сущности; одного вертикального движения недостаточно для ходьбы. У статических сущностей и маскировок нет базовой сущности для этой проверки, а неподвижный реквизит не движется по горизонтали. Дополнительными анимациями управляют скрипты, API или контроллер маскировок FMM.
jump здесь существует только в перечисленииJUMP присутствует в перечислении AnimationStateType, и состояние walk действительно запрашивает переход в прыжок, когда сущность отрывается от земли, — но состояние прыжка никогда не регистрируется, поэтому этот запрос ни к чему не приводит. Анимация с именем jump не мертва: она просто ведёт себя как любая другая пользовательская анимация и должна запускаться вручную. Исключение — маскировки игроков: у них отдельный контроллер, где jump подключён.
У маскировок игроков другой набор
Маскировка добавляет контроллер, который каждый тик запрашивает анимации у того же механизма воспроизведения. Его пять зарезервированных имён — attack, jump, sneak, walk, idle; они проверяются в этом порядке, пока не активен обратный отсчёт одноразовой анимации. Если у модели нет idle, контроллер предупреждает в консоли. Полную таблицу и ограничение длительности см. в разделе Маскировки игроков.
Пользовательские анимации
Любую анимацию, чьё имя не входит в пятёрку выше, всё равно можно проиграть по имени:
modeledEntity.playAnimation("open", /* blend */ true, /* loop */ false);
modeledEntity.stopCurrentAnimations();
boolean exists = modeledEntity.hasAnimation("open");
context.prop:play_animation("open", true, false)
context.prop:stop_animation()
blendне делает плавного перехода.trueставит анимацию в очередь, чтобы она началась после завершения тика текущего состояния;falseпрерывает и переключает немедленно.- В очереди только одно место: следующий отложенный запрос заменяет предыдущий. Пользовательская анимация запускается сразу, если текущего состояния нет. Встроенное состояние в очереди не может продвинуться при пустом текущем состоянии; в этом случае используйте
blend=false. loopприменяется только к пользовательским анимациям. Встроенное состояние использует собственную настройку зацикливания независимо от того, что вы передали.- Используйте точные имена пользовательских анимаций и в
hasAnimation: оба поиска учитывают регистр. Запросы воспроизведения встроенных состояний допускают другой регистр, но их регистрация требует имени в нижнем регистре внутри модели. - Когда незацикленная пользовательская анимация завершается, она запрашивает сохранённое последнее зафиксированное встроенное состояние, либо
idle, если записи нет. Это последнее встроенное состояние, из которого менеджер ранее вышел; оно может отличаться от активного состояния непосредственно перед пользовательской анимацией. Если запрошенного состояния возврата нет, пользовательское состояние остаётся выбранным без дальнейшего обновления кадров. playAnimationвозвращаетfalseдля неизвестного имени, кроме подавленных запросов атаки, описанных ниже. Успешный результат означает принятие запроса, а не подтверждает показ видимого кадра.stopCurrentAnimations()переходит вidle, если он есть у модели; иначе выходит из текущего состояния и оставляет модель без активной анимации.- Пока выполняется пользовательская анимация, запрос
attack,attack_meleeилиattack_rangedпоглощается, чтобы скриптовая последовательность не прерывалась обычным боем. - Смерть является конечным состоянием общей машины состояний: новые запросы возвращают
false, очередь переходов очищается, а остановка анимаций не меняет это состояние. Публичный метод остановки также отправляет отдельный запрос остановки Bedrock, поэтому не используйте его для управления анимацией смерти сразу на обоих клиентах.
Тайминг и продолжительность
- Blockbench хранит длину анимации в секундах. FMM преобразует её как
ceil(seconds x 20), поэтому продолжительность всегда целое число тиков, и короткие анимации округляются вверх, а не вниз. - Каждая анимация запекается при импорте в плоский массив кадров по тикам. Воспроизведение — это поиск в массиве на каждый тик, а не интерполяция в реальном времени.
- Зацикленные анимации индексируются как
counter % duration; незацикленные фиксируются на последнем кадре и затем перестают что-либо менять. - Времена ключевых кадров сохраняют дробную позицию в тиках (
20 x time, без округления), поэтому кадр на 0.37 с участвует в интерполяции между тиками. Кадры вычисляются на целых тиках от0доduration - 1. Ключевой кадр точно на объявленной границе окончания влияет на интерполяцию, но сам не попадает в выборку. Разместите конечную позу до этой границы, если отображаемый кадр должен её достигнуть. - Если два ключевых кадра на одном канале попадают ровно в одно и то же время, побеждает тот, который идёт позже в файле.
- Ключевой кадр с нечисловым (не конечным) временем прерывает свою дорожку и порождает одно предупреждение
Malformed animation timeline for model ...на анимацию, при этом остальные анимации модели продолжают конвертироваться.
Анимации нулевой длины допустимы
Анимация длиной 0 (или отрицательной) считается намеренной статической позой — это распространённый приём при создании мебели и других пропсов, которым нужна именованная запись «без анимации». Она молча пропускается без предупреждения и не добавляет кадров.
Режимы зацикливания
Настройка зацикливания Blockbench управляет экспортированной анимацией Bedrock:
| Режим зацикливания Blockbench | Экспорт для Bedrock |
|---|---|
loop | "loop": true |
once | "loop": false |
hold | "loop": "hold_on_last_frame" |
В Java используется правило зацикливания встроенного состояния либо аргумент loop пользовательской анимации, а не это поле Blockbench. Поэтому воспроизведение в Java и Bedrock может отличаться, если настройки не совпадают.
Типы интерполяции
Каждый ключевой кадр несёт собственный тип интерполяции, и отрезок, ведущий в ключевой кадр, использует тип этого кадра. Поддерживаются четыре:
| Тип Blockbench | Поведение в FMM |
|---|---|
linear | Прямая линейная интерполяция |
catmullrom | Сглаженная интерполяция (плавный вход/выход) |
bezier | Аппроксимируется фиксированными контрольными точками 0.42 / 0.58 — FMM не читает манипуляторы Безье отдельных ключевых кадров |
step | Держит предыдущее значение до следующего ключевого кадра |
Всё, что вне этого набора, не разбирается и сообщается как некорректная временная шкала.
Анимируемые каналы
Для каждой кости запекаются три канала: вращение, позиция и масштаб. Что важно при создании модели:
- Значения позиции делятся на 16 (пиксели Blockbench в блоки).
- Значения вращения преобразуются в радианы.
- Blockbench
format_version5 и новее меняет знак вращения по X и Y и позиции по X. FMM компенсирует это автоматически на основе объявленной версии формата, поэтому не исправляйте это вручную — но и не смешивайте объявление v5 с данными в форме v4. - Кость без ключевых кадров на канале сохраняет для этого канала своё значение покоя; кость, у которой на данный тик нет кадров вообще, сбрасывается к вращению
0,0,0, смещению0,0,0и масштабу1,1,1. - Точки данных ключевых кадров могут быть записаны в
.bbmodelв виде строк. FMM разбирает их как обычные числа — пустая строка становится1для масштаба и0в остальных случаях, а всё, что не разбирается, пишет в логFailed to parse supposed number value ...и становится0. Выражения Molang не вычисляются. - Читается только первая точка данных ключевого кадра, поэтому раздельные значения «до/после» Blockbench на step-кадре схлопываются в одно.
Что не анимируется
- Кость
hitbox. Дорожки анимации, нацеленные на неё, пропускаются полностью. - Ключевые кадры звука и инструкций временной шкалы в дорожках эффектов Blockbench. Из дорожки эффектов FMM импортирует только ключевые кадры частиц; см. Частицы. Воспроизводите звуки из Lua-скрипта или из собственного плагина.
- Кости, которые не удалось найти по имени. Дорожка, указывающая на отсутствующую кость, пишет в лог
Failed to get bone <name> from model <model>!и пропускается.
Обратная кинематика (IK)
Пустые объекты (null objects) Blockbench выступают контроллерами IK. FMM решает цепочки во время выполнения методом FABRIK (Forward And Backward Reaching Inverse Kinematics) с ограничением в 10 итераций и допуском 0.001.
Как это устроено:
- Пустой объект с
ik_source(корневая кость) иik_target(конечная кость или локатор) определяет цепочку. Поиск проверяет иерархию, а также соседние кости и потомков; соседние цели на корневом уровне дают цепочку только из исходной кости. - IK управляют только ключевые кадры позиции пустого объекта. Их покадровое смещение прибавляется к позиции покоя целевой кости или локатора. Решатель не использует сохранённую позицию покоя контроллера как основу цели. Дорожки вращения и масштаба не управляют IK.
- Каждый тик связанные с текущей анимацией цепочки IK получают смещения и рассчитываются. Связанная цепочка без данных для кадра очищается. Каждая смена анимации, а также
stopCurrentAnimations(), очищает вращения IK всех цепочек, поэтому поза IK не переносится в следующую анимацию. lock_ik_target_rotationсчитывается и сохраняется, но текущий решатель его не применяет.
Цепочки, которые не удаётся разрешить, пропускаются с именованным предупреждением в консоли — точные сообщения и ограничения при создании моделей см. в разделе Заметки по созданию моделей.
Экспорт для Bedrock
Каждая сконвертированная модель также записывает файл анимации Bedrock по пути animations/<model_id>.animation.json внутри сгенерированного бандла:
- Идентификаторы анимаций имеют вид
animation.fmm.<model_id>.a_<hex>, где<hex>— байты имени анимации в UTF-8, записанные в шестнадцатеричном виде. Поэтому два имени, различающиеся только недопустимыми для Bedrock символами, получают разные идентификаторы. animation_length— это длительность в секундах, но не меньше0.05, чтобы анимация длиной в один тик оставалась корректной.- Режим зацикливания отображается так, как показано в таблице Режимы зацикливания.
- Модель без анимаций всё равно получает одну пустую запись
idle, чтобы определение сущности Bedrock оставалось корректным. - Геометрия исключает
hitbox, созданные кости именных табличекfmm_nametag_bone_*и точки посадкиm_; авторские якоряtag_остаются. Экспортёр анимаций записывает запечённые дорожки без такого же фильтра визуальных костей. Не анимируйте исключённые кости посадки в ожидании видимой геометрии. - Экспорт анимаций Bedrock использует запечённые кадры вращения, позиции и масштаба костей. Он не переносит в эти дорожки вращения, вычисляемые решателем IK во время работы; модели, зависящие от IK, проверяйте отдельно в Bedrock.
- На каждую анимацию генерируется один контроллер анимации, переключаемый свойством сущности, — именно так FMM проигрывает конкретную анимацию на клиенте Bedrock.
- Ключевые кадры частиц становятся временной шкалой
particle_effectsанимации, поэтому клиенты Bedrock воспроизводят их нативно. См. Частицы.
О том, куда бандл попадает на диске, см. Вывод ресурс-пака.
Запуск анимаций из других систем
| Вызывающая сторона | Точка входа |
|---|---|
| Плагин (Java) | ModeledEntity#playAnimation(String, boolean blend, boolean loop) / #stopCurrentAnimations() / #hasAnimation(String) |
| Lua-скрипт пропса | context.prop:play_animation(name, blend, loop) / context.prop:stop_animation() |
| Любая Lua-таблица сущности | entity.model:play_animation(name, blend, loop) / entity.model:stop_animations() (доступно, когда entity.is_modeled равно true) |
Связанные интерфейсы описаны в руководстве по API для разработчиков и Lua: API реквизита.
Значения Lua по умолчанию различаются: context.prop:play_animation(name) использует blend=true, loop=true, а entity.model:play_animation(name) — false, false. Передавайте оба логических аргумента явно, когда эта разница важна.
Устранение неполадок
Моя анимация никогда не запускается автоматически.
Общая машина состояний распознаёт имена spawn, idle, walk, attack и death в нижнем регистре. Движение управляет переходами idle/walk; атака и смерть всё ещё требуют соответствующего запуска во время работы. Остальные имена требуют явного вызова playAnimation / play_animation, кроме дополнительных имён, выбираемых контроллером маскировок.
Моя модель вообще ничего не делает.
Скорее всего, у неё нет ни анимации spawn, ни idle, поэтому при создании не входит ни в одно состояние. Добавьте idle.
Анимация проигрывается, но ничего не движется. Проверьте длину анимации. Анимация нулевой длины считается статической позой и по замыслу молча пропускается.
Вращения зеркальны.
Проверьте meta.format_version в .bbmodel. FMM меняет знак вращения по X/Y для версии формата 5 и новее; файл, который объявляет одну версию, но содержит данные другой, окажется зеркальным.
В консоли Malformed animation timeline for model ....
Одну дорожку в этой анимации не удалось прочитать или интерполировать. Предупреждение выводится один раз на анимацию и называет задействованную кость или контроллер IK; остальные анимации модели всё равно конвертируются.
Звуки на моей временной шкале Blockbench ничего не делают. Ключевые кадры звука не импортируются. Запускайте звуки из Lua-скрипта или собственного плагина. Ключевые кадры частиц импортируются; если частицы не появляются, см. Частицы.