Voltar para o Blog
Quest Log

Sombra de sprite 2D na Godot 4: fake shadow facil

Personagem pixel art com uma sombra eliptica achatada e semitransparente no chao embaixo dos pes

Como dar sombra a um sprite 2D na Godot 4 com um blob shadow eliptico, modulate semitransparente e Tween tipado que encolhe e cresce quando o personagem pula.

Ferramenta:Godot

Um personagem que anda em cima do cenário sem nada embaixo dos pés parece recortado e colado por cima da tela, como um adesivo. O olho não acha onde ele toca o chão, e num pulo fica pior ainda: o sprite sobe e desce, mas nada ancora ele ao solo, então parece que flutua. Esse é um dos defeitos de acabamento mais comuns em protótipo 2D, e a correção é mais barata do que parece. Dar uma sombra de sprite 2D na Godot 4 com a técnica de fake shadow (ou blob shadow) resolve a sensação de profundidade em poucos minutos, sem tocar em sistema de iluminação nenhum.

Neste tutorial você vai montar uma cena com um personagem e um nó de sombra, desenhar a sombra como uma elipse achatada e semitransparente, fazer ela acompanhar o personagem e, o passo que mais engana o olho, fazer a sombra encolher quando o personagem pula e crescer quando ele cai. No fim tem a conversa honesta sobre quando a fake shadow basta e quando vale subir para uma Light2D de verdade. Todo o código é GDScript do Godot 4.x e tipado.

O que é uma fake shadow (blob shadow)

Fake shadow é uma sombra que você não calcula: você desenha. Em vez de pedir para o motor projetar a silhueta do personagem a partir de uma fonte de luz, você coloca embaixo dos pés um borrão escuro, quase sempre uma elipse achatada, e deixa ele ali. O cérebro do jogador faz o resto: qualquer mancha escura no chão, logo abaixo de um objeto, lê como sombra e ancora o objeto ao solo.

Esse borrão é chamado de blob shadow justamente porque não tem forma definida, é só uma bolha. E é exatamente por não ter forma que ele é tão útil: você não precisa que a sombra combine com a pose do personagem. A mesma elipse serve para o personagem parado, correndo ou atacando. O custo é um sprite a mais na tela, nada de luz dinâmica, nada de oclusor, nada que pese no celular mais fraco.

Para top-down e plataforma, isso quase sempre é o suficiente. Jogos comerciais inteiros usam blob shadow o tempo todo porque o jogador não está olhando para a precisão da sombra, ele está olhando para o personagem. A sombra só precisa existir e reagir à altura.

Montando a cena: personagem mais nó de sombra

A estrutura é simples. O personagem é um CharacterBody2D (ou Node2D, se você ainda não tem física), e a sombra é um Sprite2D que fica como filho dele. Colocar a sombra como filho resolve metade do trabalho de uma vez: ela já acompanha a posição do personagem no plano do chão, porque herda a transformação do pai.

A árvore fica assim:

Personagem (CharacterBody2D)
├── Sombra (Sprite2D)
└── Corpo (Sprite2D ou AnimatedSprite2D)

Repare na ordem: a Sombra vem antes do Corpo na árvore. Na Godot 2D, nós irmãos desenham na ordem da árvore, então o que vem primeiro fica atrás. A sombra tem que ficar embaixo do personagem, nunca por cima, senão vira uma mancha no peito dele.

A textura da sombra pode ser um PNG de uma elipse preta borrada que você exporta do seu editor de imagem, mas dá para começar sem arte nenhuma: use qualquer círculo branco e trate cor, achatamento e transparência no código via modulate e scale. É isso que deixa a técnica barata, você reaproveita uma textura genérica e ajusta tudo por propriedade.

O script da sombra, tipado

O nó de sombra quase não tem lógica própria no estado parado. O trabalho dele é nascer achatado, escuro e semitransparente. Vamos guardar a escala de repouso para usar como referência quando o personagem pular.

class_name SombraSprite
extends Sprite2D

## Escala da sombra quando o personagem esta no chao.
@export var escala_base: Vector2 = Vector2(1.0, 0.4)
## Transparencia da sombra no chao (0 a 1).
@export var alpha_base: float = 0.35

func _ready() -> void:
    scale = escala_base
    modulate = Color(0.0, 0.0, 0.0, alpha_base)

Três coisas acontecem aqui. O scale com y menor que x achata o círculo numa elipse, que é o formato que o olho espera de uma sombra vista meio de cima. O modulate com os três primeiros canais em zero pinta a textura de preto, não importa a cor original dela, e o quarto canal (o alpha) deixa ela semitransparente para o chão aparecer por baixo. Deixar escala_base e alpha_base exportados permite ajustar tudo no Inspector sem recompilar a cabeça.

Um detalhe que engana muita gente: a sombra não deve ser preto puro e opaco. Uma sombra 100% preta e sólida parece um buraco no chão, não uma sombra. O alpha em torno de 0.35 deixa ela presente mas leve, e é por isso que o modulate cuida da transparência junto com a cor.

Ancorando a sombra aos pés

Como a sombra é filha do personagem, ela já segue ele no eixo horizontal de graça. O que falta é deslocar ela para baixo, até os pés. Se a origem do sprite do personagem está no centro do corpo, a sombra precisa descer até a base. Isso é só a posição local do nó de sombra:

# No Inspector ou no _ready do personagem:
# Sombra.position = Vector2(0, 24)

O valor de y depende da altura do seu sprite. A regra prática: a sombra fica onde os pés tocam o chão quando o personagem está parado. Com isso resolvido no estado parado, o personagem anda pelo cenário e a sombra vai junto, sempre colada no solo. Já some a sensação de adesivo.

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

O pulo: fazer a sombra encolher e crescer

Aqui está o truque que separa uma sombra convincente de uma sombra que estraga o efeito. Quando o personagem pula, se a sombra subir junto com ele, o olho perde a referência do chão na hora e tudo volta a flutuar. A sombra tem que ficar no chão e mudar de tamanho conforme a altura.

Para isso, pare de pensar no pulo como posição e comece a pensar nele como altura. A posição do personagem no mundo continua sendo a posição no chão (onde a sombra mora). A altura do pulo é um valor à parte, que você usa só para levantar o sprite do corpo e para encolher a sombra. Quanto mais alto o personagem, menor e mais fraca a sombra, porque a luz imaginária que vem de cima pega uma área maior e mais difusa.

A forma mais limpa de amarrar sombra e altura é por um único valor normalizado: 0 no chão, 1 no ponto mais alto do pulo. A sombra interpola a escala e o alpha entre o repouso e um mínimo:

class_name SombraSprite
extends Sprite2D

@export var escala_base: Vector2 = Vector2(1.0, 0.4)
@export var alpha_base: float = 0.35
## Fracao da escala no ponto mais alto do pulo.
@export var escala_minima: float = 0.6
## Fracao do alpha no ponto mais alto do pulo.
@export var alpha_minimo: float = 0.5

func _ready() -> void:
    atualizar_por_altura(0.0)

## altura_normalizada: 0.0 no chao, 1.0 no topo do pulo.
func atualizar_por_altura(altura_normalizada: float) -> void:
    var t: float = clampf(altura_normalizada, 0.0, 1.0)
    var fator_escala: float = lerpf(1.0, escala_minima, t)
    var fator_alpha: float = lerpf(1.0, alpha_minimo, t)
    scale = escala_base * fator_escala
    modulate = Color(0.0, 0.0, 0.0, alpha_base * fator_alpha)

A função atualizar_por_altura() recebe um número entre 0 e 1 e deixa a sombra proporcional. O clampf garante que valores fora da faixa não quebrem o efeito, e o lerpf faz a interpolação entre o repouso (fator 1.0) e o topo (escala_minima / alpha_minimo). Repare que nada aqui mexe na posição da sombra: ela fica grudada no chão enquanto só o tamanho muda.

Ligando o pulo do personagem à sombra

Agora o personagem precisa calcular a altura normalizada e passar para a sombra todo frame. Vamos fazer um pulo simples por código, com uma altura própria que sobe e desce, separada do movimento no chão:

class_name Personagem
extends CharacterBody2D

@export var altura_maxima: float = 48.0
@export var duracao_pulo: float = 0.5

@onready var sombra: SombraSprite = $Sombra
@onready var corpo: Sprite2D = $Corpo

var _pulando: bool = false
var _tempo_pulo: float = 0.0

func _process(delta: float) -> void:
    if _pulando:
        _tempo_pulo += delta
        var t: float = _tempo_pulo / duracao_pulo
        if t >= 1.0:
            _encerrar_pulo()
            return
        # Curva de parabola: 0 no chao, 1 no topo, 0 de novo na queda.
        var altura_norm: float = sin(t * PI)
        var altura_px: float = altura_norm * altura_maxima
        corpo.position.y = -altura_px
        sombra.atualizar_por_altura(altura_norm)

func pular() -> void:
    if _pulando:
        return
    _pulando = true
    _tempo_pulo = 0.0

func _encerrar_pulo() -> void:
    _pulando = false
    corpo.position.y = 0.0
    sombra.atualizar_por_altura(0.0)

O sin(t * PI) dá a curva do pulo de graça: começa em 0, chega a 1 na metade do tempo (o ponto mais alto) e volta a 0 na aterrissagem. Esse mesmo valor sobe o corpo (deslocando o y dele para cima) e encolhe a sombra, porque os dois recebem a mesma altura_norm. O resultado é a leitura que o olho quer: o personagem sobe, a sombra fica parada no chão e some de tamanho, e quando ele cai a sombra cresce de volta e confirma o pouso. Chame pular() no seu input de salto.

Se você prefere disparar o efeito com um Tween tipado em vez de atualizar frame a frame, dá para animar a sombra direto no início e no fim do salto. Isso é útil quando o pulo não é por código manual e você só quer o feedback visual da sombra:

func animar_sombra_pulo() -> void:
    var tween: Tween = create_tween()
    tween.set_trans(Tween.TRANS_SINE)
    # Encolhe ate o topo.
    tween.tween_method(sombra.atualizar_por_altura, 0.0, 1.0, duracao_pulo * 0.5)
    # Cresce de volta na queda.
    tween.tween_method(sombra.atualizar_por_altura, 1.0, 0.0, duracao_pulo * 0.5)

O tween_method chama atualizar_por_altura() com valores interpolados de 0 a 1 e depois de 1 a 0, que é exatamente a faixa que a função espera. Se você já usa Tween para outras partes do game feel, como no post sobre animar com Tween por código, essa é a forma mais consistente de encaixar a sombra no mesmo fluxo.

Quando usar fake shadow e quando usar Light2D

A fake shadow não é um atalho preguiçoso: na maior parte dos jogos 2D ela é a escolha certa, não a escolha barata. Ela é previsível, custa quase nada, roda em qualquer máquina e você controla 100% da aparência. Para top-down, plataforma, roguelike, a sombra não precisa ser fisicamente correta, ela precisa ancorar o personagem e reagir ao pulo. O blob resolve.

A sombra real com PointLight2D mais LightOccluder2D só compensa quando a direção e a posição da luz são parte da jogabilidade ou da estética. Um jogo de stealth onde o jogador se esconde no escuro, uma cena noturna com tochas que lançam sombras longas conforme o personagem passa, uma fogueira que projeta a silhueta dele na parede: aí a sombra tem que ser calculada, porque ela muda conforme a luz, e isso o blob não faz. Se o seu projeto já usa iluminação dinâmica, vale ler como funciona o sistema de luz no post sobre iluminação na Godot antes de decidir.

O custo da sombra real é esse: cada oclusor é geometria a mais que a luz precisa processar, e com muitos personagens em cena isso pesa, principalmente em celular. Por isso é comum ver jogos com iluminação dinâmica bonita no cenário mas usando blob shadow nos personagens, o melhor dos dois mundos. Não há vergonha nenhuma em misturar.

Encaixando no acabamento do personagem

A sombra é uma peça de um conjunto de pequenos acabamentos que fazem o personagem parecer vivo no cenário em vez de colado por cima. Ela combina bem com o espelhamento do sprite quando o personagem muda de direção, que você resolve com o flip do sprite por direção, e com a animação de quadros do corpo. Se o seu personagem ainda está parado, o próximo passo natural é dar movimento a ele seguindo o guia de animação de sprite 2D no jogo, e aí a sombra parada embaixo de um corpo que anda e pula fecha a sensação de peso.

O ganho de uma sombra bem feita é desproporcional ao esforço. São poucas linhas, uma textura genérica e um valor de altura compartilhado, e o personagem para de flutuar. É o tipo de acabamento que o jogador não percebe quando está lá, mas percebe na hora quando falta.

Resumo rápido

A fake shadow é um Sprite2D filho do personagem, achatado por scale, pintado de preto e semitransparente por modulate, posicionado nos pés. No pulo, você trata a altura como um valor normalizado de 0 a 1 e usa ele tanto para levantar o corpo quanto para encolher e clarear a sombra, mantendo ela grudada no chão. Para top-down e plataforma isso basta e sobra; a Light2D com oclusor fica reservada para quando a própria luz é parte do jogo. Comece pelo blob, meça o quanto já melhora, e só suba para sombra real se o projeto realmente pedir.

Perguntas frequentes

Como dar sombra a um sprite 2D na Godot 4?

A forma mais barata e um blob shadow: um Sprite2D com uma textura eliptica achatada, posicionado embaixo dos pes do personagem, com modulate preto e alpha baixo. Ele fica num no filho que acompanha a posicao do personagem e nao precisa de luz nenhuma.

Qual a diferenca entre fake shadow e sombra real com Light2D?

A fake shadow e um sprite desenhado a mao que voce cola no chao, sem calculo de luz. A sombra real vem de um PointLight2D com LightOccluder2D, que projeta a sombra conforme a posicao da luz. A fake e mais barata e suficiente para top-down e plataforma; a real so compensa quando a direcao da luz importa para o jogo.

Como fazer a sombra encolher quando o personagem pula?

Trate a altura do pulo como um valor separado da posicao no chao. Quanto maior a altura, menor a escala da sombra e menor o alpha dela. Da para animar isso com um Tween tipado no inicio e no fim do pulo, ou atualizar a escala a cada frame seguindo a altura atual.