Autoloads, Groups, Scene Instancing & Node Communication
An autoload is a scene or script added to the root of the tree at startup, always available by name.
28.1 Autoloads (singletons)
An autoload is a scene or script added to the root of the tree at startup, always available by name. Register in *Project Settings → Globals → Autoload* (name GameState, path res://autoload/game_state.gd).
Good uses: game state, event bus, audio manager, save manager, scene transitions. Avoid stuffing all logic into autoloads — they become global "god objects".
# game_state.gd
extends Node
signal score_changed(score: int)
var score := 0:
set(v):
score = v
score_changed.emit(score)
var current_level := 1
var unlocked_levels: Array[int] = [1]
func reset() -> void:
score = 0
current_level = 1# anywhere
GameState.score += 10
GameState.score_changed.connect(_on_score_changed)28.2 Groups
Godot 4.3 added global groups defined in *Project Settings → Globals → Groups* with descriptions.
add_to_group("enemies") # or in Node dock → Groups
remove_from_group("enemies")
is_in_group("enemies")
var enemies := get_tree().get_nodes_in_group("enemies")
get_tree().call_group("enemies", "alert", global_position) # call method on all
get_tree().set_group("lights", "visible", false) # set property on all
var player := get_tree().get_first_node_in_group("player")
get_tree().get_node_count_in_group("enemies")28.3 Instancing scenes
instantiate() creates the node tree; add_child() puts it in the scene tree (triggering _enter_tree/_ready).
Use add_child.call_deferred(node) (or call_deferred("add_child", node)) when adding during physics callbacks/signals ("Can't change this state while flushing queries").
remove_child(node) keeps the node alive (re-add later); queue_free() deletes it.
reparent(new_parent) moves a node keeping its global transform (4.0+).
const BulletScene := preload("res://scenes/bullet.tscn")
func shoot() -> void:
var bullet := BulletScene.instantiate() as Bullet
bullet.global_position = $Muzzle.global_position
bullet.rotation = global_rotation
bullet.speed = 800.0 # set properties before adding (before _ready)
get_tree().current_scene.add_child(bullet) # not a child of the player, so it doesn't move with it28.4 Changing scenes
For persistent HUDs/players, use a Main scene that swaps level children manually, or autoloads.
get_tree().change_scene_to_file("res://scenes/level_2.tscn")
get_tree().change_scene_to_packed(preloaded_scene)
get_tree().reload_current_scene()
get_tree().quit()28.5 Node communication patterns
"Call down, signal up": a parent knows its children and calls their methods; children don't know their parents and emit signals instead. Siblings communicate through their common parent or a signal bus.
Parent → child Direct call: $Weapon.fire() Child → parent Signal: health_changed.emit(hp); parent connects Sibling ↔ sibling
Parent wires them in _ready(): $Player.died.connect($HUD.show_game_over) Event bus autoload, or groups Many objects of a kind Groups (call_group)
Exported node references or setting properties after instancing Querying a manager
Autoload or get_first_node_in_group("manager") Avoid get_parent().get_parent().do_thing() and long absolute paths — they break when you restructure scenes.
# level.gd — the parent wires its children together
extends Node2D
@onready var player: Player = $Player
@onready var hud: HUD = $HUD
func _ready() -> void:
player.health_changed.connect(hud.set_health)
player.died.connect(_on_player_died)
func _on_player_died() -> void:
hud.show_game_over()
await get_tree().create_timer(2.0).timeout
get_tree().reload_current_scene()28.6 Composition with child nodes
Build behaviors as reusable component nodes:
Any entity (player, crate, enemy) gets health by adding a HealthComponent child — composition over deep inheritance.
# health_component.gd
class_name HealthComponent
extends Node
signal died
signal changed(current: int, maximum: int)
@export var max_health := 10
@onready var current := max_health
func damage(amount: int) -> void:
current = maxi(current - amount, 0)
changed.emit(current, max_health)
if current == 0:
died.emit()🧪 Part II Exercises
Create a Character base class with take_damage() and die(), then Player and Slime subclasses that override die() and call super().
Write a health property with a setter that clamps the value, emits health_changed, and updates a ProgressBar — guarded with is_node_ready().
Export ten different property types (range with suffix, enum, flags, file, node, resource, typed array, group, subgroup, multiline) and organize them in the Inspector.
Log the lifecycle callbacks of a parent with two children and explain the printed order of _enter_tree, _ready, and _exit_tree.
Build a signal-bus autoload with coin_collected and player_died; make coins, the HUD, and a sound manager communicate only through it.
Write a cutscene coroutine that fades the screen, shows three lines of dialogue (advanced by ui_accept using await), and moves the camera with a tween.
Implement a platformer controller with coyote time, jump buffering, variable jump height, and AnimatedSprite2D states.
Set up named collision layers for world, player, enemies, bullets, and pickups; make bullets hit only enemies and walls.
Make a TileMapLayer level editor: left click places a tile, right click erases, and S saves the used cells to JSON in user://.
Build a main menu, an options menu with music/SFX volume sliders (audio buses) saved to a ConfigFile, and a pause menu using PROCESS_MODE_WHEN_PAUSED.