Voltar para o Blog
Quest Log

Pixel Art Jump Animation: Subida, Ápice e Queda

Quatro poses de um personagem em pixel art pulando: agachado, subindo esticado, encolhido no alto e caindo

Pixel art jump animation: antecipação, subida, ápice, queda e aterrissagem com squash and stretch, e a engine trocando frames pela velocidade vertical.

Ferramenta:Aseprite

Pixel Art Jump Animation: Subida, Ápice e Queda

Uma pixel art jump animation não é uma animação só: é um conjunto de poses curtas (antecipação, subida, ápice, queda e aterrissagem) que a engine troca de acordo com a velocidade vertical do personagem. Desenhar o pulo como uma sequência fixa de 8 frames funciona no editor e quebra no jogo, porque o tempo no ar muda a cada pulo.

Aqui você vai desenhar cada parte do pulo em 32x32 no Aseprite, aplicar squash and stretch em pixel e ligar tudo à física: com código C# para Unity 6.3 e GDScript para Godot 4 escolhendo o frame pela velocidade vertical.

Por que o pulo não é uma animação só

Num jogo de plataforma bom, o pulo tem altura variável: segurar o botão vai mais alto, soltar cedo corta a subida. Cair de uma plataforma alta dura o dobro de cair de um degrau. Bater a cabeça no teto interrompe a subida no meio. O post de pulo com coyote time e jump buffer mostra como o código faz isso.

Se a animação de pulo tem 8 frames a 80 ms, ela dura 640 ms. Num pulo curto de 400 ms, o personagem pousa ainda com a pose de subida. Numa queda de 1,2 segundo, ele termina a animação no ar e congela no último frame, ou pior, volta ao primeiro em loop.

A solução é desenhar estados, não uma sequência:

ParteQuando tocaFramesLoop
AntecipaçãoBotão apertado, ainda no chão1Não
SubidaVelocidade vertical para cima, alta1 a 2Sim, se 2
ÁpiceVelocidade vertical perto de zero1Não
QuedaVelocidade vertical para baixo1 a 2Sim, se 2
AterrissagemTocou o chão vindo do ar1 a 2Não

No Aseprite, cada parte vira uma tag: jump_anticipation, jump_up, jump_apex, fall, land. O post de tags de animação no Aseprite explica como nomear para a exportação.

Pixel art jump animation: antecipação e subida

Antecipação. O personagem agacha antes de sair do chão. Corpo 2 pixels mais baixo que a idle, joelhos dobrados, braços para trás. É o frame de squash: o personagem fica mais largo e mais baixo.

Cuidado com o tempo: em plataforma, o pulo precisa sair no mesmo frame em que o botão é apertado. A antecipação não pode atrasar a física. Duas soluções: tocar a antecipação por 1 frame de jogo (cerca de 16 ms a 60 FPS), só para o olho registrar, enquanto o personagem já sobe; ou desenhar a antecipação e usá-la só quando o personagem pousa e pula de novo em seguida.

Subida. O corpo estica: pernas juntas e esticadas para baixo, braços para cima ou junto ao corpo, cabeça erguida. É o frame de stretch: 1 a 2 pixels mais alto e 1 pixel mais estreito que a idle. Se usar 2 frames, o segundo pode ter uma diferença pequena (cabelo ou capa atrasados para baixo) para dar vida em subidas longas.

Ápice

No ponto mais alto, a velocidade vertical chega a zero. O corpo encolhe: joelhos sobem em direção ao peito, braços abrem para equilibrar. É a pose mais "solta" do pulo, e a que comunica o momento de suspensão.

O ápice não deve durar só o instante exato de velocidade zero, senão ninguém vê. Ele toca enquanto a velocidade vertical está numa faixa perto de zero, por exemplo entre +1,5 e -1,5 unidades por segundo. É essa faixa que o código usa logo abaixo.

Queda e aterrissagem

Queda. As pernas descem e se afastam, prontas para pousar. Braços para cima, como quem se segura no ar. Cabelo e capa vão para cima (o ar empurra). Se usar 2 frames em loop alternando o cabelo ou a capa, a queda longa fica viva.

Aterrissagem. O squash mais forte: corpo 2 a 3 pixels mais baixo, joelhos bem dobrados, pés afastados. Dura pouco (60 a 100 ms) e volta para a idle ou para a corrida. Em queda alta, um segundo frame de recuperação vende o impacto. Em jogos com movimento rápido, deixe a aterrissagem ser interrompida pelo input: se o jogador já está correndo, ele não deve esperar o fim do frame.

Desenhe as cinco poses no mesmo canvas, com a linha do chão numa camada guia, e ligue o onion skin para comparar o volume entre elas.

Squash and stretch em pixel

O princípio vem da animação tradicional: um objeto que comprime fica mais largo, um objeto que estica fica mais fino, e o volume total parece o mesmo. Em pixel art, a regra se traduz em contas pequenas:

  • Squash (antecipação, aterrissagem): altura do corpo menos 2 pixels, largura mais 1 ou 2.
  • Stretch (subida, queda rápida): altura mais 1 ou 2 pixels, largura menos 1.
  • Cabeça intacta. Em 32x32, deformar a cabeça destrói o rosto. Aplique o squash e o stretch no tronco e nas pernas.

Duas formas de fazer: redesenhar a pose (melhor resultado, mais trabalho) ou aplicar na engine, escalando o sprite em Y e X por um instante. A segunda é rápida mas deforma os pixels, criando pixels de tamanhos diferentes; em pixel art, prefira desenhar.

Até aqui o pulo existe como arte. Para ele parecer pulo de verdade, o código precisa escolher a pose certa no momento certo, e isso é a parte que separa um protótipo de um personagem gostoso de controlar.

Curso de Arte 2D
Quer fazer a arte do seu jogo?

No curso de Arte 2D para Jogos da CursoGame.Dev você vai do rabisco ao portfólio: personagens, cenários, tilesets, UI e animação, sem precisar saber desenhar.

Conhecer o curso de Arte 2D
Suporte no WhatsAppAcesso vitalícioGarantia de 7 dias

Desenhar o personagem e programar o movimento dele como coisas separadas é o motivo de tanto pulo "estranho" em jogo independente. Produzir as poses já sabendo como a engine vai escolhê-las encurta o caminho até o personagem que você imaginou.

Trocar frame pela velocidade vertical na engine

Unity 6.3. No Animator, crie os estados com os nomes das tags (idle, jump_up, jump_apex, fall, land), cada um com o clipe correspondente. O script lê a velocidade do Rigidbody2D e toca o estado certo. Repare no linearVelocity: no Unity 6, velocity foi substituído por ele.

Arquivo PlayerJumpAnimation.cs:

using UnityEngine;

public class PlayerJumpAnimation : MonoBehaviour
{
    [SerializeField] private Rigidbody2D rb;
    [SerializeField] private Animator animator;
    [SerializeField] private Transform groundCheck;
    [SerializeField] private float groundCheckRadius = 0.1f;
    [SerializeField] private LayerMask groundLayer;
    [SerializeField] private float apexThreshold = 1.5f;
    [SerializeField] private float landDuration = 0.1f;

    private static readonly int IdleState = Animator.StringToHash("idle");
    private static readonly int JumpUpState = Animator.StringToHash("jump_up");
    private static readonly int JumpApexState = Animator.StringToHash("jump_apex");
    private static readonly int FallState = Animator.StringToHash("fall");
    private static readonly int LandState = Animator.StringToHash("land");

    private int currentState;
    private bool wasGrounded = true;
    private float landTimer;

    private void Update()
    {
        bool grounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer) != null;
        float verticalSpeed = rb.linearVelocity.y;

        if (grounded && !wasGrounded)
        {
            landTimer = landDuration;
        }
        wasGrounded = grounded;

        int nextState;
        if (!grounded)
        {
            if (verticalSpeed > apexThreshold)
            {
                nextState = JumpUpState;
            }
            else if (verticalSpeed < -apexThreshold)
            {
                nextState = FallState;
            }
            else
            {
                nextState = JumpApexState;
            }
        }
        else if (landTimer > 0f)
        {
            landTimer -= Time.deltaTime;
            nextState = LandState;
        }
        else
        {
            nextState = IdleState;
        }

        if (nextState != currentState)
        {
            animator.Play(nextState);
            currentState = nextState;
        }
    }
}

O script só chama Play quando o estado muda, para não reiniciar o clipe a cada frame. No seu jogo, o IdleState do chão é substituído pela sua lógica de locomoção (idle, walk, run). O apexThreshold em 1,5 unidade por segundo é um ponto de partida; aumente se o ápice passar rápido demais para ser visto.

Godot 4. Com AnimatedSprite2D, a lógica é a mesma, lembrando que na Godot o eixo Y aponta para baixo: velocidade negativa é subida.

extends CharacterBody2D

const APEX_THRESHOLD: float = 60.0

@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D

func _update_air_animation() -> void:
    if is_on_floor():
        return
    if velocity.y < -APEX_THRESHOLD:
        sprite.play("jump_up")
    elif velocity.y > APEX_THRESHOLD:
        sprite.play("fall")
    else:
        sprite.play("jump_apex")

Chame _update_air_animation() no fim do _physics_process, depois do move_and_slide(). Chamar play com a animação que já está tocando não a reinicia, então não precisa guardar estado. O limite está em pixels por segundo; 60 é um bom começo para personagens de 32 pixels.

Testar o pulo antes de desenhar tudo

Antes de caprichar nos detalhes, faça uma versão rascunho das cinco poses só com a silhueta, numa cor chapada, e leve para a engine. Pule de lugares diferentes: do chão para uma plataforma baixa, de uma plataforma alta para o chão, um toque rápido no botão e um pulo segurado até o fim. Observe três coisas:

  1. O ápice aparece em todos os pulos? Se num pulo curto ele some, aumente a faixa do limite.
  2. A queda longa fica viva? Se o personagem parece congelado caindo, a queda precisa de um segundo frame em loop ou de um detalhe secundário (cabelo, capa) se mexendo.
  3. A aterrissagem lê sem atrasar? Se você sente o personagem "grudar" no chão, encurte o frame ou deixe a corrida interromper.

Só depois desse teste vale desenhar o acabamento, com cor, sombra e luz. Ajustar uma silhueta leva um minuto; refazer uma pose finalizada porque o código pede outra leitura leva uma tarde. Esse rascunho também mostra se você precisa de poses extras, como a de pulo duplo (uma cambalhota de 3 ou 4 frames) ou a de deslizar na parede.

Erros comuns

Pulo como animação única. Pose errada em pulo curto ou longo. Estados separados pela velocidade.

Antecipação atrasando o pulo. O jogador sente atraso no controle. A física sai no frame do botão; a antecipação é só visual.

Ápice invisível. O estado só toca com velocidade exatamente zero. Use uma faixa.

Aterrissagem que trava o personagem. Esperar o frame de pouso terminar para andar irrita. Deixe o input interromper.

Squash na cabeça. O rosto deforma. Comprima tronco e pernas.

Escala do sprite na engine em pixel art. Pixels de tamanhos diferentes. Desenhe as poses.

Queda sem loop. Em queda longa, o último frame congela. Loop de 2 frames ou frame único estável.

Próximo passo

Desenhe as cinco poses, teste com pulos de alturas diferentes e ajuste a faixa do ápice até ele aparecer em todo pulo. Depois, una com a parte de controle: aceleração no ar e altura variável estão no post de movimento de personagem 2D de plataforma.

Perguntas frequentes

Quantos frames tem uma animação de pulo em pixel art?

Um pulo completo costuma ter de 4 a 8 frames divididos em partes: 1 de antecipação, 1 ou 2 de subida, 1 de ápice, 1 ou 2 de queda e 1 ou 2 de aterrissagem. Cada parte é uma animação curta separada que a engine escolhe pela situação.

Por que não fazer o pulo como uma animação só?

Porque a duração do pulo muda: pulo curto quando o jogador solta o botão cedo, queda longa de uma plataforma alta, cabeçada no teto. Uma animação de duração fixa nunca bate com a física. Separando em partes, a engine mostra a pose certa para a velocidade vertical do momento.

O que é squash and stretch em pixel art?

É achatar o personagem quando ele comprime (antecipação e aterrissagem) e alongar quando ele se move rápido (subida e queda), mantendo o volume. Em 32x32, isso é 1 ou 2 pixels a menos na altura e a mais na largura, ou o contrário.

Como trocar a animação de pulo pela velocidade na Unity?

Leia a velocidade vertical do Rigidbody2D, que no Unity 6 é rb.linearVelocity.y, e escolha o estado: acima de um limite positivo, subida; abaixo do negativo, queda; entre os dois, ápice. Toque o estado com Animator.Play só quando ele mudar, ou use parâmetros e transições no Animator.