Keep your place in this quest

Log in or sign up for free to subscribe, follow lesson progress, and access more learning content.

Agora vamos entender como personagens e animações funcionam no Cave. Um personagem não é apenas um modelo 3D andando pelo nível. Em um jogo real, geralmente ele combina física, lógica de movimento, uma malha visível, uma armadura, animações e código ou Logic Bricks que decidem qual animação deve ser executada.

Para tornar isso prático, usaremos o template padrão Player dos projetos iniciais do Cave como referência principal. Se você criar um Third Person Game ou Top Down Game, o Cave gera um player que já se move, possui um personagem animado visível e executa animações básicas de locomoção.

image.png

Esta lição vai mostrar como essa configuração está organizada e como você pode controlar animações via Python.

Você vai aprender:

  • Como o template inicial Player é construído.
  • Por que a malha visual é uma entidade filha ao invés de estar na entidade raiz do player.
  • O que o Animation Component faz.
  • Como obter o animador a partir do Python (a lógica é similar para Logic Bricks).
  • Como tocar animações pelo nome.
  • Como funcionam blending, layers e filtros de ossos.
  • Como callbacks e sockets de animação se encaixam no sistema.

A Estrutura do Template Player

No projeto inicial, o Player é um Entity Template. Isso significa que a configuração do player é reutilizável e pode ser colocada em várias cenas.

A estrutura é mais ou menos assim:

image.png

Os filhos exatos podem variar dependendo das opções do projeto que você selecionou, mas a ideia importante é que a entidade raiz Player possui a configuração de gameplay, enquanto a entidade filha Mesh possui o personagem animado visível.

O inimigo inicial usa uma ideia muito semelhante. A entidade raiz do inimigo possui a física e comportamento do personagem, e sua entidade filha Mesh contém a malha visível e o Animation Component.

A Entidade Raiz Player

A entidade raiz Player representa o personagem do gameplay. Ela geralmente contém:

  • Um Transform Component.
  • Um Character Component.
  • Componentes Python para movimento do player, UI, helpers de animação e outras lógicas de gameplay.
  • Propriedades como saúde ou configurações opcionais de comportamento.
  • Filhos usados para câmera, UI, malha visual e objetos auxiliares.

O Character Component é especialmente importante porque gerencia a física no estilo personagem: caminhar, pular, colisão, inclinações e movimentação pelo mundo. Isso significa que o Player raiz é principalmente responsável pelo gameplay e física.

A Entidade Filha Mesh

Dentro do template player, existe uma entidade filha chamada Mesh. Essa filha geralmente contém:

  • Um Transform Component.
  • Um ou mais Mesh Components.
  • Um Animation Component.

O Mesh Component fornece ao personagem seu modelo 3D visível e material. Se o personagem usa múltiplos materiais, o Cave pode representar isso com múltiplos Mesh Components, seguindo a mesma regra de multimaterial explicada na lição de importação.

O Animation Component é o que avalia a armadura e executa as animações.

Resumindo de forma simples:

Entidade Responsabilidade Principal
Player Física, movimento, lógica de gameplay, propriedades.
Player -> Mesh Modelo visível, material, animação de armadura.

Essa separação é intencional.

Por Que a Mesh É Uma Entidade Filha

Você pode se perguntar por que a malha não está diretamente na entidade raiz Player.

O motivo é que o personagem da física e o personagem animado visível frequentemente precisam de transforms diferentes.

O transform raiz do Player representa o corpo do gameplay. É o que se move pelo mundo, colide com paredes e carrega o Character Component. O transform da entidade filha Mesh representa como o personagem parece.

Essa separação é útil em muitas situações:

  • A malha pode precisar de uma escala diferente da cápsula física.
  • A malha pode precisar de um pequeno deslocamento para alinhar com a colisão do personagem.
  • A malha pode precisar se rotacionar independentemente do corpo de movimento.
  • A malha pode precisar olhar na direção do movimento enquanto a raiz mantém uma orientação de gameplay.
  • A malha pode precisar continuar olhando para frente enquanto o player se move para o lado.

Por exemplo, imagine que o player está se movendo para a esquerda. Se você tem uma animação adequada de "andar para a esquerda", pode querer que a malha do personagem continue olhando para frente enquanto a animação controla o movimento lateral. Mas se não houver animação lateral, pode querer que a malha rotacione para a esquerda, para que o personagem pareça andando naquela direção.

Ambos os casos são válidos.

Eles exigem transforms separados:

Transform Controla
Transform raiz Player Posição da física, corpo do gameplay, movimentação pelo mundo.
Transform filho Mesh Orientação visual, escala, deslocamentos, apresentação da animação.

É por isso que o player e o inimigo iniciais mantêm a malha animada como uma entidade filha.

O Que o Animation Component Faz

O Animation Component é o componente principal usado para tocar animações esqueléticas em uma entidade.

Ele precisa de:

  • Uma Armature.
  • Uma Animation padrão.
  • Um Mesh Component válido na mesma entidade (ou múltiplos).

A armadura define o esqueleto. A animação define como esse esqueleto se movimenta ao longo do tempo. O Animation Component avalia a pose animada final e a aplica ao personagem visível.

No player padrão inicial, a entidade filha Mesh usa:

  • Proto Mesh
  • Proto Mat
  • Proto Armature
  • p-idle como animação padrão (animação de descanso)

Esses assets iniciais estão ali para que você possa inspecionar imediatamente um personagem animado funcionando.

Armaduras, Animações e Retargeting

  • Uma Armature é o esqueleto do personagem. Ela contém os ossos que a malha segue.
  • Uma Animation armazena o movimento ao longo do tempo para os ossos na armadura.

O Animation Component conecta essas partes:

Parte Significado
Mesh O modelo do personagem visível.
Armature O esqueleto dentro do personagem.
Animation O movimento, como idle, walk, run ou fall.
Animation Component O componente que executa a animação na entidade.

O Cave também pode fazer retargeting de animações quando possível. Se a animação tocada pertence a uma armadura diferente compatível, o Animation Component pode usar retargeting para aplicar aquele movimento na armadura atual.

O retargeting é útil quando você quer reutilizar animações entre personagens compatíveis, mas ainda depende da qualidade e compatibilidade dos rigs importados. Se uma animação parecer distorcida, desviada ou estranha, verifique o rig de origem, a armadura importada e a configuração do retargeting.


Obtendo o Animador via Python

Agora vamos começar a explorar como animar personagens por meio da lógica.

Em projetos Cave, é prática comum manter a lógica de gameplay do player na entidade raiz Player, e acessar a entidade filha Mesh a partir desse código. Os scripts iniciais padrão seguem essa ideia.

Aqui está o padrão básico:

import cave

class PlayerAnimationExample(cave.Component):
    def start(self, scene: cave.Scene):
        self.mesh : cave.Entity = self.entity.getChild("Mesh")
        self.animator : cave.AnimationComponent = self.mesh.get("Animation")

    def update(self):
        # Exemplo: executando uma animação de idle.
        self.animator.playByName("p-idle", blend=0.2, loop=True)

Neste exemplo:

  • self.entity é a entidade raiz Player.
  • getChild("Mesh") localiza a entidade filha visual.
  • self.mesh.get("Animation") obtém o Animation Component.
  • A variável é chamada animator, que é a prática comum de nomeação em scripts Cave.
  • playByName(...) executa uma animação pelo nome do asset.

Esta é a conexão básica que você precisa antes de controlar animações por código.

Tocando Animações pelo Nome

O método mais comum que você usará é o playByName.

O uso básico é assim:

self.animator.playByName("p-walk", blend=0.2, loop=True)

Os parâmetros importantes são:

Parâmetro Significado
anim O nome do asset de animação a ser tocado.
blend Quanto tempo, em segundos, o Cave deve fazer o blending para a nova animação.
loop Se a animação deve repetir.
layer Qual layer de animação deve tocar a animação.

Por exemplo:

self.animator.playByName("p-idle", blend=0.2, loop=True)
self.animator.playByName("p-walk", blend=0.2, loop=True)
self.animator.playByName("p-run", blend=0.2, loop=True)
self.animator.playByName("p-fall", blend=0.2, loop=True)

Essas são as chamadas usadas pelos scripts padrão do player e inimigo.

Um Exemplo Simples de Locomoção

Uma configuração comum de animação do player é:

  • Tocar idle quando o personagem está parado.
  • Tocar walk quando o personagem se move.
  • Tocar run quando o personagem se move correndo.
  • Tocar fall quando o personagem não está no chão.

Aqui está um exemplo simplificado:

import cave

class SimplePlayerAnimator(cave.Component):
    def start(self, scene: cave.Scene):
        self.character : cave.CharacterComponent = self.entity.get("Character")

        self.mesh : cave.Entity = self.entity.getChild("Mesh")
        self.meshTransform : cave.TransformComponent = self.mesh.getTransform() if self.mesh else None
        self.animator      : cave.AnimationComponent = self.mesh.get("Animation") if self.mesh else None

    def update(self):
        if self.animator is None or self.character is None:
            return

        direction = self.character.getWalkDirection()
        isMoving = direction.length() > 0
        isRunning = False # Substitua isto pela sua própria condição de entrada ou gameplay.

if self.character.onGround():
            if isMoving:
                if isRunning:
                    self.animator.playByName("p-run", blend=0.2, loop=True)
                else:
                    self.animator.playByName("p-walk", blend=0.2, loop=True)
            else:
                self.animator.playByName("p-idle", blend=0.2, loop=True)
        else:
            self.animator.playByName("p-fall", blend=0.2, loop=True)

Este exemplo é propositalmente simples. O controlador inicial real também trata entrada, direção do movimento, pulo, comportamento opcional de apontar e clicar, e rotação da malha, mas a ideia da animação é a mesma.

Girando a Malha para a Direção do Movimento

Porque a entidade visual Mesh tem sua própria transformação, você pode girá-la separadamente da raiz Player.

O jogador inicial faz isso quando o personagem se move:

if direction.length() > 0 and self.meshTransform:
    self.meshTransform.lookAtSmooth(
        self.entity.getTransform().transformDirection(-direction),
        6.0 * cave.getDeltaTime()
    )

A ideia importante não é a matemática exata. A ideia importante é que o corpo da jogabilidade e a malha visual podem ser controlados separadamente.

Isso permite que você escolha como o personagem deve ficar posicionado:

  • Olhar para a direção do movimento.
  • Manter olhando para frente enquanto se move lateralmente.
  • Girar suavemente em direção à direção desejada.
  • Usar regras diferentes de orientação visual para modos de jogabilidade distintos.

Esta é uma das principais razões para o personagem inicial ter uma entidade filho Mesh.

Mistura de Animação

A mistura de animação torna as transições mais suaves.

Sem mistura, trocar de uma animação para outra pode ocorrer instantaneamente. Com mistura, o Cave pode fazer transições suaves entre animações na mesma camada.

Por exemplo:

# De:
self.animator.playByName("p-idle", blend=0.2, loop=True)

# Para:
self.animator.playByName("p-walk", blend=0.2, loop=True)

Aqui, blend=0.2 significa que o Cave fará a transição para a nova animação ao longo de 0.2 segundos.

Isso é especialmente importante para personagens. O jogador pode notar transições bruscas muito facilmente, mesmo em um protótipo.

Bons valores iniciais para blend geralmente são pequenos:

  • 0.1 para transições muito rápidas.
  • 0.2 para transições normais de locomoção.
  • 0.4 ou mais para transições mais lentas e pesadas.

Você deve ajustar isso conforme a sensação desejada.

Camadas de Animação

O Cave suporta múltiplas camadas de animação. Cada camada pode reproduzir sua própria animação, e camadas superiores podem ser empilhadas sobre as inferiores. Você pode executar animações em qualquer camada que desejar.

A animação padrão de locomoção geralmente roda na camada 0.

Por exemplo:

self.animator.playByName("p-walk", blend=0.2, layer=0, loop=True)

Se você tem uma animação de ação para a parte superior do corpo, como atacar, mirar ou recarregar, pode reproduzi-la em outra camada:

self.animator.playByName("Attack", blend=0.1, layer=1, loop=False)

A ideia é:

Camada Propósito Comum
Camada 0 Locomoção completo do corpo, como idle, walk, run, jump, fall.
Camada 1 Ações da parte superior do corpo, como atacar, mirar, recarregar, segurar arma.

Isso permite que o personagem continue andando enquanto outra camada adiciona uma ação sobreposta.

Pesos das Camadas

Cada camada tem um peso, que controla o quanto de influência essa camada tem.

Você pode alterar isso via Python:

self.animator.setLayerWeight(1, 1.0)

Também pode ler o valor:

weight = self.animator.getLayerWeight(1)

O peso 1.0 significa que a camada está totalmente ativa. Um peso de 0.0 significa que ela não tem influência visível.

Pesos das camadas são úteis quando você quer fazer um fade de ativação ou desativação de uma camada de ação, como levantar uma arma lentamente, mirar, ou transitar para uma postura especial.

Vale mencionar que o parâmetro blend somente mistura animações dentro da mesma camada. Não misturamos animações que são executadas em camadas diferentes. Portanto, se você tem uma animação na camada 0, e de repente começa a tocar uma animação na camada 1, não haverá mistura entre elas, independentemente do valor do blend. A mistura ocorre apenas se já houver outra animação executando na mesma camada. Se desejar misturar entre camadas, precisa criar uma lógica personalizada para usar o peso das camadas para isso.

Filtros de Ossos

Filtros de ossos controlam quais ossos são afetados por uma camada e a influência de cada osso.

Isso é o que torna possível animação na parte superior do corpo.

Por exemplo, você pode querer:

  • Camada 0 para controlar o corpo inteiro.
  • Camada 1 para controlar apenas a coluna, braços, mãos e ossos da arma.

Em Python, você pode criar um filtro para uma camada e atribuir influência a um osso e seus filhos:

def setupUpperBodyLayer(self):
    armature = self.animator.armature.get()
    spine = armature.getBone("mixamorig:Spine")

    upperBody = self.animator.createLayerFilter(1)
    upperBody.defaultBlend = 0.0
    upperBody.setToBone(spine, 1.0, recursive=True)

Neste exemplo:

  • defaultBlend = 0.0 significa que a camada não afeta ossos por padrão.
  • setToBone(spine, 1.0, recursive=True) significa que a coluna e seus filhos são totalmente afetados.
  • A camada 1 pode agora ser usada para animações da parte superior do corpo.

Os nomes dos ossos dependem do armature que você importou. Sempre inspecione seu armature e use os nomes corretos para seu personagem.

Exemplo Prático de Camadas

Imagine um personagem em terceira pessoa que pode andar e atacar ao mesmo tempo.

Uma configuração possível é:

Camada O Que Ela Controla
Camada 0 Idle, andar, correr, cair.
Camada 1 Ataque da parte superior do corpo.

O código poderia ser assim:

import cave

class CombatAnimator(cave.Component):
    def start(self, scene: cave.Scene):
        self.mesh = self.entity.getChild("Mesh")
        self.animator : cave.AnimationComponent = self.mesh.get("Animation") if self.mesh else None

        if self.animator:
            armature = self.animator.armature.get()
            spine = armature.getBone("mixamorig:Spine")

            upperBody = self.animator.createLayerFilter(1)
            upperBody.defaultBlend = 0.0
            upperBody.setToBone(spine, 1.0, recursive=True)
            self.animator.setLayerWeight(1, 1.0)

    def playWalk(self):
        self.animator.playByName("p-walk", blend=0.2, layer=0, loop=True)

    def playAttack(self):
        self.animator.playByName("Attack", blend=0.1, layer=1, loop=False)

Este exemplo assume que você tem um asset de animação chamado Attack. Se seu projeto usa um nome diferente, use esse nome.

A ideia importante é que a locomoção fica na camada 0, enquanto o ataque roda na camada 1 e afeta apenas os ossos filtrados.

Callbacks de Animação

Animações podem executar código Python em momentos específicos.

O Cave suporta callbacks como:

  • No início da animação.
  • No fim da animação.
  • Em um quadro específico da animação.

Callbacks são úteis quando a animação deve disparar jogabilidade ou efeitos.

Exemplos:

  • Tocar som do passo.
  • Gerar poeira ao pisar.
  • Aplicar dano num quadro de ataque.
  • Iniciar trilha de arma.
  • Disparar efeito de partículas.
  • Notificar que uma animação terminou.

As animações geralmente sabem o momento exato melhor que o código. Se uma espada deve causar dano apenas quando o balanço alcança o alvo, um callback de animação pode posicionar esse momento no quadro certo.

Exemplo de Callback de Passo

Projetos iniciais podem incluir callbacks nos passos das animações de andar e correr. Inspecionar esses exemplos é um bom jeito de ver callback em ação.

A ideia é simples:

  1. A animação sabe quando o pé toca o chão.
  2. O callback está posicionado naquele quadro.
  3. O callback toca um som ou gera um pequeno efeito.

Num callback de passo muito simples, você poderia ter:

cave.playSound("Footstep Grass", volume=0.5)

Depois, você pode expandir essa ideia para escolher sons diferentes dependendo do material do chão, velocidade do personagem ou estado atual.

Callbacks Reutilizáveis

Callbacks pertencem ao asset de animação.

Isso significa que se múltiplas entidades tocarem a mesma animação, o callback pode rodar para cada entidade que o usar.

Isso torna os callbacks reutilizáveis. Por exemplo, a mesma animação de andar pode disparar sons de passo para todos os personagens que a reproduzirem, desde que o código do callback use a entidade dona corretamente.

A animação armazena o tempo exato. A entidade fornece o contexto.

Callbacks Avançados de Poses

O Cave também suporta callbacks pós-avaliação via Python. Eles ocorrem após o Animation Component avaliar a pose do armature.

Este é um recurso avançado, mas pode ser usado para:

  • Cinemática inversa.
  • Mira da cabeça.
  • Mira da arma.
  • Posicionamento dos pés.
  • Ajustes finais da pose.

O Player Toolkit inicial inclui um exemplo de Foot IK que usa este conceito. Ele obtém o filho Mesh, obtém o componente Animation, e então registra um método com:

self.animator.addPostEvaluationCallback(self.postEvaluation)

Você não precisa escrever sistemas de IK imediatamente, mas é útil saber que o Cave permite que o Python ajuste a pose final após a execução normal da animação.

Componente Animation Socket

O Animation Socket Component permite que uma entidade filha siga um osso de uma entidade animada pai.

A entidade filha deve estar sob uma entidade pai que tenha um Animation Component. Então o socket pode escolher um osso e copiar posição, rotação, e opcionalmente escala desse osso, com offsets se necessário.

Sockets são úteis para prender coisas em personagens animados:

  • Espada na mão.
  • Arma de fogo na mão.
  • Escudo no braço.
  • Capacete na cabeça.
  • Mochila no osso da coluna.
  • Objeto anexado à mão.

Por exemplo, se um personagem segura uma espada, a espada pode ser uma entidade filha com um Componente de Soquete de Animação que segue o osso da mão. Enquanto o personagem ataca, corre ou fica parado, a espada permanece presa à posição animada correta.

O Que Você Deve Lembrar

  • O jogador inicial é um template com uma entidade raiz Player e uma entidade filha Mesh.
  • A entidade raiz Player geralmente controla física, movimento, propriedades e lógica de jogabilidade.
  • A entidade filha Mesh geralmente controla o modelo visível, material, armadura e Componente de Animação.
  • Essa separação oferece transformações separadas para o corpo físico e o personagem visual.
  • O Componente de Animação executa animações esqueléticas usando uma armadura e recursos de animação.
  • Em Python, é comum chamar o Componente de Animação de animator.
  • playByName executa um recurso de animação pelo nome e suporta mistura, repetição e camadas.
  • Camadas permitem que múltiplas animações sejam sobrepostas.
  • Filtros de osso permitem que uma camada afete apenas parte do esqueleto.
  • Callbacks de animação conectam o tempo da animação a eventos de jogabilidade.
  • Soquetes de animação anexam objetos a ossos animados.

Ao inspecionar o template Player ou Enemy inicial, use essa estrutura como seu mapa: entidade raiz para jogabilidade, entidade filha Mesh para visual e animação.