Y-sort: Ordenar Profundidade em Jogos 2D no Godot 4

Aprenda a usar y-sort no Godot 4 para ordenar profundidade em jogos 2D top-down. Personagem na frente ou atras de arvores, TileMapLayer e z_index.
Voce monta seu mapa top-down no estilo Zelda, coloca algumas arvores, posiciona o personagem e tudo parece certo. Ai o jogador anda para cima, fica atras de uma arvore, e o desenho mostra o personagem por cima da copa, como se ele flutuasse na frente do tronco. No frame seguinte ele desce e agora a arvore o cobre quando nao deveria. Esse problema de profundidade e exatamente o que o y-sort godot resolve, e no Godot 4 a forma de fazer isso mudou em relacao ao Godot 3.
Neste post a gente vai entender por que isso acontece, como ativar o Y Sort no nó certo, por que o ponto de origem do nó importa tanto e como combinar tudo com TileMapLayer e z_index quando o cenario fica mais complexo.
Y-sort: Ordenar Profundidade em Jogos 2D no Godot 4
Por que o personagem fica na ordem errada
Em 2D nao existe profundidade real. O Godot desenha os nós em uma ordem e pronto: o que vem depois aparece por cima do que veio antes. Por padrao, essa ordem segue a posicao dos nós na arvore da cena. Um filho que esta mais abaixo na lista de irmaos desenha por cima dos que estao acima dele.
Isso funciona para coisas estaticas, mas quebra na hora que um personagem se move. Imagine uma arvore parada e um jogador andando. As vezes o jogador esta "acima" da arvore no mapa (mais para o norte, com Y menor) e deveria aparecer atras dela. As vezes ele esta "abaixo" (Y maior) e deveria aparecer na frente. Como a ordem da arvore na cena e fixa, em um dos casos o desenho vai sair errado.
A logica que a gente quer e simples: quem esta mais embaixo na tela (Y global maior) esta mais perto da camera e deve ser desenhado por cima. Quem esta mais em cima (Y menor) fica mais ao fundo. Ordenar os irmaos por essa posicao Y, frame a frame, e o que chamamos de y-sort.
Como o Godot 4 mudou o Y Sort
Se voce ja mexeu com Godot 3, talvez lembre de um nó chamado YSort. Voce colocava seus sprites e personagens como filhos dele e a ordenacao acontecia. No Godot 4 esse nó desapareceu.
Em vez de um nó separado, agora qualquer Node2D (na verdade qualquer CanvasItem) tem uma propriedade chamada y_sort_enabled. Voce ativa essa propriedade no nó pai e todos os filhos diretos dele passam a ser ordenados pela posicao Y global. Nada de container especial: o proprio nó que segura a cena vira o responsavel pela ordenacao.
No Inspector, selecione o nó pai (por exemplo um Node2D chamado World ou Level), abra a secao Ordering ou procure por Y Sort, e marque a opcao Y Sort Enabled. Pronto. Os filhos diretos agora respeitam a profundidade vertical.
Em GDScript voce faz o mesmo assim:
extends Node2D
func _ready() -> void:
# Ativa a ordenacao por Y nos filhos diretos deste no
y_sort_enabled = true
Um detalhe importante: a ordenacao olha so para os filhos diretos do nó que tem y_sort_enabled ligado. Se voce tem hierarquias aninhadas e quer que tudo participe da mesma ordenacao, normalmente os filhos intermediarios tambem precisam ter Y Sort habilitado, para que o efeito se propague pela arvore. Mantenha sua estrutura simples e isso raramente vira um problema.
O ponto de ordenacao e o origin do nó
Aqui esta a parte que mais confunde quem esta comecando. O Godot nao usa o centro do sprite nem a base da imagem para decidir a ordem. Ele usa a posicao Y global do nó, que e o ponto de origem (0,0) local daquele nó, ja convertido para coordenadas globais.
Traduzindo: o que importa e onde fica o origin do seu personagem ou objeto, e nao onde a imagem esta desenhada. Se voce tem um CharacterBody2D com um Sprite2D dentro, a ordenacao vai usar a posicao do CharacterBody2D, nao a do Sprite.
Por isso o offset do sprite e tao importante. Em um jogo top-down, o ponto que deve definir a profundidade sao os pes do personagem, porque e ali que ele "toca" o chao. Se o origin do nó esta na cabeca ou no centro do corpo, a ordenacao vai parecer errada: o personagem pode aparecer atras de uma arvore quando os pes dele ja passaram dela.
A solucao e deslocar a imagem para cima, de forma que o origin do nó fique exatamente na altura dos pes. No Sprite2D voce faz isso com a propriedade Offset. Por exemplo, se o personagem tem 32 pixels de altura e voce quer o origin nos pes:
extends CharacterBody2D
@onready var sprite: Sprite2D = $Sprite2D
func _ready() -> void:
# Sobe a imagem 16px para o origin (0,0) cair nos pes do personagem
sprite.offset = Vector2(0, -16)
O mesmo vale para arvores, pedras e arbustos: o origin deve ficar na base do objeto, onde ele encosta no chao. Com os origins alinhados nessa logica de "ponto de contato com o solo", a ordem por Y fica natural e o jogador nunca vai notar que existe um truque acontecendo.
Se voce ainda esta organizando como cada elemento entra no mundo, vale revisar como instanciar cenas no Godot, porque os objetos que voce coloca em runtime tambem precisam respeitar a mesma logica de origin para ordenar direito.
Y Sort em TileMapLayer
Cenarios de verdade nao sao so personagens soltos. Boa parte do mapa vem de tiles, e e comum querer que certas camadas de tiles participem da ordenacao junto com os personagens. Por exemplo, uma camada de arbustos altos ou paredes baixas que o jogador as vezes cobre e as vezes e coberto.
No Godot 4 o antigo TileMap foi dividido em nós TileMapLayer, um por camada. Cada TileMapLayer tem a sua propria opcao Y Sort Enabled no Inspector. Quando voce ativa, os tiles individuais daquela camada passam a ser ordenados pela posicao Y, e nao mais desenhados em bloco.
Mas tem um detalhe extra que e facil esquecer: o Y Sort Origin de cada tile. No editor do TileSet, ao configurar um tile, existe uma propriedade chamada Y Sort Origin. Ela define em que altura, dentro da celula, aquele tile deve ser considerado para a ordenacao. Por padrao costuma ser zero, o topo da celula, o que quase nunca e o que voce quer. Para uma arvore desenhada em um tile, voce ajusta o Y Sort Origin para a base do tronco, exatamente como fez com o offset do sprite do personagem.
Para tudo ordenar junto, a estrutura precisa estar sob um mesmo pai com Y Sort ligado. Um arranjo comum e:
# Estrutura da cena (visao geral):
# World (Node2D, Y Sort Enabled = true)
# - Ground (TileMapLayer, sem Y sort, e so o chao)
# - Objects (TileMapLayer, Y Sort Enabled = true, arvores e arbustos)
# - Player (CharacterBody2D)
# - Enemies (Node2D, Y Sort Enabled = true)
O chao quase sempre fica em uma camada separada sem Y sort, porque ele e sempre o fundo e nunca precisa passar na frente de ninguem. Ja a camada de objetos e os personagens ficam todos sob o World com Y Sort ativo, dividindo a mesma ordenacao. Assim um inimigo, o jogador e uma arvore competem pela ordem pela posicao Y de cada um, do jeito certo.
Se voce ainda esta montando esse cenario base, da uma olhada em como trabalhar com TileMap no Godot 4 para separar bem as camadas antes de ligar o Y sort.
Quando o z_index entra na jogada
Y sort resolve a maioria dos casos, mas nem tudo deve ser ordenado por Y. Algumas coisas precisam ficar sempre na frente ou sempre atras, independente da posicao vertical.
Pense em uma sombra projetada no chao: ela deve ficar sempre abaixo de todo mundo. Ou um efeito de particula de fumaca que deve aparecer sempre por cima do personagem. Ou a interface, que nunca pode ser coberta por nada do mundo. Para esses casos, voce usa z_index.
O z_index tem prioridade sobre a ordenacao por Y. Nós com z_index maior desenham por cima de nós com z_index menor, e so dentro do mesmo z_index a ordenacao por Y entra em acao. O valor padrao e zero, e voce pode usar valores negativos para empurrar algo para o fundo.
extends Node2D
@onready var shadow: Sprite2D = $Shadow
@onready var smoke: GPUParticles2D = $Smoke
func _ready() -> void:
# Sombra sempre atras de tudo no mesmo nivel
shadow.z_index = -1
# Fumaca sempre na frente do personagem
smoke.z_index = 1
Tem ainda a propriedade z_as_relative, ligada por padrao. Com ela ligada, o z_index de um filho e somado ao do pai, o que ajuda a manter grupos inteiros em uma faixa. Se voce precisa de um valor absoluto que ignora o pai, desligue z_as_relative no Inspector.
A regra pratica e: use Y sort para tudo que se move pelo chao e precisa de profundidade dinamica, e reserve o z_index para os poucos elementos que tem uma ordem fixa e conhecida, como sombras, efeitos e UI.
Resumo rapido
Para fechar, o caminho mais curto para um top-down com profundidade correta no Godot 4:
Ative y_sort_enabled no nó pai que segura personagens e objetos. Ajuste o offset de cada sprite para que o origin (0,0) do nó caia na base, nos pes do personagem ou no tronco da arvore. Nos seus TileMapLayer com objetos altos, ligue Y Sort Enabled e configure o Y Sort Origin de cada tile no TileSet. Mantenha o chao em uma camada propria sem Y sort. E use z_index so para sombras, efeitos e UI que precisam de uma ordem fixa.
Se quiser entender melhor o papel de cada nó nessa arvore de cena e por que o CharacterBody2D, o Sprite2D e os TileMapLayer se comportam assim, vale revisar os nodes do Godot explicados. Com os origins no lugar certo e o Y sort ligado no nó pai, seu personagem vai passar atras e na frente das arvores na hora exata, sem voce precisar escrever uma linha de logica de ordenacao manual.

