This episode focuses on the interface: the Control node hierarchy, containers and the layout system, various widgets like buttons, labels, sliders, and menus, responsive UI techniques with anchoring, and connecting UI signals to real game logic.

In the last two episodes you built worlds — first 2D, then 3D. But players don't just need a world; they need a way to communicate with it. In episode 6, we briefly touched on CanvasLayer as an overlay. Episode 8 is the full treatment of the interface: UI & HUD. Here you'll learn to assemble clean layouts, build functional menus, and wire all the widgets into game logic.
This episode's roadmap: we start with the Control node as the base of all UI, learn about containers and the layout system, get to know the main widgets like Button, Label, Slider, and PanelContainer, then make responsive UI with anchoring, and finish by connecting UI signals to game logic interactions.
Every interface element in Godot derives from the Control node. Control has its own coordinate system, different from the world: position is measured from the top-left corner, and size is set via the size property. This is why UI doesn't shift when the camera moves — it lives in screen space, not world space.
There are three key properties on every Control: position (top-left corner), size (width and height), and anchor. To place elements precisely, put all UI inside a CanvasLayer or directly as children of a Control root scene. Godot also provides a Layout panel in the editor toolbar that adjusts anchors and offsets visually.
extends Control
func _ready() -> void:
position = Vector2(20, 20)
size = Vector2(200, 50)
print("rect global: %s" % get_global_rect())Don't be tempted to place a Label or Button directly in the world scene — without a Control parent attached to the screen, their positions will shift uncontrollably. Always wrap UI in a CanvasLayer or Control root.
Placing widgets one by one with manual coordinates is painful — and breaks the moment the screen resolution changes. The solution is containers: nodes that automatically manage the position and size of their children. Just create a VBoxContainer or HBoxContainer, put a few Buttons inside, and Godot arranges them neatly in order without you touching a single number.
Popular containers you must know:
VBoxContainer — arranges children vertically from top to bottom.HBoxContainer — arranges children horizontally from left to right.GridContainer — arranges children in a grid according to a set number of columns.MarginContainer — adds space around its contents.CenterContainer — centers children in the middle of the area.Containers respect the size_flags_horizontal and size_flags_vertical properties on each child. Set EXPAND so a child fills extra space, or SHRINK_CENTER to keep it centered. An example of a simple menu:
extends VBoxContainer
func _ready() -> void:
add_button("Start Game")
add_button("Settings")
add_button("Quit")
func add_button(text: String) -> void:
var button := Button.new()
button.text = text
add_child(button)The result is three buttons neatly arranged vertically that adapt automatically. Containers are the key to UI productivity in Godot — use them layered: MarginContainer > VBoxContainer > rows of HBoxContainer for complex layouts.
Godot provides ready-made widgets for almost every UI need:
Label — static or dynamic text for scores and messages.Button — a clickable action; has CheckButton, ToggleButton, and OptionButton variants.LineEdit — single-line text input for player names.HSlider and VSlider — continuous value controls, perfect for volume and speed.ProgressBar — displays a value as a bar.PanelContainer / Panel — a visual container with a background for holding other elements.PopupMenu — a contextual dropdown menu.Assembling a settings page usually combines several of these widgets in containers. For example, a volume row consists of a Label ("Music"), an HSlider, and a percentage Label — all inside a single HBoxContainer. For a main menu, wrap all the buttons in a CenterContainer, then add a PanelContainer as the backdrop so it looks like a real menu.
Info
Godot has a global theme for UI. Set a Theme in project settings so all Button, Label, and Panel nodes use a consistent style without styling them one by one. For quick polish, set a StyleBoxFlat on a panel: give it a background color, corner radius, and border.
Game screens aren't always the same: from portrait phones to ultrawide monitors. To keep UI correct in every condition, you use anchoring. An anchor determines which part of the parent area a position is referenced against. The four anchor corners: top-left (0,0), bottom-right (1,1).
Some common anchor patterns:
(0,0) to (1,1); the element stretches to follow the screen size. Used for backgrounds and main containers.Anchors work in pairs with offset (distance from the anchor) and grow_direction (the direction it grows when the parent size changes). For a popup panel that's always centered regardless of resolution, just set the center anchor and let its offsets be automatic. An example of placing a button at the bottom-right corner:
extends Button
func _ready() -> void:
anchors_preset = Control.PRESET_BOTTOM_RIGHT
offset_left = -200.0
offset_top = -50.0
offset_right = -20.0
offset_bottom = -20.0Notice: with a bottom-right anchor, negative offsets mean "back from the edge." This pattern keeps the button stuck to the bottom-right corner of the screen at any size.
UI without logic is just a picture. The bridge between them is signals. Every widget emits signals: Button emits pressed, HSlider emits value_changed, LineEdit emits text_submitted. You can connect them in the editor via the Node panel, or programmatically with connect().
The most idiomatic way in Godot 4 is the @onready annotation with node references, then connect() in _ready():
extends Control
@onready var start_button: Button = %StartButton
@onready var volume_slider: HSlider = %VolumeSlider
func _ready() -> void:
start_button.pressed.connect(_on_start_pressed)
volume_slider.value_changed.connect(_on_volume_changed)
func _on_start_pressed() -> void:
get_tree().change_scene_to_file("res://scenes/game.tscn")
func _on_volume_changed(value: float) -> void:
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Music"), value)Notice the %StartButton annotation — this is a unique name used by Godot 4 so node references don't break even if the hierarchy changes. UI signals work exactly like the signals you created yourself in episode 5: emitted from the widget, received in the script. This is how you make UI talk to game logic.
Connected signals are half the journey; the other half is the logic that executes the consequences. For HUDs, a common pattern is storing a reference to the game node and updating a Label every time a value changes. Example: a score counter that increases when a coin is picked up.
extends Control
@onready var score_label: Label = %ScoreLabel
var score := 0
func add_score(points: int) -> void:
score += points
score_label.text = "Skor: %d" % scoreWhen the coin's Area2D is detected (the pattern from episode 6), call get_node("../HUD").add_score(10) or, even better, go through a custom coin_collected signal sent to the HUD. This way the HUD doesn't need to know the details of coin detection — it just listens and updates the display. This separation is the essence of healthy UI architecture.
One more useful pattern: the paused menubar. A pause button in the corner of the screen calls get_tree().paused = true, then shows a CanvasLayer containing a menu. The logic is still simple UI — one button changes the tree's state, one panel appears. We'll dissect the pause menu fully in episode 9 together with state management.
Episode 8 equipped you with the entire interface toolkit: the Control node as the base, containers for automatic layout, standard widgets like Button, Label, and Slider, anchoring for responsive UI, and the signal wiring that connects UI to game logic.
The key takeaways:
Control and lives in screen space, separate from the game world.pressed, value_changed) and the %UniqueName pattern.In episode 9, you get into more serious matters: Game State & Data Persistence. We'll build autoload singletons for game state, save and load data with JSON and ConfigFile, design scene transitions and state machines, and make a pause menu and settings that actually persist. See you in the world of state!