Godot AnimatedSprite2D: Animar Sprites no Editor e Código
Guia do nó AnimatedSprite2D na Godot 4: crie o SpriteFrames, monte idle, walk e jump de um spritesheet, ajuste FPS e loop e controle tudo por GDScript.
O AnimatedSprite2D é o atalho mais direto da Godot 4 para dar vida a um personagem 2D. Você joga um spritesheet dentro dele, separa os frames em animações nomeadas (idle, walk, jump) e chama cada uma por código com uma linha. Nada de timeline complexa, nada de interpolação manual: o nó troca os frames no ritmo que você definir e repete em loop sozinho. É o primeiro nó de animação que todo mundo aprende, e para boa parte dos jogos ele resolve o personagem inteiro.
Neste guia eu monto o fluxo completo: o que o AnimatedSprite2D faz e quando ele é melhor que o AnimationPlayer, como criar o recurso SpriteFrames, como fatiar um spritesheet em idle, walk e jump, como ajustar FPS e loop, e como controlar tudo por GDScript com play, stop e o sinal animation_finished. No fim eu amarro as animações a uma máquina de estados simples, que é como isso aparece num jogo de verdade. Todo o código é GDScript da Godot 4.
O que é o AnimatedSprite2D e quando usar
O AnimatedSprite2D é um nó que exibe uma sequência de imagens, uma de cada vez, trocando no tempo. Ele guarda essas sequências num recurso chamado SpriteFrames, que é basicamente um dicionário: cada animação tem um nome, uma lista de frames, um FPS e uma flag de loop. Você monta esse recurso uma vez no editor e o nó cuida da reprodução.
A dúvida mais comum é quando usar o AnimatedSprite2D e quando usar o AnimationPlayer. O critério prático é curto. Use o AnimatedSprite2D quando a única coisa que anima é o frame do sprite: protótipo, inimigo simples, item de cenário, personagem sem hitbox sincronizada. Ele é mais rápido de montar e deixa a lógica de troca de animação legível no script.
Use o AnimationPlayer quando a animação precisa de mais do que trocar frame. Som de passo no frame exato em que o pé toca o chão, uma hitbox que liga só nos frames em que a espada cruza o ar, a câmera tremendo no impacto: tudo isso vive na mesma timeline do AnimationPlayer e fica sincronizado por padrão. No AnimatedSprite2D você teria que resolver essas coisas no código, com timer, e o timer dessincroniza na primeira vez que alguém mexe na duração da animação.
Resumo honesto: comece pelo AnimatedSprite2D. No dia em que precisar sincronizar áudio e colisão com os frames (e num jogo de ação esse dia chega), você migra o personagem para o AnimationPlayer. Até lá, o AnimatedSprite2D entrega o resultado com muito menos atrito.
Criando o recurso SpriteFrames
Adicione um nó AnimatedSprite2D na cena do personagem. Selecione ele e repare no Inspector: a propriedade Sprite Frames está vazia, com o aviso <empty>. Clique nela e escolha New SpriteFrames. Clique de novo no recurso criado e o painel SpriteFrames abre na parte de baixo do editor.
Esse painel é onde o trabalho acontece. Do lado esquerdo fica a lista de animações (começa com uma animação chamada default). Do lado direito ficam os frames da animação selecionada, além dos controles de FPS e loop. É tudo que você precisa.
Antes de importar os frames, um ajuste que economiza dor de cabeça com pixel art. A Godot 4 aplica filtro linear nas texturas por padrão, o que deixa o sprite borrado quando ele aparece grande na tela. Vá em Project Settings > Rendering > Textures e mude o Default Texture Filter para Nearest. Isso vale para o projeto inteiro e preserva a borda dura do pixel.
Adicionando animações a partir de um spritesheet
Com o SpriteFrames aberto, vamos fatiar um spritesheet em três animações: idle, walk e jump. Se você ainda não tem um spritesheet pronto, o fluxo de desenhar e exportar está no guia de exportar spritesheet do Aseprite para a Godot.
Primeiro renomeie a animação default para idle. Dê um duplo clique no nome e troque. Agora, com idle selecionada, clique no ícone Add frames from a Sprite Sheet (o botão com uma grade, ao lado dos botões de adicionar frame). Escolha o arquivo do spritesheet.
Abre um diálogo de fatiamento. Aqui você diz à Godot como a imagem está dividida:
- Em Horizontal e Vertical, informe quantas colunas e quantas linhas a grade tem. Um spritesheet de 4 frames em linha é Horizontal = 4, Vertical = 1.
- O diálogo mostra os frames com um contorno. Clique nos frames que pertencem ao idle (ou arraste para selecionar vários em sequência).
- Clique em Add N Frame(s) e eles entram na animação.
Repita para as outras animações. Clique no botão Add Animation (o ícone de folha em branco na lista de animações), nomeie como walk, e importe os frames de caminhada do mesmo spritesheet. Faça o mesmo para jump. No fim você tem três animações nomeadas dentro do mesmo recurso SpriteFrames, cada uma com seus próprios frames.
Se preferir montar cada animação a partir de arquivos de imagem separados (um PNG por frame), o botão Add frames from file aceita vários de uma vez e você seleciona na ordem. O resto do fluxo é igual. Para entender o spritesheet por dentro antes de fatiar, vale a leitura sobre animação de sprite 2D no jogo.
Definindo FPS e loop de cada animação
Cada animação tem dois controles que mudam completamente a sensação do movimento: o FPS e o loop.
O FPS fica no canto direito do painel e define quantos frames por segundo aquela animação toca. Ele é por animação, não global: o idle pode rodar a 6 FPS (respiração lenta, calmo) e o walk a 12 FPS (passo firme). Comece com 8 a 12 para a maioria das ações e ajuste no olho. FPS baixo demais fica travado, FPS alto demais com poucos frames fica nervoso.
O loop é o botão com a seta circular, ao lado do FPS. Ligado, a animação recomeça do primeiro frame quando chega no último, para sempre. Desligado, ela toca uma vez e para no último frame.
A regra que não falha: animações de ciclo ficam em loop, animações de evento ficam sem. Idle e walk são ciclos, deixe em loop. Jump é um evento, deixe sem loop. Isso importa não só pelo visual, mas porque o sinal animation_finished, que a gente vai usar no código, só dispara em animação sem loop. Uma animação em loop nunca "termina", então o sinal nunca vem.
Montar o AnimatedSprite2D é fácil, mas dominar a Godot de ponta a ponta, do movimento à máquina de estados a um jogo publicável, pede método e prática guiada. Se você quer parar de catar tutorial solto no YouTube e aprender Godot de verdade, numa trilha que sai do zero e chega num jogo completo, o curso de Godot da CursoGame.Dev foi feito exatamente para isso.
Tocando animações por código com GDScript
Com o SpriteFrames montado, o script decide qual animação toca e quando. A API do AnimatedSprite2D é pequena e você usa quase sempre os mesmos três ou quatro métodos.
O básico é o play, que recebe o nome da animação:
extends CharacterBody2D
const SPEED: float = 130.0
@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D
func _physics_process(delta: float) -> void:
var direction: float = Input.get_axis("ui_left", "ui_right")
velocity.x = direction * SPEED
move_and_slide()
if direction != 0.0:
sprite.play("walk")
else:
sprite.play("idle")
Repare que play é chamado todo frame e não tem problema: se a animação pedida já está tocando, a Godot não reinicia ela do zero. Isso deixa a lógica de locomoção limpa, sem flags para checar "já estou andando?".
Os outros métodos e propriedades que você vai usar direto:
sprite.play("jump") # toca a animação jump do início
sprite.stop() # para e congela no frame atual
sprite.play("walk") # retoma uma animação
sprite.speed_scale = 2.0 # toca tudo no dobro da velocidade
sprite.frame = 0 # força o frame atual (inteiro)
if sprite.is_playing():
print(sprite.animation) # nome da animação atual (ex.: "walk")
O speed_scale é mais útil do que parece. Um efeito de câmera lenta no personagem pode ser só sprite.speed_scale = 0.5, sem tocar em nenhum frame.
Esperando a animação terminar com animation_finished
Para animações de evento, você quase sempre precisa saber quando elas acabaram para voltar ao estado normal. O sinal animation_finished resolve isso, e com await o código fica linear:
var atacando: bool = false
func atacar() -> void:
if atacando:
return
atacando = true
sprite.play("attack")
await sprite.animation_finished
atacando = false
sprite.play("idle")
Aqui está a pegadinha que eu avisei lá em cima: animation_finished nunca dispara em animação com loop. Se o seu await ficou pendurado para sempre e o personagem travou no meio do ataque, confira se a animação attack não está em loop sem querer no painel SpriteFrames.
Virando o personagem com flip_h
Você não precisa desenhar uma animação de andar para a esquerda e outra para a direita. Desenhe só o personagem virado para um lado e espelhe o nó com a propriedade flip_h. Ela inverte o sprite na horizontal.
func _physics_process(delta: float) -> void:
var direction: float = Input.get_axis("ui_left", "ui_right")
velocity.x = direction * SPEED
move_and_slide()
if direction > 0.0:
sprite.flip_h = false
elif direction < 0.0:
sprite.flip_h = true
if direction != 0.0:
sprite.play("walk")
else:
sprite.play("idle")
O cuidado aqui é só atualizar o flip_h quando há direção. Se você setar flip_h = direction < 0 direto, no momento em que o personagem para (direction == 0) ele vira para a direita de novo, mesmo que estivesse olhando para a esquerda. O if/elif acima mantém a última direção quando o personagem está parado.
Um detalhe que pega iniciante: se você tem uma hitbox ou um ponto de spawn de tiro filho do personagem, o flip_h espelha só o sprite, não os outros nodes. Para espelhar a hitbox junto, você inverte a posição dela por código ou a coloca como filha de um nó que você escala em x. Mas para o visual do personagem em si, flip_h basta.
Amarrando a uma máquina de estados simples
Num jogo de verdade, decidir a animação com if/else solto vira bagunça rápido. Uma máquina de estados simples organiza isso: o personagem está em um estado por vez (IDLE, WALK, JUMP), cada estado sabe qual animação tocar, e as transições ficam explícitas.
Dá para montar uma versão enxuta com um enum, sem framework nenhum:
extends CharacterBody2D
enum Estado { IDLE, WALK, JUMP }
const SPEED: float = 130.0
const JUMP_FORCE: float = -300.0
var estado: Estado = Estado.IDLE
@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity += get_gravity() * delta
var direction: float = Input.get_axis("ui_left", "ui_right")
velocity.x = direction * SPEED
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_FORCE
move_and_slide()
atualizar_flip(direction)
atualizar_estado(direction)
func atualizar_flip(direction: float) -> void:
if direction > 0.0:
sprite.flip_h = false
elif direction < 0.0:
sprite.flip_h = true
func atualizar_estado(direction: float) -> void:
var novo_estado: Estado = estado
if not is_on_floor():
novo_estado = Estado.JUMP
elif direction != 0.0:
novo_estado = Estado.WALK
else:
novo_estado = Estado.IDLE
if novo_estado != estado:
estado = novo_estado
tocar_animacao_do_estado()
func tocar_animacao_do_estado() -> void:
match estado:
Estado.IDLE:
sprite.play("idle")
Estado.WALK:
sprite.play("walk")
Estado.JUMP:
sprite.play("jump")
O truque que mantém isso eficiente está no atualizar_estado: a animação só é trocada quando o estado realmente muda (novo_estado != estado). Sem essa checagem você chamaria play com o mesmo nome todo frame, o que funciona, mas misturar a troca de estado com a reprodução deixa o código mais claro sobre o que aconteceu.
Essa estrutura escala bem. Para adicionar um estado de agachar ou de ataque, você acrescenta o valor no enum, a condição em atualizar_estado e o caso no match. Quando a árvore de estados crescer a ponto de incomodar, o próximo passo é migrar o personagem para o AnimationTree, que monta uma máquina de estados de animação em cima do AnimationPlayer. Mas isso é assunto para quando o AnimatedSprite2D ficar apertado, e por bastante tempo ele não fica.
Fechando
O AnimatedSprite2D é o caminho mais curto entre um spritesheet e um personagem que se mexe na tela. Você cria o SpriteFrames, fatia o spritesheet em idle, walk e jump, ajusta FPS e loop de cada um, e controla tudo com play, stop, flip_h e o sinal animation_finished. Com uma máquina de estados simples por cima, isso já é o esqueleto de locomoção de um platformer.
Para fixar, pega um spritesheet qualquer e monta o ciclo completo: três animações no SpriteFrames, o script de movimento com flip, a máquina de estados do exemplo e um pulo que volta ao idle quando toca o chão. É um exercício de uma tarde e cobre tudo que importa do nó. Quando precisar de som e hitbox sincronizados com os frames, você já sabe o próximo destino: o AnimationPlayer.
Perguntas frequentes
Qual a diferença entre AnimatedSprite2D e AnimationPlayer na Godot?
O AnimatedSprite2D só troca frames de sprite e guarda essas animações num recurso SpriteFrames. O AnimationPlayer anima qualquer propriedade de qualquer node (posição, cor, colisão, áudio) numa timeline. Para um personagem que só precisa trocar de frame, o AnimatedSprite2D é mais rápido de montar. Quando você precisa sincronizar som, hitbox e frame na mesma timeline, vá de AnimationPlayer.
Como deixo uma animação do AnimatedSprite2D em loop?
No painel SpriteFrames, selecione a animação e ligue o botão de loop (o ícone de seta circular). Animações de ciclo como idle e walk ficam em loop. Animações de evento como jump ou attack ficam sem loop, para que o sinal animation_finished dispare quando elas terminam.
Por que meu sprite fica borrado no AnimatedSprite2D?
A Godot 4 usa filtro de textura linear por padrão, o que borra pixel art. Vá em Project Settings > Rendering > Textures e mude o Default Texture Filter para Nearest. Isso corrige o projeto inteiro de uma vez.
Como viro o personagem para o outro lado sem criar animações duplicadas?
Use a propriedade flip_h do AnimatedSprite2D. Em vez de desenhar uma animação de andar para a esquerda e outra para a direita, você desenha uma só e espelha com sprite.flip_h = true quando o personagem anda para o lado oposto.
