G2.4 — Control & UI Layout¶
What you'll learn
- Lay out UI with
Controlnodes via anchors and offsets, not raw positions. - Let containers (
VBoxContainer,HBoxContainer,GridContainer,MarginContainer) position children automatically. - Use size flags (
SIZE_FILL,SIZE_EXPAND_FILL,SIZE_SHRINK_CENTER) to control how space is distributed. - Recognize that inside a container you set size flags and minimum size, not positions.
How it applies
- Hand-placed UI breaks on every other screen. Position a button at fixed coordinates and it lands wrong on a different resolution, window size, or aspect ratio. Anchors and containers make one layout adapt to the whole class of screens — equivalence partitioning across the resolution axis, the same move as the project's stretch settings.
- Fighting a container wastes time. Set a child's position inside a
VBoxContainerand the container immediately overrides it; the position "won't stick." Knowing the container owns its children's layout tells you to set size flags, not coordinates. - Missing size flags make layouts look broken. A child that should stretch to fill a row stays
its minimum size without
SIZE_EXPAND_FILL; a panel that should fill its parent does not without the right anchors. The flags are the controls; absent, the defaults rarely match intent. - The wrong container lays out on the wrong axis. Children stacked vertically when you wanted a row, or vice versa, is a one-node fix once you know which container does which.
Concepts¶
Control: laid out, not positioned¶
A Control (G1.2) is a UI node, and UI is laid out rather than free-placed. Instead of a world
position, a Control has:
- anchors — four values (left, top, right, bottom) that pin its edges to fractions of its
parent's rectangle. Anchors of
(0, 0, 1, 1)pin all four edges to the parent's edges, so the control fills the parent and grows with it. - offsets — pixel distances from those anchors (margins).
The editor's anchor presets (a toolbar dropdown when a Control is selected) set common cases for
you: Full Rect (fill the parent), Center, Top Wide, Bottom Right, and so on. Anchors are what
make UI resolution-independent: a Full Rect panel fills a 1280×720 window and a 4K window alike,
because its edges are fractions of the parent, not fixed pixels.
Containers lay out their children¶
A container is a Control whose job is to position and size its children automatically:
VBoxContainer— stacks children vertically.HBoxContainer— arranges children in a row.GridContainer— a grid of a set column count.MarginContainer— adds padding around its child.
Inside a container you do not set a child's position or anchors — the container computes them. The
mental switch: outside a container you place a Control with anchors; inside one you hand layout to the
container and only influence it through size flags and minimum sizes.
Size flags¶
Size flags tell a container how to distribute leftover space along an axis:
SIZE_FILL— the child fills the space the container gives it.SIZE_EXPAND_FILL— the child also claims a share of leftover space, growing to fill it. Two siblings both set to expand split the extra space between them.SIZE_SHRINK_CENTER— the child stays at its minimum size, centered in its slot.
# in code (usually set in the Inspector instead):
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
A child's custom_minimum_size sets a floor the container will not shrink below. Between anchors
(for top-level controls), containers (for grouped controls), and size flags (for space distribution),
you describe a layout that adapts instead of one that is pinned to one screen.
Example
A vertical menu that stays centered and sized on any window:
CenterContainer # centers its single child
└── VBoxContainer # stacks the buttons vertically
├── PlayButton (Button)
├── OptionsButton (Button)
└── QuitButton (Button)
No button has a position set — the VBoxContainer stacks them and the CenterContainer centers the
stack, so the menu sits correctly whether the window is small or 4K. Setting each button's position
by hand would look right at one size and wrong everywhere else, and the containers would override
those positions anyway.
Common UI leaf nodes¶
The pieces that go inside the layout: Label (static text), Button (clickable, emits
pressed — wired in G2.6), Panel/PanelContainer (a styled background), TextureRect (an
image laid out as UI), ProgressBar (a bar with a value, e.g. health). These are the leaves;
containers and anchors are the skeleton that arranges them.
Walkthrough¶
- Add a
CanvasLayer(G2.3), and under it aControl. With theControlselected, open the anchor presets and choose Full Rect; note it fills the screen and stays filled as you resize the game window. - Under that, add a
VBoxContainerwith threeButtonchildren. Run and resize: the buttons stack and the layout holds. Try to drag one button to a new position in the editor — the container snaps it back, demonstrating that the container owns layout. - Set the middle button's
size_flags_verticaltoSIZE_EXPAND_FILLand watch it claim the extra vertical space. Set it back toSIZE_FILLand the row redistributes. - Wrap the
VBoxContainerin aCenterContainerand confirm the stack centers regardless of window size. Add aMarginContainerto pad it from the edges.
Optional sanity check
Put a Label directly under a CanvasLayer (not in a container) and set its anchors to Center —
it stays centered as you resize. Now move it into a VBoxContainer and try to set its anchors — they
no longer do anything, because the container is laying it out. That is the "anchors outside
containers, size flags inside" rule, felt directly.
Self-check quiz¶
Q1 — Why lay out a menu with anchors and containers instead of fixed positions?
A. Fixed positions are not allowed on Control nodes.
B. Anchors and containers make one layout adapt to any resolution/window size; fixed positions look right at one size and wrong at others.
C. Containers render faster.
D. There is no difference for UI.
Reveal answer
B. Anchors express edges as fractions of the parent and containers compute child layout, so the UI adapts across the whole class of screen sizes — one layout, many resolutions. A is false (you can set positions; it is just the wrong approach for adaptive UI). C is not the reason. D ignores the resolution problem that motivates the whole system.
Q2 — You set a button's position inside a VBoxContainer, but it snaps back. Why?
A. The button is broken.
B. The container owns its children's layout; inside a container you influence layout with size flags and minimum size, not position.
C. You must lock the position first.
D. VBoxContainer only accepts Label children.
Reveal answer
B. A container computes its children's positions and sizes, overriding any position you set;
the levers inside a container are size flags and custom_minimum_size. A misreads expected
behavior as a bug. C invents a lock. D is false — containers accept any Control.
Q3 — Two buttons share a row; you want one to take all the leftover width. Which size flag?
A. SIZE_SHRINK_CENTER on both.
B. SIZE_EXPAND_FILL on the one that should grow.
C. SIZE_FILL on both.
D. Set the button's position wider.
Reveal answer
B. SIZE_EXPAND_FILL makes a child claim a share of leftover space; putting it on one button
lets that button grow into the extra width. A keeps both at minimum size, centered. C fills the
assigned slots but does not claim leftover space. D fights the container, which will override
the position.
Integration question¶
Q4 — open
A HUD built directly with fixed positions looks right at 1280×720 but, on a tester's 2560×1440
ultrawide, the health bar floats in the middle and the score runs off the edge. Using anchors,
containers, and size flags — and the CanvasLayer idea from G2.3 — describe how you would rebuild it
so one layout serves every screen, and name the QA principle this mirrors.
Reveal expected answer
Rebuild the HUD under a CanvasLayer (G2.3) so it is pinned to the screen and immune to the
world camera, with a root Control set to Full Rect anchors so it always fills the viewport
whatever its size. Place the health bar in a corner using an anchor preset (e.g. Top Left) so it
sticks to that corner at any resolution rather than a fixed pixel spot, and put the score in a
Top Right-anchored container so it grows inward instead of running off. Group related elements
in containers (an HBoxContainer for a row of resource readouts, a MarginContainer for edge
padding) and use size flags (SIZE_EXPAND_FILL for elements that should absorb extra space)
so the arrangement redistributes rather than overflowing. The result is one layout that adapts to
1280×720 and 2560×1440 alike, because every position is expressed as a fraction of the screen and
a container rule, not a fixed coordinate. The QA principle is equivalence partitioning across
the resolution axis: a layout verified at one representative size covers the whole class of
sizes, instead of forcing a retest (and a likely defect) at every resolution — the same reasoning
behind the project's stretch settings.