Quick answer: Controls follow the window only if their anchors are relative to something that resizes. Use a root Control set to the Full Rect preset under a CanvasLayer, lay out children with Containers rather than fixed positions, and pick a Stretch Mode (Project Settings > Display > Window > Stretch) that matches your game.
The UI looks right at the size you designed it, then a player maximises the window and the health bar is stuck in the middle of the screen or the menu overflows. Godot has two separate systems involved — window stretching and Control anchoring — and both have to be set up.
Step 1: choose a stretch mode
Project Settings > Display > Window > Stretch > Mode:
- disabled — nothing scales; a bigger window just shows more of the world, and UI keeps its pixel size. Controls must anchor themselves.
- canvas_items — 2D and UI are scaled to the window while staying sharp. The usual choice for most games with HUDs and menus.
- viewport — the game renders at the base resolution and the image is scaled. Best for pixel art.
Set Aspect to expand if you want extra space on wider screens instead of black bars, then design anchors so the UI uses that extra space sensibly. Check that Display > Window > Size > Resizable is on if players should be able to resize.
Step 2: anchor to a parent that resizes
Anchors are relative to the parent Control's rectangle. A Control whose parent is not a Control (for example a Node2D) has no sensible rectangle to follow, and it also moves with the camera. Build HUDs like this:
HUD (CanvasLayer)
└── Root (Control, Layout preset: Full Rect)
├── TopBar (HBoxContainer, preset: Top Wide)
└── Minimap (TextureRect, preset: Bottom Right)
Apply presets from the Layout menu in the toolbar (the anchor icon) rather than dragging nodes into place: dragging changes offsets but leaves anchors at the top-left.
Step 3: let containers do the layout
Inside a Container, children's anchors and positions are controlled by the container, so edits to them appear to do nothing. Use Size Flags instead: Fill and Expand make a child grow into available space, and size_flags_stretch_ratio shares space between siblings. Use MarginContainer for padding instead of manual offsets.
Step 4: check minimum sizes
A Control never shrinks below its custom_minimum_size or its content's minimum size (long Label text, large textures). If a panel will not get smaller, look for a child with a large minimum size, and enable autowrap_mode on long labels.
Reacting to size changes in code
If you position things manually, update them when the viewport changes rather than once in _ready():
func _ready():
get_viewport().size_changed.connect(_layout)
_layout()
func _layout():
var size := get_viewport_rect().size
$Crosshair.position = size / 2 - $Crosshair.size / 2
Test with the editor's Run > Window size options or by resizing the running game, at a few extreme aspect ratios such as 4:3, 16:9 and 21:9.