# Nodes and Signals build a scene tree and wire nodes together with signals. ```{raw} html ▶ Run in browser ``` **Tags:** `tutorial` `beginner` `signals` `nodes` # Nodes and Signals Your first window showed a single node drawing itself. Real games are *many* nodes that need to react to each other: a score label updates when an enemy dies, a health bar shrinks when the player is hit. The clean way to wire that up is **signals**, and this tutorial builds the smallest example that shows why. By the end you will have a scene tree of three nodes where a `Player` drives a `HealthBar` without ever referencing it. ## Step 1: Build a scene tree Nodes form a tree. A node becomes part of the running game when you `add_child()` it onto a node already in the tree. The root `Game` node adds two children in `on_ready()`: ```python class Game(Node2D): def on_ready(self): self.player = self.add_child(Player(position=Vec2(400, 300))) self.bar = self.add_child(HealthBar(position=Vec2(250, 60))) ``` `add_child()` returns the child, so you can keep a handle to it. Every child gets its own `on_ready()`, `on_update(dt)` and `on_draw(renderer)` hooks. ## Step 2: Declare a signal A signal is an event a node can announce. Create one as an attribute in `on_ready()` (children are made ready before their parent wires them, so the signal exists in time to connect) and `emit()` it when the event happens: ```python class Player(Node2D): def on_ready(self): self.max_health = 100 self.health = self.max_health self.health_changed = Signal() # carries (current, maximum) self.died = Signal() # bare "it happened" event def take_damage(self, amount): self.health = max(0, self.health - amount) self.health_changed.emit(self.health, self.max_health) if self.health == 0: self.died.emit() ``` The `Player` announces what happened. It does **not** know or care who is listening. ## Step 3: Connect a listener The `HealthBar` exposes a plain method and draws whatever it was last told: ```python class HealthBar(Node2D): def on_health_changed(self, current, maximum): self._ratio = current / maximum ``` The root wires the emitter to the listener with `connect()`: ```python self.player.health_changed.connect(self.bar.on_health_changed) self.player.died.connect(self._on_player_died) ``` Now every `take_damage()` fans out to every connected listener. Add a second `HealthBar` and connect it too: no change to `Player`. That decoupling is the whole reason signals exist. ## Step 4: Drive it `Game.on_update(dt)` damages the player once a second, and revives it when it dies, so you can watch the bar drain and reset: ```python def on_update(self, dt): self._elapsed += dt if self._elapsed >= 1.0: self._elapsed = 0.0 self.player.take_damage(10) def _on_player_died(self): self.player.revive() ``` ## Run it ```bash uv run python examples/tutorials/nodes_and_signals/main.py ``` ## What's next - **Input and Movement** -- drive a node from the keyboard with input actions. - **Bouncing Balls** -- `Property` descriptors and many children at once. ## Source ```{literalinclude} ../../examples/tutorials/nodes_and_signals/main.py :language: python :linenos: ```