Lesson Goal

In Lesson 2 you set naming conventions and folder structure.
Now you will turn that structure into a practical scene graph you can build on for the rest of the course.

By the end of this lesson, you will:

  • split gameplay into reusable scenes instead of one giant scene
  • define clear node ownership boundaries
  • wire communication with signals instead of brittle node-path dependencies
  • ship a small playable setup that is easy to extend

Step 1 - Build the Core Scene Hierarchy

Create or validate this baseline:

  • Main.tscn (session root, scene transitions, high-level systems)
  • World.tscn (level container, environment, encounter anchors)
  • Player.tscn (movement, combat input, local state)
  • UIRoot.tscn (HUD and prompts, no gameplay authority)

Keep each scene focused on one responsibility.

Mini task:
Open your scene tree and remove any "misc" nodes that do not clearly belong to the root scene's responsibility.


Step 2 - Compose Scenes, Do Not Copy-Paste Them

Use instancing so feature changes propagate safely:

  1. Instance Player.tscn into World.tscn.
  2. Instance UIRoot.tscn into Main.tscn.
  3. Keep level-specific content in World.tscn child scenes (for example Room_A.tscn, Room_B.tscn).

Avoid duplicating player or UI nodes directly in each level scene.

Common mistake: Creating per-level "Player2", "Player3" variants that diverge and break bug fixes later.


Step 3 - Define Node Ownership Rules

Set clear ownership before adding more features:

  • Main owns flow and transitions.
  • World owns interactables, navigation markers, and mission context.
  • Player owns movement state, animation trigger events, and combat intent.
  • UIRoot only displays data and sends user intent signals.

If ownership is unclear, refactors become dangerous and slow.

Pro tip:
Write ownership in comments at the top of each root script for quick team alignment.


Step 4 - Use Signal-First Communication

Use this pattern:

  • Player emits health_changed, stamina_changed, interaction_prompt_requested
  • UIRoot listens and updates visuals
  • World listens for gameplay events like interacted_with_object

Keep dependencies one-way where possible.

signal health_changed(current: int, max_value: int)

func apply_damage(amount: int) -> void:
    current_health = max(current_health - amount, 0)
    health_changed.emit(current_health, max_health)

This keeps scenes modular and easier to test.


Step 5 - Build a Small Validation Slice

Create a tiny playable validation scene:

  1. Player can move in one room.
  2. One interactable emits a signal to UI prompt.
  3. One damage source updates player health and HUD.
  4. Scene reload/reset works without orphaned nodes.

If this slice is stable, your foundation is healthy.

Mini challenge:
Add one "checkpoint reached" signal and log it in both World and UIRoot to prove event flow works cleanly.


Troubleshooting

  • Signal not firing: confirm connection occurs after node is ready and target node exists.
  • Null instance errors: verify scene paths and avoid hard-coded deep node paths.
  • UI updates lagging or missing: ensure UI subscribes once and is not recreated unexpectedly.
  • Duplicate event handling: check if you connect signals multiple times on re-entry.

Common Mistakes to Avoid

  • putting game flow, player logic, and UI logic in one script
  • resolving everything with get_node("../../..")
  • creating too many autoloads for feature logic that should live in scene scope
  • naming node roles by implementation detail instead of gameplay purpose

Quick Recap

You now have:

  • a clean scene composition baseline
  • explicit ownership rules
  • signal-first communication between gameplay and UI
  • a validation slice that proves your architecture can scale

In the next lesson, you will build player movement and combat basics on top of this foundation.

Bookmark this lesson and share it with teammates before you add inventory, quests, or save systems.