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

Анимации 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_version 5 и новее меняет знак вращения по 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.

Как это устроено:

  1. Пустой объект с ik_source (корневая кость) и ik_target (конечная кость или локатор) определяет цепочку. Поиск проверяет иерархию, а также соседние кости и потомков; соседние цели на корневом уровне дают цепочку только из исходной кости.
  2. IK управляют только ключевые кадры позиции пустого объекта. Их покадровое смещение прибавляется к позиции покоя целевой кости или локатора. Решатель не использует сохранённую позицию покоя контроллера как основу цели. Дорожки вращения и масштаба не управляют IK.
  3. Каждый тик связанные с текущей анимацией цепочки IK получают смещения и рассчитываются. Связанная цепочка без данных для кадра очищается. Каждая смена анимации, а также stopCurrentAnimations(), очищает вращения IK всех цепочек, поэтому поза IK не переносится в следующую анимацию.
  4. 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-скрипта или собственного плагина. Ключевые кадры частиц импортируются; если частицы не появляются, см. Частицы.