Godot do Zero: Animações 2D com AnimatedSprite2D e AnimationPlayer

Capítulo 8

Tempo estimado de leitura: 9 minutos

+ Exercício

O que são animações 2D na Godot (e por que existem dois caminhos)

Em jogos 2D, “animação” pode significar duas coisas diferentes: trocar quadros (frames) de um sprite para dar vida ao personagem e animar propriedades (posição, rotação, escala, cor, opacidade, emissão de partículas, sons, eventos) com controle de timing. Na Godot, esses dois usos se encaixam muito bem em dois nós:

  • AnimatedSprite2D: ideal para animações por frames (spritesheet/frames) como idle, run, jump, attack.
  • AnimationPlayer: ideal para animar propriedades e orquestrar efeitos (ex.: piscar ao tomar dano, recuo, câmera, partículas, som, habilitar/desabilitar hitbox em um intervalo específico).

Um fluxo comum é: AnimatedSprite2D para o “desenho” do personagem e AnimationPlayer para efeitos e timing de gameplay (principalmente ataques e hit reactions).

Configuração prática: personagem com AnimatedSprite2D

1) Estrutura recomendada de nós

Dentro da cena do personagem (por exemplo, um CharacterBody2D já existente), use algo como:

Player (CharacterBody2D)  ├─ AnimatedSprite2D (Sprite)  ├─ AnimationPlayer (FX)  ├─ AttackArea (Area2D)      └─ CollisionShape2D

AnimatedSprite2D cuidará das animações por frames. O AnimationPlayer (opcional, mas recomendado) cuidará de efeitos e do timing do ataque (ex.: ligar/desligar a hitbox).

2) Criando as animações no AnimatedSprite2D

Selecione o nó AnimatedSprite2D e, no Inspector, configure:

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

Baixar o aplicativo

  • Sprite Frames: crie um novo recurso (New SpriteFrames).
  • Abra o editor de SpriteFrames e crie animações com estes nomes (exemplo): idle, run, jump, fall, attack.
  • Para cada animação, adicione os frames correspondentes (por spritesheet ou imagens separadas).
  • Ajuste o FPS e marque Loop apenas nas animações que repetem (geralmente idle e run). Em jump, fall e attack, normalmente não usar loop.

Dica prática: mantenha os nomes das animações consistentes e simples, porque você vai referenciá-los no script.

Configuração prática: inimigo com AnimatedSprite2D

1) Animações do inimigo

No inimigo, repita o processo no AnimatedSprite2D e crie: idle, walk, hit.

  • idle e walk: geralmente em loop.
  • hit: geralmente sem loop (toca uma vez e volta para idle/walk).

Rotina clara para trocar animações sem “reiniciar” toda hora

Um erro comum é chamar play("run") a cada frame mesmo quando a animação já está tocando. Isso pode causar “travadas” (a animação volta para o primeiro frame repetidamente) e também dificulta transições.

A solução é centralizar a troca em uma função que só troca quando necessário.

Função utilitária: tocar apenas se mudou

func play_anim_if_changed(sprite: AnimatedSprite2D, anim_name: StringName) -> void:    if sprite.animation != anim_name:        sprite.play(anim_name)

Use essa função sempre que decidir a animação do frame.

Personagem: escolhendo animação por estados físicos e eventos

1) Conceito: prioridade de estados

Você terá estados que competem entre si. Exemplo de prioridade típica:

  • Ataque (evento) costuma ter prioridade máxima enquanto estiver acontecendo.
  • No ar: jump (subindo) ou fall (descendo).
  • No chão: run se estiver se movendo, senão idle.

Essa prioridade evita que, por exemplo, o personagem toque run no meio do attack.

2) Exemplo de variáveis de controle

No script do player, você pode manter um “estado de ataque” simples:

@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D@onready var fx: AnimationPlayer = $AnimationPlayer@onready var attack_area: Area2D = $AttackAreavar is_attacking: bool = false

3) Atualização de animação (rotina por frame)

Crie uma função dedicada e chame-a no seu _physics_process (ou no ponto onde você já atualiza movimento):

func update_animation() -> void:    # 1) Ataque tem prioridade    if is_attacking:        play_anim_if_changed(sprite, "attack")        return    # 2) No ar: decide entre jump/fall pela velocidade vertical    if not is_on_floor():        if velocity.y < 0.0:            play_anim_if_changed(sprite, "jump")        else:            play_anim_if_changed(sprite, "fall")        return    # 3) No chão: idle/run pela velocidade horizontal    if abs(velocity.x) > 1.0:        play_anim_if_changed(sprite, "run")    else:        play_anim_if_changed(sprite, "idle")

Observações importantes:

  • O limiar 1.0 evita “tremedeira” quando a velocidade fica muito pequena.
  • O return deixa explícita a prioridade e evita que uma decisão sobrescreva a outra no mesmo frame.

4) Virar o sprite (direção) sem misturar com animação

É comum espelhar o sprite conforme a direção. Faça isso separado da troca de animação:

func update_facing() -> void:    if velocity.x > 0.0:        sprite.flip_h = false    elif velocity.x < 0.0:        sprite.flip_h = true

Assim, a lógica de “qual animação tocar” não fica poluída com detalhes de renderização.

5) Disparando ataque por evento (input) sem reiniciar repetidamente

Quando o ataque é acionado, você quer:

  • Marcar is_attacking = true.
  • Tocar a animação attack uma vez.
  • Voltar ao controle normal quando terminar.

Uma forma robusta é usar o sinal animation_finished do AnimatedSprite2D:

func _ready() -> void:    sprite.animation_finished.connect(_on_sprite_animation_finished)    attack_area.monitoring = false
func try_attack() -> void:    if is_attacking:        return    is_attacking = true    play_anim_if_changed(sprite, "attack")    # Se você não usar AnimationPlayer para timing, pode ligar a hitbox aqui,    # mas o ideal é controlar a janela de acerto com AnimationPlayer (ver abaixo).
func _on_sprite_animation_finished() -> void:    if sprite.animation == "attack":        is_attacking = false

Cuidados:

  • Não chame try_attack() continuamente enquanto o botão estiver pressionado; prefira um evento de “apertou” (ex.: is_action_just_pressed no seu código de entrada já existente).
  • Se a animação attack estiver em loop, o sinal não vai encerrar como esperado. Garanta que attack não seja loop.

Quando usar AnimationPlayer (e como combinar com AnimatedSprite2D)

AnimatedSprite2D: melhor para frames

  • Troca de frames rápida e simples.
  • Ideal para “estado visual” do personagem/inimigo.
  • Menos indicado para sincronizar eventos precisos (ex.: ligar hitbox só em 0.12s–0.22s) sem lógica extra.

AnimationPlayer: melhor para propriedades, efeitos e timing

Use AnimationPlayer quando você precisa:

  • Animar propriedades (ex.: modulate.a para fade, position.x para recoil, scale para squash/stretch).
  • Controlar janelas de hitbox (habilitar/desabilitar AttackArea.monitoring em tempos específicos).
  • Disparar eventos com precisão (chamar função via Call Method Track, tocar som, emitir partículas).

Passo a passo: ataque com janela de acerto usando AnimationPlayer

1) Criar animação de ataque no AnimationPlayer

Selecione o nó AnimationPlayer e crie uma animação chamada attack_fx (nome livre). Nessa animação, adicione tracks:

  • Property Track para AttackArea:monitoring (ou AttackArea/CollisionShape2D:disabled).
  • Defina monitoring = true apenas durante a janela em que o golpe deve acertar (ex.: do 0.10s ao 0.25s) e false fora dela.
  • (Opcional) Property Track para AnimatedSprite2D:modulate ou position para dar impacto.
  • (Opcional) Call Method Track para chamar uma função como spawn_slash_effect() no tempo certo.

Exemplo de ideia de timing:

TempoAção
0.00smonitoring = false
0.10smonitoring = true (começa a acertar)
0.25smonitoring = false (para de acertar)

2) Disparar AnimatedSprite2D e AnimationPlayer juntos

No ataque, você toca a animação de frames (attack) e a animação de efeitos/timing (attack_fx):

func try_attack() -> void:    if is_attacking:        return    is_attacking = true    play_anim_if_changed(sprite, "attack")    fx.play("attack_fx")

Para encerrar o estado de ataque, você pode continuar usando o animation_finished do sprite (fim visual) ou o sinal do AnimationPlayer (fim do timing). Se quiser que o ataque termine quando o timing acabar:

func _ready() -> void:    fx.animation_finished.connect(_on_fx_finished)    sprite.animation_finished.connect(_on_sprite_animation_finished)    attack_area.monitoring = false
func _on_fx_finished(anim_name: StringName) -> void:    if anim_name == "attack_fx":        # garante que a hitbox desligue mesmo se algo interromper        attack_area.monitoring = false

Você pode escolher uma única fonte de verdade para encerrar is_attacking. Em geral, é mais simples encerrar pelo fim do attack (sprite), e usar o AnimationPlayer apenas para a janela de hit.

Inimigo: troca de animações (idle/walk/hit) com prioridade

1) Conceito: “hit” interrompe o resto

Para o inimigo, a lógica costuma ser:

  • Se está em hit (tomando dano), toca hit e não troca para walk até acabar.
  • Senão, se está se movendo, walk; caso contrário, idle.

2) Exemplo de script de animação do inimigo

@onready var sprite: AnimatedSprite2D = $AnimatedSprite2Dvar is_hit: bool = falsefunc update_animation() -> void:    if is_hit:        play_anim_if_changed(sprite, "hit")        return    if abs(velocity.x) > 1.0:        play_anim_if_changed(sprite, "walk")    else:        play_anim_if_changed(sprite, "idle")func take_hit() -> void:    if is_hit:        return    is_hit = true    play_anim_if_changed(sprite, "hit")func _ready() -> void:    sprite.animation_finished.connect(_on_sprite_animation_finished)func _on_sprite_animation_finished() -> void:    if sprite.animation == "hit":        is_hit = false

Se o inimigo não usa velocity (por exemplo, movimenta por outro método), substitua o critério por uma variável is_moving ou pela direção atual.

Checklist de cuidados para animações estáveis

  • Não reinicie animações: sempre compare sprite.animation antes de chamar play().
  • Defina prioridades com return (ataque/hit > ar > chão).
  • Evite loops em animações que precisam terminar (attack, hit, muitas vezes jump).
  • Separe responsabilidades: uma função para direção (flip), outra para animação.
  • Use AnimationPlayer para timing de hitbox e efeitos; use AnimatedSprite2D para frames.
  • Garanta desligamentos: ao final de um ataque, assegure que a hitbox volte a false (especialmente se o ataque puder ser interrompido).

Agora responda o exercício sobre o conteúdo:

Qual abordagem é mais adequada para controlar a “janela de acerto” de um ataque (ligar/desligar a hitbox em tempos específicos) e sincronizar efeitos com precisão em um jogo 2D na Godot?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

O AnimationPlayer é indicado para animar propriedades e controlar timing com precisão, como habilitar/desabilitar a hitbox em instantes específicos. O AnimatedSprite2D fica responsável pela animação por frames.

Próximo capitúlo

Godot do Zero: Inimigos simples com patrulha e detecção do jogador

Arrow Right Icon
Capa do Ebook gratuito Godot do Zero: Criando seu Primeiro Jogo 2D com GDScript
47%

Godot do Zero: Criando seu Primeiro Jogo 2D com GDScript

Novo curso

17 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.