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 os personagens e animações funcionam no Cave. Um personagem não é apenas um modelo 3D vagando pelo nível. Em uma configuração de jogo real, geralmente 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 reproduzida.

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 jogador que já se move, tem um personagem animado visível e toca animações básicas de locomoção.

image.png

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

Você vai aprender:

  • Como o template Player inicial é construído.
  • Por que a malha visual é uma entidade filha em vez de estar na entidade principal do jogador.
  • O que o Animation Component faz.
  • Como obter o animador a partir do Python (lógica similar se aplica aos Logic Bricks).
  • Como tocar animações pelo nome.
  • Como funcionam a mistura, as camadas e os filtros de ossos.
  • Como os callbacks de animação e sockets se encaixam no sistema.

A Estrutura do Template Player

No projeto inicial, o Player é um Entity Template. Isso significa que a configuração do jogador é 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 de projeto que você selecionou, mas a ideia importante é que a entidade raiz Player possui a configuração de jogabilidade, enquanto a entidade Mesh filho possui o personagem animado visível.

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

A Entidade Raiz do Player

A entidade raiz Player representa o personagem jogável. Geralmente, contém:

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

O Character Component é especialmente importante porque lida com física de estilo personagem: caminhada, pulo, colisão, inclinações e movimento contra o mundo. Isso significa que o Player raiz é principalmente responsável pela jogabilidade e pela física.

A Entidade Filha Mesh

Dentro do template do jogador, há uma entidade filha chamada Mesh. Essa filha geralmente contém:

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

O Mesh Component dá ao personagem seu modelo 3D visível e material. Se o personagem usar vários 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 toca as animações.

Então, de maneira simples:

Entity Main Responsibility
Player Física, movimento, lógica de jogabilidade, propriedades.
Player -> Mesh Modelo visível, material, animação da armadura.

Essa separação é intencional.

Por Que a Malha É uma Entidade Filha

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

A razão é que o personagem físico e o personagem animado visível geralmente precisam de transformações diferentes.

A transformação raiz do Player representa o corpo da jogabilidade. É a coisa que se move pelo mundo, colide com paredes e carrega o Character Component. A transformação da entidade filha Mesh representa como o personagem se parece.

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

  • A malha pode precisar de uma escala diferente do cápsula física.
  • A malha pode precisar de um pequeno deslocamento para alinhar com a colisão do personagem.
  • A malha pode precisar girar independentemente do corpo de movimento.
  • A malha pode precisar enfrentar a direção do movimento enquanto a raiz mantém uma orientação de jogabilidade.
  • A malha pode precisar permanecer virada para frente enquanto o jogador se move para os lados.

Por exemplo, imagine que o jogador está se movendo para a esquerda. Se você possui uma animação de "andar para a esquerda" apropriada, pode querer que a malha do personagem mantenha-se voltada para frente enquanto a animação lida com o movimento para o lado. Mas se você não tem uma animação lateral, pode querer que a malha gire para a esquerda para que o personagem ande visualmente naquela direção.

Ambos os casos são válidos.

Eles requerem transformações separadas:

Transform Controls
Transformação raiz do Player Posição física, corpo de jogabilidade, movimento pelo mundo.
Transformação filha Mesh Orientação visual, escala, deslocamentos, apresentação da animação.

É por isso que o jogador inicial e o inimigo 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.
  • De uma Animation padrão.
  • De um Mesh Component válido na mesma entidade (ou múltiplos).

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

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

  • Proto Mesh
  • Proto Mat
  • Proto Armature
  • p-idle como a animação padrão (animação em repouso)

Esses ativos iniciais estão lá para que você possa imediatamente inspecionar 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 movimento ao longo do tempo para os ossos em uma armadura.

O Animation Component conecta essas peças juntas:

Piece Meaning
Mesh O modelo visível do personagem.
Armature O esqueleto dentro do personagem.
Animation O movimento, como parado, andar, correr ou cair.
Animation Component O componente que toca a animação na entidade.

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

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 torcida, deslocada ou estranha, verifique o rig de origem, a armadura importada e a configuração de retargeting.


Obtendo o Animador do Python

Agora vamos começar a explorar como podemos animar personagens através da lógica.

Nos projetos do Cave, é prática comum manter a lógica de jogabilidade do jogador na entidade raiz Player, e então acessar a entidade filha Mesh a partir desse código. Os scripts 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: tocando uma animação de repouso.
        self.animator.playByName("p-idle", blend=0.2, loop=True)

Neste exemplo:

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

Essa é a conexão básica que você precisa antes de controlar animações através do código.

Tocando Animações pelo Nome

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

Seu uso básico se parece assim:

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

Os parâmetros importantes são:

Parâmetro Significado
anim O nome do ativo de animação a ser reproduzido.
blend Quanto tempo, em segundos, o Cave deve misturar na nova animação.
loop Se a animação deve se repetir.
layer Qual camada 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 mesmas chamadas usadas pelos scripts padrão de jogador e inimigo.

Um Exemplo Simples de Locomoção

Uma configuração comum de animação de jogador é:

  • Tocar parado quando o personagem está em pé.
  • Tocar andar quando o personagem se move.
  • Tocar correr quando o personagem se move enquanto corre.
  • Tocar cair 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 isso pela sua própria entrada ou condição de jogabilidade.

```python
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 é intencionalmente simples. O verdadeiro controlador inicial também lida com entrada, direção do movimento, salto, comportamento de apontar e clicar opcional, e rotação da malha, mas a ideia da animação é a mesma.

Rotacionando a Malha em Direção ao Movimento

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

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 de jogo e a malha visual podem ser controlados separadamente.

Isso permite que você escolha como o personagem deve se orientar:

  • Virar na direção do movimento.
  • Continuar de frente enquanto se desloca lateralmente.
  • Rotacionar suavemente em direção à direção desejada.
  • Usar diferentes regras de orientação visual para diferentes modos de jogo.

Esta é uma das principais razões pelas quais o personagem inicial tem uma entidade Mesh filha.

Mistura de Animação

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

Sem mistura, mudar de uma animação para outra pode ser instantânea. Com a mistura, o Cave pode transitar suavemente entre animações na mesma camada.

Por exemplo:

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

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

Aqui, blend=0.2 significa que o Cave vai misturar na nova animação ao longo de 0.2 segundos.

Isso é especialmente importante para os personagens. Um jogador pode notar facilmente mudanças bruscas nas animações, mesmo em um protótipo.

Boas valores de mistura para iniciantes geralmente são pequenos:

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

Você deve ajustar isso conforme necessário.

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 camadas inferiores. Você pode executar animações em qualquer camada que desejar.

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

Por exemplo:

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

Se você tiver uma animação de ação da parte superior do corpo, como um ataque, mira ou recarga, você 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 total do corpo, como parado, andando, correndo, pulando, caindo.
Camada 1 Ações da parte superior do corpo, como ataque, mira, recarga, segurar arma.

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

Pesos de Camada

Cada camada tem um peso, que controla quanta influência essa camada possui.

Você pode mudá-lo do Python:

self.animator.setLayerWeight(1, 1.0)

Você também pode lê-lo:

weight = self.animator.getLayerWeight(1)

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

Os pesos de camada são úteis quando você deseja desvanecer uma camada de ação para dentro ou para fora, como levantar lentamente uma arma, mirando ou misturando em uma postura especial.

Vale a pena mencionar que o parâmetro de mistura só mistura animações entre as mesmas camadas. E não vamos misturar animações sendo reproduzidas em camadas diferentes. Então, se você tiver uma animação sendo reproduzida na camada 0, e de repente reproduzir uma animação na camada 1, não importa qual seja o valor de mistura, não vai misturar entre elas. Ele só vai misturar se já havia outra animação sendo executada na camada 1. Se você quiser misturar entre camadas, precisará fazer uma lógica personalizada para usar o peso da camada para isso.

Filtros de Osso

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

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

Por exemplo, você pode querer:

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

No 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 agora pode ser usada para animações da parte superior do corpo.

Os nomes dos seus ossos dependem da armadura que você importou. Sempre examine sua armadura e use os nomes corretos dos ossos para o seu personagem.

Exemplo Prático de Camada

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

Uma configuração possível é:

Camada O que Ela Manipula
Camada 0 Parado, andando, correndo, caindo.
Camada 1 Ataque da parte superior do corpo.

O código poderia parecer 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 ativo de animação chamado Attack. Se seu projeto usar um nome de animação diferente, use esse nome em vez disso.

A ideia importante é que a locomoção permaneça na camada 0, enquanto o ataque acontece 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 final da animação.
  • Em um quadro específico da animação.

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

Exemplos:

  • Reproduzir um som de passo.
  • Spawn de poeira em um passo.
  • Aplicar dano em um quadro de ataque.
  • Começar um rastro de arma.
  • Acionar um efeito de partículas.
  • Notificar a lógica que uma animação terminou.

As animações geralmente conhecem o tempo exato melhor do que o código. Se uma espada deve causar dano apenas quando o golpe alcança a área do alvo, um callback de animação pode colocar esse momento de jogabilidade no quadro correto.

Exemplo de Callback de Passo

Projetos iniciais podem incluir callbacks de passo em animações de caminhada e corrida. Inspecionar esses é uma boa maneira de ver um callback de animação prático em ação.

A ideia é simples:

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

Dentro de um callback de animação, o Cave fornece variáveis úteis como:

  • entity, a entidade proprietária.
  • animator, o Componente de Animação.
  • handle, a camada/manipulador de animação sendo reproduzida.

Um callback de passo muito pequeno poderia parecer assim:

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

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

Callbacks de Animação Reutilizáveis

Callbacks pertencem ao ativo de animação.

Isso significa que se várias entidades reproduzirem a mesma animação, o callback pode ser executado para cada entidade que a utilizar.

Isso torna os callbacks reutilizáveis. Por exemplo, a mesma animação de caminhada pode acionar sons de passos para cada personagem que a reproduzir, desde que o código do callback use a entidade proprietária corretamente.

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

Callbacks de Pose Avançados

O Cave também suporta callbacks pós-avaliação através do Python. Esses acontecem após o Componente de Animação avaliar a pose da armadura.

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

  • Cinemática inversa.
  • Mira da cabeça.
  • Mira de armas.
  • Colocação dos pés.
  • Ajustes finais de pose.

O Player Toolkit inicial inclui um exemplo de IK de pé que usa esse conceito. Ele obtém o filho Mesh, obtém o componente de Animação, 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 reprodução normal da animação.

  • Espada em uma mão.
  • Arma em uma mão.
  • Escudo em um braço.
  • Capacete em uma cabeça.
  • Mochila em um osso da coluna.
  • Objeto anexado a uma mão.

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

O Que Você Deve Lembrar

  • O jogador inicial é um modelo com uma entidade raiz Player e uma entidade filha Mesh.
  • A raiz Player geralmente lida com física, movimento, propriedades e lógica de jogo.
  • A entidade filha Mesh geralmente lida com o modelo visível, material, armadura e Componente de Animação.
  • Essa separação dá ao corpo físico e ao personagem visual transformações separadas.
  • O Componente de Animação reproduz animações esqueléticas usando uma armadura e ativos de animação.
  • Em Python, é comum chamar o Componente de Animação de animator.
  • playByName reproduz um ativo de animação pelo nome e suporta mesclagem, looping e camadas.
  • Camadas permitem que várias animações se empilhem juntas.
  • 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 jogo.
  • Sockets de animação anexam objetos a ossos animados.

Quando você inspeciona o modelo Player ou Enemy inicial, use esta estrutura como seu mapa: entidade raiz para jogabilidade, entidade filha Mesh para visuais e animação.