Sombra de sprite 2D na Godot 4: fake shadow facil
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.
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.
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.

