# Patterns Cross-cutting idioms that don't belong to one class. Each entry shows the canonical API for a pattern Godot and other engine users will search for. ## Autoloads — persistent singletons An autoload is a node registered on the `SceneTree` itself. It lives outside the scene root, so `change_scene()` leaves it untouched — perfect for global state (score, settings, audio manager). ```python class GameState(Node): lives = Property(3) score = Property(0) # Register once, at app start app.tree.add_autoload("GameState", GameState()) # Reach it from anywhere in the scene gs = self.tree.autoloads["GameState"] gs.score += 100 ``` `SceneTree.add_autoload(name, node)` calls `_enter_tree()` and `ready()` immediately. Remove with `remove_autoload(name)`. Autoloads preserve their groups and unique-name registrations across `change_scene`. ## Scene transitions Swap the active root to move between screens. Autoloads persist; scene-local groups and unique nodes are rebuilt for the new tree. ```python class TitleScreen(Node): def ready(self): self["PlayButton"].pressed.connect(self._on_play) def _on_play(self): self.tree.change_scene(GameScene()) class GameScene(Node): def ready(self): # ... gameplay setup ... self.player.died.connect(self._on_death) def _on_death(self): self.tree.change_scene(GameOverScreen(score=self.score)) ``` `change_scene(new_root)` runs `_exit_tree()` on the old root and `_ready_recursive()` on the new one — exactly the same path as the initial root. Works from any node via `self.tree`. ## Signals — decoupled events Declare as class attributes; connect and emit on instances: ```python class Enemy(Node): died = Signal() # no args damaged = Signal(int) # typed: emits one int hit_by = Signal(int, str) # typed: (amount, source) # Connect enemy.died.connect(self._on_enemy_died) enemy.damaged.connect(lambda hp: self.show_damage(hp)) enemy.hit_by.connect(self._log_hit, once=True) # auto-disconnects after first fire # Emit enemy.died() # or enemy.died.emit() enemy.damaged(42) ``` `connect()` returns a `Connection` handle; call `.disconnect()` on it, or pass the callback back to `signal.disconnect(fn)`. `once=True` is a one-shot connection. ## Typed signals — arity validation When a signal declares types, `connect()` inspects the callback's signature and warns if it can't accept the emitted arguments: ```python health_changed = Signal(int, int) # old_hp, new_hp def bad(hp): # only accepts one arg print(hp) health_changed.connect(bad) # logs: accepts at most 1 arg (signal emits 2) ``` The check fires at connect time, not emit time, so bugs surface without needing to run the emitting code path. Callables with `*args` are always accepted. Types themselves are advisory — nothing enforces runtime type checks on the emitted values. `Signal[int, str]` bracket syntax works the same as `Signal(int, str)`. ## Input actions in `ready()` Register input bindings **inside the root node's `ready()`**, never at module scope: ```python class Game(Node): def ready(self): InputMap.add_action("jump", [Key.SPACE]) InputMap.add_action("move", [Key.A, Key.D, Key.LEFT, Key.RIGHT]) ``` Reason: the web exporter skips the module's `if __name__ == "__main__"` block and module-level statements, so `InputMap.add_action(...)` calls written there are silently dropped. Actions registered during `ready()` run regardless of entry point and work on desktop and web builds. Query actions with `Input.is_action_pressed("jump")`, `Input.is_action_just_pressed("jump")`, `Input.get_strength("jump")` (0.0–1.0), or `Input.get_vector("left", "right", "up", "down")` for a normalised 2D direction.