Pular para o conteúdo principal

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çãoRepeteQuando toca
spawnnãoUma vez, quando o modelo é criado. Cai para idle ao terminar
idlesimEnquanto a velocidade da entidade subjacente estiver em 0.08 ou abaixo
walksimEnquanto a velocidade da entidade subjacente estiver acima de 0.08
attacknãoQuando acionada; volta para idle ao terminar
deathnãoEm removeWithDeathAnimation()

Regras que decorrem de como a máquina de estados é construída:

  • O estado inicial é spawn se o modelo tiver um, caso contrário idle. 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 walk mas sem idle nunca sai de walk por 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 em idle; o armor stand que sustenta um prop não se move, então props também ficam em idle. 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 aqui

JUMP 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()
  • blend não faz cross-fade. true enfileira a animação para começar depois que o tick do estado atual terminar; false interrompe e troca imediatamente.
  • loop se 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 idle se não houver nenhum.
  • playAnimation retorna false quando o nome não corresponde nem a um estado registrado nem a uma animação do modelo.
  • stopCurrentAnimations() faz a transição para idle quando 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_melee ou attack_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 BlockbenchExecução em JavaExportação Bedrock
looprepete indefinidamente"loop": true
oncetoca até o fim e para"loop": false
holdtoca 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 BlockbenchComportamento no FMM
linearInterpolação linear direta
catmullromInterpolação suavizada (ease in/out)
bezierAproximada com pontos de controle fixos 0.42 / 0.58 — o FMM não lê as alças bezier de cada keyframe
stepTrava 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_version 5 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ção 0,0,0, escala 1,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 vira 1 para escala e 0 nos demais casos, e qualquer coisa não interpretável registra Failed to parse supposed number value ... e vira 0. 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 bone ou null_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:

  1. Um null object com ik_source (osso raiz da cadeia) e ik_target (osso final ou locator) define uma cadeia. A cadeia é descoberta subindo pela hierarquia do alvo até a origem.
  2. 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.
  3. 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.
  4. lock_ik_target_rotation no 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 em 0.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 idle sem efeito, para que a definição de entidade Bedrock permaneça válida.
  • Apenas ossos visuais são exportados. hitbox, os ossos de nametag fmm_nametag_bone_* gerados automaticamente e os ossos de ponto de montagem m_ são excluídos da geometria e, portanto, do bloco de ossos da animação. O osso tag_ 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

ChamadorPonto de entrada
Plugin (Java)ModeledEntity#playAnimation(String, boolean blend, boolean loop) / #stopCurrentAnimations() / #hasAnimation(String)
Script Lua de propcontext.prop:play_animation(name, blend, loop) / context.prop:stop_animation()
Qualquer tabela de entidade Luaentity.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.