Animações do FreeMinecraftModels
Esta página documenta o que o FreeMinecraftModels realmente faz com os dados de animação dentro de um arquivo .bbmodel ou .fmmodel: quais nomes são especiais, como os keyframes são pré-processados, quais modos de interpolação e de repetição são respeitados e como a IK é controlada. Ela é deliberadamente conservadora — tudo aqui é visível no pipeline de importação e de execução.
Para as regras de nomenclatura de ossos e o restante do contrato de importação, veja Notas de Autoria de Modelos.
As Cinco Animações de Estado
O FreeMinecraftModels vincula exatamente cinco nomes de animação em minúsculas a estados automáticos de execução. Todo o resto no modelo é uma animação personalizada.
| Nome da animação | Repete | Quando toca |
|---|---|---|
spawn | não | Uma vez, quando o modelo é criado. Cai para idle ao terminar |
idle | sim | Enquanto a velocidade da entidade subjacente estiver em 0.08 ou abaixo |
walk | sim | Enquanto a velocidade da entidade subjacente estiver acima de 0.08 |
attack | não | Quando acionada; volta para idle ao terminar |
death | não | Em removeWithDeathAnimation() |
Regras que decorrem de como a máquina de estados é construída:
- O estado inicial é
spawnse o modelo tiver um, caso contrárioidle. Um modelo sem nenhum dos dois não tem estado atual, então nada anima até que algo seja tocado explicitamente. - Apenas animações que existem no modelo recebem um estado. Um modelo com
walkmas semidlenunca sai dewalkpor conta própria. - A alternância idle/walk lê a velocidade da entidade subjacente, então na prática é um recurso de
DynamicEntity. Entidades estáticas e disfarces de jogador não têm entidade subjacente para esse fim e simplesmente permanecem emidle; o armor stand que sustenta um prop não se move, então props também ficam emidle. Os três acionam suas animações reais por scripts, pela API ou (no caso dos disfarces) pelo próprio controlador de disfarce do FMM.
jump existe apenas no enum aquiJUMP existe no enum AnimationStateType, e o estado walk de fato solicita uma transição de jump quando a entidade sai do chão — mas nenhum estado de jump chega a ser registrado, então essa solicitação não resolve para nada. Uma animação chamada jump não é inútil: ela apenas se comporta como qualquer outra animação personalizada e precisa ser acionada manualmente. Disfarces de jogador são a exceção — eles usam um controlador separado no qual jump está implementado.
Disfarces de Jogador Usam Um Conjunto Diferente
Um disfarce de jogador não executa a máquina de estados acima. Ele tem seu próprio controlador por tick com cinco nomes reservados — attack, jump, sneak, walk, idle — avaliados nessa ordem de prioridade, e avisa no console se o modelo não tiver um idle. Veja Disfarces de Jogador para a tabela completa e a ressalva de tempo das animações de disparo único.
Animações Personalizadas
Qualquer animação cujo nome não seja um dos cinco acima ainda pode ser tocada pelo nome:
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()
blendnão faz cross-fade.trueenfileira a animação para começar depois que o tick do estado atual terminar;falseinterrompe e troca imediatamente.loopse aplica apenas a animações personalizadas. Um estado embutido usa sua própria configuração de repetição, independentemente do que você passar.- Quando uma animação personalizada sem repetição termina, a entidade volta ao último estado embutido efetivado (o que quer que estivesse fazendo antes), ou para
idlese não houver nenhum. playAnimationretornafalsequando o nome não corresponde nem a um estado registrado nem a uma animação do modelo.stopCurrentAnimations()faz a transição paraidlequando o modelo tem um; caso contrário, sai do estado atual e deixa o modelo sem nenhuma animação ativa.- Enquanto uma animação personalizada está em execução, um pedido de
attack,attack_meleeouattack_rangedé descartado para que uma sequência com script não seja interrompida por combate rotineiro.
Tempo e Duração
- O Blockbench guarda a duração da animação em segundos. O FMM a converte com
ceil(segundos x 20), então a duração é sempre um número inteiro de ticks e animações curtas são arredondadas para cima, não para baixo. - Toda animação é pré-processada em um array plano de frames por tick no momento da importação. A reprodução é uma consulta ao array por tick, não interpolação em tempo real.
- Animações em repetição indexam com
counter % duration; as que não repetem travam no último frame e param de renderizar mudanças a partir daí. - Os tempos dos keyframes mantêm sua posição fracionária de tick (
20 x tempo, sem arredondar), então um keyframe em 0,37 s cai entre ticks e é interpolado corretamente, em vez de ser encaixado ou descartado. O keyframe final de uma animação é preservado, e não cortado pelo arredondamento. - Se dois keyframes no mesmo canal caírem exatamente no mesmo tempo, o que vier depois na ordem do arquivo vence.
- Um keyframe com tempo não finito aborta aquela trilha e produz um único aviso
Malformed animation timeline for model ...por animação, enquanto as outras animações do modelo continuam sendo convertidas.
Animações de Duração Zero São Válidas
Uma animação cuja duração é 0 (ou negativa) é tratada como uma pose estática intencional — uma escolha comum de autoria para móveis e outros props que precisam de uma entrada nomeada de "sem animação". Ela é ignorada silenciosamente, sem aviso, e não contribui com nenhum frame.
Modos de Repetição
A configuração de repetição do Blockbench é lida diretamente da animação:
| Modo de repetição do Blockbench | Execução em Java | Exportação Bedrock |
|---|---|---|
loop | repete indefinidamente | "loop": true |
once | toca até o fim e para | "loop": false |
hold | toca até o fim e mantém o último frame | "loop": "hold_on_last_frame" |
Tipos de Interpolação
Cada keyframe carrega seu próprio tipo de interpolação, e o trecho que leva até um keyframe usa o tipo desse keyframe. Quatro são suportados:
| Tipo do Blockbench | Comportamento no FMM |
|---|---|
linear | Interpolação linear direta |
catmullrom | Interpolação suavizada (ease in/out) |
bezier | Aproximada com pontos de controle fixos 0.42 / 0.58 — o FMM não lê as alças bezier de cada keyframe |
step | Trava no valor anterior até o próximo keyframe |
Qualquer coisa fora desse conjunto falha na análise e é reportada como uma timeline malformada.
Canais Animados
Três canais são pré-processados por osso: rotação, posição e escala. Notas que importam na hora de criar:
- Valores de posição são divididos por 16 (pixels do Blockbench para blocos).
- Valores de rotação são convertidos para radianos.
- O
format_version5 e superiores do Blockbench inverte o sinal da rotação em X e Y e da posição em X. O FMM compensa isso automaticamente com base na versão de formato declarada, então não corrija manualmente — mas também não misture uma declaração v5 com dados no formato v4. - Um osso sem keyframes em um canal mantém seu valor de repouso naquele canal; um osso sem nenhum frame para um determinado tick é redefinido para rotação
0,0,0, translação0,0,0, escala1,1,1. - Os pontos de dados dos keyframes podem estar escritos como strings no
.bbmodel. O FMM os interpreta como números simples — uma string vazia vira1para escala e0nos demais casos, e qualquer coisa não interpretável registraFailed to parse supposed number value ...e vira0. Expressões Molang não são avaliadas. - Apenas o primeiro ponto de dados de um keyframe é lido, então os valores pre/post separados do Blockbench em um keyframe step se reduzem a um só.
O Que Não É Animado
- O osso
hitbox. Trilhas de animação que o tenham como alvo são ignoradas por completo. - Effect tracks do Blockbench (animadores de som, partícula e instruções de timeline). Qualquer animador cujo tipo não seja
boneounull_objecté ignorado, então o FMM não dispara sons nem partículas a partir de uma timeline de animação. Acione isso por um script Lua ou pelo seu próprio plugin. - Ossos que não puderam ser resolvidos pelo nome. Uma trilha que aponta para um osso inexistente registra
Failed to get bone <name> from model <model>!e é ignorada.
Cinemática Inversa (IK)
Null objects do Blockbench funcionam como controladores de IK. O FMM resolve as cadeias em tempo de execução com FABRIK (Forward And Backward Reaching Inverse Kinematics), limitado a 10 iterações com tolerância de 0.001.
Como isso se encaixa:
- Um null object com
ik_source(osso raiz da cadeia) eik_target(osso final ou locator) define uma cadeia. A cadeia é descoberta subindo pela hierarquia do alvo até a origem. - Apenas os keyframes de posição do null object são lidos. Eles viram um deslocamento de meta por frame relativo à posição de repouso do controlador; trilhas de rotação e escala em um null object são ignoradas.
- A cada tick, o deslocamento de meta do frame atual é aplicado e a cadeia é resolvida. Em um frame sem dados de IK, as rotações de IK da cadeia são limpas em vez disso.
lock_ik_target_rotationno null object é lido do modelo.
Cadeias que não podem ser resolvidas são ignoradas com um aviso nomeado no console — veja Notas de Autoria de Modelos para as mensagens exatas e as restrições de autoria.
Exportação Bedrock
Cada modelo convertido também gera um arquivo de animação Bedrock em animations/<model_id>.animation.json dentro do bundle gerado:
- Os identificadores de animação são
animation.fmm.<model_id>.<animation_name>, com os nomes higienizados para o Bedrock. animation_lengthé a duração em segundos, com piso em0.05, para que uma animação de um tick continue válida.- O modo de repetição é mapeado conforme a tabela Modos de Repetição.
- Um modelo sem nenhuma animação ainda recebe uma única entrada
idlesem efeito, para que a definição de entidade Bedrock permaneça válida. - Apenas ossos visuais são exportados.
hitbox, os ossos de nametagfmm_nametag_bone_*gerados automaticamente e os ossos de ponto de montagemm_são excluídos da geometria e, portanto, do bloco de ossos da animação. O ossotag_que você cria não é excluído — ele é exportado como qualquer outro osso (normalmente vazio, sem cubos); apenas o osso de nametag gerado a partir dele é filtrado. - Um controlador de animação é gerado por animação, alternado por uma entity property, que é como o FMM toca uma animação específica em um cliente Bedrock.
Veja Saída do Resource Pack para saber onde o bundle é gravado no disco.
Tocando Animações a Partir de Outros Sistemas
| Chamador | Ponto de entrada |
|---|---|
| Plugin (Java) | ModeledEntity#playAnimation(String, boolean blend, boolean loop) / #stopCurrentAnimations() / #hasAnimation(String) |
| Script Lua de prop | context.prop:play_animation(name, blend, loop) / context.prop:stop_animation() |
| Qualquer tabela de entidade Lua | entity.model:play_animation(name, blend, loop) / entity.model:stop_animations() (disponível quando entity.is_modeled é true) |
Veja o Guia de API e Desenvolvimento e a API Lua de Props e Itens para as superfícies em torno disso.
Solução de Problemas
Minha animação nunca toca automaticamente.
Apenas spawn, idle, walk, attack e death disparam sozinhas. Todo o resto precisa de uma chamada explícita a playAnimation / play_animation.
Meu modelo não faz absolutamente nada.
Ele provavelmente não tem nem uma animação spawn nem uma idle, então nenhum estado é assumido na criação. Adicione um idle.
Minha animação toca, mas nada se move. Verifique a duração da animação. Uma animação de duração zero é tratada como pose estática e é ignorada silenciosamente, por design.
As rotações estão espelhadas.
Verifique meta.format_version no .bbmodel. O FMM inverte o sinal da rotação em X/Y para a versão de formato 5 e superiores; um arquivo que declara uma versão mas carrega os dados de outra sairá espelhado.
O console diz Malformed animation timeline for model ....
Uma trilha daquela animação não pôde ser lida ou interpolada. O aviso dispara uma vez por animação e nomeia o osso ou controlador de IK envolvido; as outras animações do modelo continuam sendo convertidas.
Sons e partículas na minha timeline do Blockbench não fazem nada. Effect tracks não são importadas. Acione-as a partir de um script Lua ou do seu próprio plugin.