Quick answer: CanvasLayer.layer is the outermost sort key; z_index only orders nodes within the same layer. Put UI in a CanvasLayer with layer = 10+, gameplay in layer = 0, background in layer = -10. Then use z_index inside each.

You crank up z_index = 100 on a HUD label and the player’s sprite still draws over it. Or your pause menu gets covered by an explosion VFX. The fix is to stop fighting z_index and use CanvasLayer.layer, which is a coarser, simpler dial.

The Symptom

Setting z_index high on a Control or Sprite2D fails to put it on top. Reordering nodes in the tree changes draw order within a layer but cannot move a node above a different CanvasLayer. UI elements get hidden behind gameplay sprites.

The Sort Hierarchy

Godot 4’s 2D sort, in order of priority:

  1. CanvasLayer.layer — the outermost key; higher draws on top of lower.
  2. z_index within the layer (range -4096 to 4096).
  3. Y-sort if a parent has y_sort_enabled (overrides tree order for children).
  4. Tree order — later siblings draw on top of earlier siblings.

If two nodes are in different CanvasLayers, only step 1 matters — z_index, Y-sort, and tree order are irrelevant.

The Fix

Step 1: Pick layer numbers per role. A clean convention:

Background CanvasLayer:    layer = -10
Gameplay (no CanvasLayer): layer =   0
HUD CanvasLayer:           layer =  10
Menus CanvasLayer:         layer =  20
Debug overlay CanvasLayer: layer =  100

Anything in the gameplay layer (nodes outside any CanvasLayer) draws between background and HUD. The pause menu in “Menus” always wins over gameplay because 20 > 0.

Step 2: Use z_index sparingly inside layers. Within Gameplay, set z_index on the player to 1 and on regular sprites to 0 if you need the player on top. Don’t use giant z_index values to try to compete with another CanvasLayer.

Step 3: Y-sort containers for top-down games.

Gameplay (Node2D, y_sort_enabled = true)
  — Player (CharacterBody2D)
  — Tree (Sprite2D)
  — Enemy (CharacterBody2D)

With y_sort_enabled on the parent, children sort by their y position. The player walking behind a tree draws first; in front draws second.

UI That Ignores the Camera

HUD elements shouldn’t scroll with the camera. CanvasLayer.follow_viewport_enabled = false (the default) makes the layer’s contents render in screen space, not world space. Set it true only when you want the layer to track the camera (parallax background).

Verifying

Project → Settings → Debug → Settings → Visible Collision Shapes — while not specifically a sort visualizer, running the scene and inspecting Remote Tree shows the layer values live. Or simply add a print to confirm layers: print(layer) in _ready.

“Layer is the outer key, z_index the inner. Pick layer numbers per role and stop fighting z_index.”

Related Issues

For Y-sort failing on TileMapLayer, see Y-sort tilemap. For UI clipping issues, see Control anchor.

CanvasLayer.layer first. z_index inside. Y-sort for top-down.