Quick answer: Everything outside a CanvasLayer is drawn on layer 0. A CanvasLayer is drawn by its layer value — higher numbers on top — and z_index and tree order only sort within one layer. Give the HUD a higher layer than any other CanvasLayer, and make sure its nodes are actually children of it.
Draw order in Godot 2D has two levels: which canvas layer something is on, then where it sits inside that layer. Most "my UI is behind the game" bugs come from trying to fix a layer problem with z_index.
How layers stack
- The main scene draws on canvas layer 0.
- Each CanvasLayer node draws its children on its own layer, set by the
layerproperty (default 1). - Layers are drawn from lowest to highest. A node on layer 1 is always above everything on layer 0, whatever their
z_index. z_indexand position in the scene tree only decide order within a layer.
Cause 1: two CanvasLayers with the same or reversed layer
A pause menu on layer 1 and a HUD also on layer 1 sort by tree order. A transition effect on layer 10 hides a menu on layer 5. Pick a scheme and stick to it, for example:
-100 ParallaxBackground (its default)
0 game world
1 HUD
5 pause menu
10 screen transitions
100 debug overlay
Cause 2: the node is not under the CanvasLayer
Only descendants of the CanvasLayer use its layer. A Control instanced into the scene root, or reparented at runtime with add_child() on the wrong node, falls back to layer 0. Check the Remote tree while the game runs.
Cause 3: z_index used to fix it
Raising z_index on a world sprite cannot put it above a CanvasLayer, and lowering it on a HUD cannot put it below. Change layer instead.
Cause 4: the layer is hidden or follows the camera
CanvasLayer has its own visible property in Godot 4; hiding it hides all children. If Follow Viewport is enabled, the layer moves with the camera like world objects — useful for some effects, wrong for a HUD.
Cause 5: it is in a different viewport
A SubViewport has its own canvas and layers. A CanvasLayer inside a SubViewport can only be above things inside that SubViewport, and the SubViewport's output is then drawn wherever its container or texture is.
Setting it from code
$HUD.layer = 1
$PauseMenu.layer = 5
Remember that higher layers also receive mouse input first, so an invisible-but-visible full-screen Control on a high layer blocks clicks on everything below.