Voltar para o Blog
Quest Log

Godot AnimatedSprite2D: Animar Sprites no Editor e Código

Frames de animação de um personagem herói correndo em estilo pixel art, montados em um spritesheet 2D

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.

Ferramenta:Godot

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:

  1. Em Horizontal e Vertical, informe quantas colunas e quantas linhas a grade tem. Um spritesheet de 4 frames em linha é Horizontal = 4, Vertical = 1.
  2. 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).
  3. 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.

Curso de Godot
Quer fazer seu primeiro jogo na Godot?

No curso de Godot da CursoGame.Dev você vai do zero ao primeiro jogo, com um desafio prático em cada level e suporte individual no WhatsApp quando travar.

Conhecer o curso de Godot
Suporte no WhatsAppAcesso vitalícioGarantia de 7 dias

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.