Godot 4.x's presentation layer: Control nodes, anchors and containers, themes, fonts, focus and HUDs; the
AnimationPlayer, AnimationTree and Tween APIs; sprite animation; and audio players, buses and effects. Material
effects are in Shaders; scene and pause mechanics in
Nodes, scenes & signals.
Control basics
Control is the base of every UI node. It lives in 2D canvas space, has a rectangle (position, size),
receives GUI input, and is laid out either by anchors or by a parent container.
Member
Notes
position, size, global_position
rect in parent coordinates; set by the container when inside one
custom_minimum_size
the reliable way to size something inside containers
get_combined_minimum_size()
what containers use for layout
pivot_offset, rotation, scale
transform around the pivot (pivot_offset_ratio 0 to 1 in 4.6+)
mouse_filter
MOUSE_FILTER_STOP (eat clicks), PASS (handle and let parents see), IGNORE (click-through)
var panel := Panel.new()add_child(panel)panel.set_anchors_and_offsets_preset( Control.PRESET_FULL_RECT)var hint := Label.new()hint.set_anchors_preset(Control.PRESET_CENTER_BOTTOM)hint.grow_horizontal = Control.GROW_DIRECTION_BOTH
grow_horizontal / grow_vertical decide which way a Control expands when its content outgrows its minimum
size (for example a centered label that should grow both ways).
Containers & size flags
A container positions and sizes its children; anchors of children are ignored. Nest containers instead of
placing things by hand.
Container
Lays out
HBoxContainer / VBoxContainer
a row / column; constant separation, property alignment
Pixel art: set stretch/mode = viewport, scale_mode = integer, and the default texture filter
(rendering/textures/canvas_textures/default_texture_filter) to Nearest.
Themes & overrides
A Theme resource holds styles per control type (colors, constants, fonts, font sizes, icons, styleboxes).
Set it project-wide (Project Settings > GUI > Theme > Custom) or on any Control's theme property: children
inherit it.
Level
How
Scope
Project theme
gui/theme/custom
everything
Node theme
inspector or control.theme = preload(...)
that branch
Type variation
theme type HeaderLabel based on Label; set theme_type_variation
opt-in look, like a CSS class
Local override
Inspector > Theme Overrides, or add_theme_*_override
BBCode tags include [b] [i] [u] [s] [code] [center] [right] [color=...] [bgcolor=...] [font_size=...] [font=...] [img] [url] [table=n] [cell] and effects [wave] [shake] [tornado] [fade] [rainbow]. Set the
default font in the project theme or Project Settings > GUI > Theme > Custom Font.
Focus & navigation
Keyboard and gamepad menus work through focus: the focused Control receives ui_accept, and ui_up /
ui_down / ui_left / ui_right move focus to the nearest neighbor.
func open_menu() -> void: show() $VBox/Resume.grab_focus() # required for gamepadsfunc _ready() -> void: # explicit neighbors when the geometry guess is wrong $VBox/Quit.focus_neighbor_bottom = ^"../Resume" $VBox/Resume.focus_neighbor_top = ^"../Quit" get_viewport().gui_focus_changed.connect(_on_focus)func _on_focus(c: Control) -> void: $Sfx/Tick.play()
API
Notes
grab_focus(), release_focus(), has_focus()
manage focus
focus_neighbor_left / _top / _right / _bottom
NodePaths for arrow navigation
focus_next, focus_previous
Tab order
get_viewport().gui_get_focus_owner()
who has focus
gui_focus_changed(control)
Viewport signal
focus stylebox
the outline; mouse and keyboard focus can be styled separately in 4.6+
draw order between layers (default 1; the world is layer 0; negative for backgrounds)
visible
hide the whole layer
offset, rotation, scale
transform the entire layer
follow_viewport_enabled
make the layer follow the camera again (parallax-like layers)
For 3D games the same pattern applies: put the HUD in a CanvasLayer above the 3D view.
AnimationPlayer
Animates any property of any node (plus method calls and audio) along a timeline. Create animations in the
Animation bottom panel; key properties with the key icon next to each inspector property.
Track type
Animates
Property (Value)
any property: modulate, position, frame, visible, shader params
Position / Rotation / Scale 3D
fast transform tracks for 3D and skeletons
Blend Shape
mesh morph targets
Call Method
calls a function at a key (spawn a projectile on frame 3)
Bezier Curve
a float with hand-edited curves
Audio Playback
plays audio clips on an AudioStreamPlayer
Animation Playback
triggers animations on another AnimationPlayer
Update mode (value tracks)
Behavior
Continuous
interpolate between keys
Discrete
jump at each key (sprite frames, booleans)
Capture
start from the current value, then follow the keys
@onready var anim: AnimationPlayer = $AnimationPlayerfunc _ready() -> void: anim.animation_finished.connect(_on_anim_finished) anim.play("idle") anim.play("walk", 0.2) # 0.2 s cross-fade anim.play("attack", -1, 1.5) # 1.5x speed anim.play_backwards("open") anim.queue("idle") # after the current anim.speed_scale = 0.5 anim.seek(0.3, true) # jump and update print(anim.current_animation, anim.is_playing())func _on_anim_finished(name: StringName) -> void: if name == &"attack": anim.play("idle")
Fact
Detail
Libraries
animations live in AnimationLibrary resources; the default library has an empty name ("walk"), others are addressed as "combat/slash"
RESET animation
stores default values; the editor restores them on save so scenes are not saved mid-animation
Autoplay
the "A" button in the Animation panel plays an animation on load
Loop
per animation: none, linear, ping-pong; looping animations never emit animation_finished
Blend times
anim.set_blend_time("run", "idle", 0.15) or playback_default_blend_time
Callback mode
callback_mode_process: idle, physics or manual (advance(delta))
Shared base
AnimationMixer (4.2+) is the base of AnimationPlayer and AnimationTree
# Build an animation in codevar a := Animation.new()var t := a.add_track(Animation.TYPE_VALUE)a.track_set_path(t, "Sprite2D:modulate")a.track_insert_key(t, 0.0, Color.WHITE)a.track_insert_key(t, 0.1, Color.RED)a.track_insert_key(t, 0.2, Color.WHITE)a.length = 0.2var lib := AnimationLibrary.new()lib.add_animation("flash", a)anim.add_animation_library("fx", lib)anim.play("fx/flash")
AnimationTree
AnimationTree blends and sequences the animations of an AnimationPlayer (anim_player property) using a node
graph: state machines, blend spaces and one-shots. Set active = true.
Root node type
Use
AnimationNodeStateMachine
states with transitions: idle, run, jump, fall
AnimationNodeBlendTree
a graph of blend nodes
AnimationNodeBlendSpace1D
blend by one value (speed: idle, walk, run)
AnimationNodeBlendSpace2D
blend by a vector (8-way strafing, aim offsets)
AnimationNodeOneShot
fire an overlay once (attack, hit reaction)
AnimationNodeBlend2 / Add2
mix two inputs by amount
AnimationNodeTimeScale
speed control
AnimationNodeTransition
switch between inputs with cross-fades
@onready var tree: AnimationTree = $AnimationTree@onready var playback: AnimationNodeStateMachinePlayback = \ tree.get("parameters/playback")func _physics_process(_d: float) -> void: # BlendSpace2D named "Move" inside the state machine tree.set("parameters/Move/blend_position", velocity.normalized()) # Advance conditions on transitions tree.set("parameters/conditions/on_floor", is_on_floor()) if Input.is_action_just_pressed("attack"): playback.travel("Attack") # path-finds transitions print(playback.get_current_node())func hit() -> void: tree.set("parameters/HitShot/request", AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE)
Hover a parameter in the inspector to copy its exact path. Transitions can also use an Advance Expression
evaluated on the tree's advance_expression_base_node.
Tweens
A Tween animates values from code with no editor setup. Create it with create_tween() (bound to the node:
paused with it, killed when it is freed); it starts on the next frame and is invalid once finished.
func pop_in(node: Control) -> void: node.scale = Vector2.ZERO var t := create_tween() t.tween_property(node, "scale", Vector2.ONE, 0.35) \ .set_trans(Tween.TRANS_BACK) \ .set_ease(Tween.EASE_OUT) t.tween_interval(1.0) # wait t.tween_property(node, "modulate:a", 0.0, 0.3) t.parallel().tween_property( # same time node, "position:y", -20.0, 0.3).as_relative() t.tween_callback(node.queue_free) # at the end
Tweener
Does
tween_property(obj, "prop", final, sec)
animate a property; sub-properties via "modulate:a", "position:x"
@onready var sfx: AudioStreamPlayer2D = $Sfxfunc play_hit() -> void: sfx.pitch_scale = randf_range(0.9, 1.1) # variation sfx.play()# Fire-and-forget one-shot that outlives its emitterfunc play_detached(stream: AudioStream, at: Vector2) -> void: var p := AudioStreamPlayer2D.new() p.stream = stream p.global_position = at p.bus = &"SFX" p.finished.connect(p.queue_free) get_tree().current_scene.add_child(p) p.play()
Buses & effects
The Audio bottom panel edits the bus layout (saved as res://default_bus_layout.tres). Every player outputs to a
bus; buses route into others and finally into Master. Typical layout: Master, Music, SFX, UI, Voice.
func set_bus_volume(bus_name: String, linear: float) -> void: var i := AudioServer.get_bus_index(bus_name) AudioServer.set_bus_volume_db(i, linear_to_db(linear)) AudioServer.set_bus_mute(i, linear <= 0.001)func muffle(on: bool) -> void: # e.g. while paused var i := AudioServer.get_bus_index("Music") AudioServer.set_bus_effect_enabled(i, 0, on) # a filter
Human hearing is logarithmic: map UI sliders (0 to 1) through linear_to_db, never straight to dB. Audio players
pause with the tree unless their process_mode is Always (give the music player Always).
Recipes
Health bar with lag
Use for a HUD bar where damage shows as a trailing "ghost" segment behind the real value.
health_bar.gd
extends Control@onready var bar: ProgressBar = $Bar # front@onready var ghost: ProgressBar = $Ghost # behindvar _tween: Tweenfunc setup(max_value: int) -> void: for b: ProgressBar in [bar, ghost]: b.max_value = max_value b.value = max_valuefunc set_health(v: int) -> void: bar.value = v if _tween: _tween.kill() _tween = create_tween() _tween.tween_interval(0.3) _tween.tween_property(ghost, "value", float(v), 0.4) \ .set_trans(Tween.TRANS_CUBIC) \ .set_ease(Tween.EASE_OUT)
Pause menu
Use as a CanvasLayer scene in each level; it runs while the tree is paused and grabs focus for gamepads.
pause_menu.gd
extends CanvasLayer@onready var resume: Button = %Resume@onready var quit: Button = %Quitfunc _ready() -> void: layer = 10 process_mode = Node.PROCESS_MODE_ALWAYS visible = false resume.pressed.connect(toggle) quit.pressed.connect(get_tree().quit)func _unhandled_input(event: InputEvent) -> void: if event.is_action_pressed("ui_cancel"): toggle() get_viewport().set_input_as_handled()func toggle() -> void: var paused := not get_tree().paused get_tree().paused = paused visible = paused if paused: resume.grab_focus()
Screen shake with a tween
Use on a Camera2D for hits and explosions; the random offset decays to zero over the duration.