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) └─ CollisionShape2DAnimatedSprite2D 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:
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
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
idleerun). Emjump,falleattack, 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.
idleewalk: geralmente em loop.hit: geralmente sem loop (toca uma vez e volta paraidle/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) oufall(descendo). - No chão:
runse estiver se movendo, senãoidle.
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 = false3) 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.0evita “tremedeira” quando a velocidade fica muito pequena. - O
returndeixa 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 = trueAssim, 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
attackuma 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 = falsefunc 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 = falseCuidados:
- Não chame
try_attack()continuamente enquanto o botão estiver pressionado; prefira um evento de “apertou” (ex.:is_action_just_pressedno seu código de entrada já existente). - Se a animação
attackestiver em loop, o sinal não vai encerrar como esperado. Garanta queattacknã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.apara fade,position.xpara recoil,scalepara squash/stretch). - Controlar janelas de hitbox (habilitar/desabilitar
AttackArea.monitoringem 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(ouAttackArea/CollisionShape2D:disabled). - Defina
monitoring = trueapenas durante a janela em que o golpe deve acertar (ex.: do 0.10s ao 0.25s) efalsefora dela. - (Opcional) Property Track para
AnimatedSprite2D:modulateoupositionpara dar impacto. - (Opcional) Call Method Track para chamar uma função como
spawn_slash_effect()no tempo certo.
Exemplo de ideia de timing:
| Tempo | Ação |
|---|---|
| 0.00s | monitoring = false |
| 0.10s | monitoring = true (começa a acertar) |
| 0.25s | monitoring = 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 = falsefunc _on_fx_finished(anim_name: StringName) -> void: if anim_name == "attack_fx": # garante que a hitbox desligue mesmo se algo interromper attack_area.monitoring = falseVocê 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
hite não troca parawalkaté 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 = falseSe 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.animationantes de chamarplay(). - Defina prioridades com
return(ataque/hit > ar > chão). - Evite loops em animações que precisam terminar (
attack,hit, muitas vezesjump). - 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).