Quick answer: Y-sort compares y_sort_origin per node. Tiles default origin is the tile center, which is wrong for tall art. Set each wall tile’s Y Sort Origin to its visual bottom in the TileSet editor, enable Y Sort on the TileMap layer, and ensure characters update their y_sort_origin to feet position.
Here is how to fix Godot 4 TileMap Y-sort that produces wrong draw order between characters and tiles. The character walks behind a fence even when standing in front of it. Or in front of it when standing behind. Y-sort works by comparing per-node origin Y values; tiles need their origins calibrated correctly or the comparison gives nonsense results for tall art.
The Symptom
Y-sort works for character vs character, but breaks at TileMap interactions. A tree tile draws above the player when player is standing in front of the trunk. A waist-high fence draws below the player even when player should be hidden behind it.
What Causes This
Tile Y Sort Origin defaults to center. A 16x32 tile’s center is at y=16. The visible bottom (where the tile contacts the floor) is at y=32. Y-sort uses origin, not bottom; tall art with center origin sorts wrong.
Y-sort not enabled on layer. TileMap has per-layer Y Sort Enabled in Godot 4. If unchecked, the layer renders in fixed order regardless of node origins.
Character y_sort_origin at top. A character whose Sprite2D pivot is at center has y_sort_origin equal to position.y, but visually they sort against tile origins that may be at the floor level.
Multiple Y-sort containers fighting. A TileMap inside a Y-sorted Node2D parent can cause contradictory orderings when both try to sort.
The Fix
Step 1: Enable Y Sort on the TileMap layer. Select the TileMap. Expand Layers. For each layer that contains characters/walls/objects, check Y Sort Enabled.
Step 2: Set Tile Y Sort Origin in the TileSet editor. Open the TileSet asset. Select each tall tile (walls, trees, fences). In the right panel, scroll to Y Sort Origin and set it to the visual contact point. For a 32x64 tree where the trunk meets the ground at the bottom, set Y Sort Origin = (0, 32) measured from tile center, or use the on-screen handle to drag the origin to the floor line.
Step 3: Calibrate characters.
extends CharacterBody2D
@onready var sprite: Sprite2D = $Sprite2D
func _ready():
# Y-sort against feet, not center
y_sort_enabled = true
# If sprite pivot is center, the node origin should already be at feet
# If pivot is top, offset
Or set y_sort_origin per frame in code:
func _process(_delta):
# Sort against the bottom of the sprite
var feet_y = sprite.global_position.y + sprite.texture.get_height() * 0.5
y_sort_origin = feet_y - global_position.y
Step 4: Ensure parent containers cooperate. If TileMap is under a Y-sorted Node2D, both must agree. Disable Y-sort on whichever container does not need it; usually leave it on the TileMap layer and disable on the parent (or vice versa).
Step 5: Use multiple TileMap layers for floor vs walls.
TileMapLayer (Floor) - Y Sort Disabled
TileMapLayer (Walls) - Y Sort Enabled
Player CharacterBody2D - Y Sort Enabled (sibling of walls layer)
Floors do not need Y-sort (they always render below). Walls and characters share the Y-sorted layer above.
Debugging Tip
Toggle Visible Collision Shapes and add a temporary debug script that prints each tile’s y_sort_origin for the cells under the cursor. Compare against the player’s y_sort_origin. If the values are wrong, it points directly at which tile’s origin needs adjustment.
“Y-sort needs origins at visual bottoms. Tiles default wrong; set each tall tile’s origin manually.”
Related Issues
For CanvasLayer ordering, see CanvasLayer Follow Viewport. For Camera smoothing, see Camera2D Smoothing.
Y-sort layer on. Tile origins at the bottom. Character origin at feet. The sort works.