Godot Composition & Architecture (Apps & UI)
Decision Gate β App vs Gameplay Entity
| Root node / task | Route |
|---|---|
| Control, EditorPlugin, tool window, settings dock, form UI | Stay here β Orchestrator + components |
| Player, Enemy, Weapon, Hitbox, gameplay CharacterBody | godot-composition β not this skill |
App-only gate: If the node is a gameplay actor (Player/Enemy/Weapon/Hitbox), use godot-composition. This skill owns Control / EditorPlugin / tool composition.
The Core Philosophy
The Litmus Test (Rock Test)
Before writing a script, ask: "If I attached this script to a literal rock, would it still function?"
- Pass: An
AuthComponenton a rock allows the rock to log in. (Context Agnostic) - Fail: A
LoginFormscript on a rock tries to grab text fields the rock doesn't have. (Coupled)
MANDATORY: Validate new components with comp_rock_test_boilerplate.gd.
The Backpack Model (Has-A > Is-A)
Treat the Root Node as an empty Backpack.
- Wrong:
SubmitButtonextendsAnimatedButtonextendsBaseButton. - Right: Root HAS-A
AnimationComponentand HAS-ANetworkRequestComponent.
The Hierarchy of Power (Communication Rules)
| Direction | Source β Target | Method | Reason |
|---|---|---|---|
| Downward | Orchestrator β Component | Function Call | Manager owns the workers. |
| Upward | Component β Orchestrator | Signals | Workers are blind. |
| Sideways | Component A β Component B | FORBIDDEN | Siblings never talk directly. |
Sideways Fix: Component A signals the Orchestrator; Orchestrator calls Component B.
Available Scripts
MANDATORY: Read the matching script before implementing the pattern. Do not reinvent Orchestrator wiring inline.
comp_rock_test_boilerplate.gd
MANDATORY first read β Attach-candidate-to-literal-rock harness that fails hard-coupled components early.
comp_orchestrator_base.gd
MANDATORY when creating any App/UI root β Signal-up / call-down wiring skeleton (0% business math).
comp_logic_visual_syncer.gd
MANDATORY for VLS β Logic emits state_changed; visuals/animations react without logic knowing AnimationPlayer/Theme.
comp_base_component.gd
Shared component lifecycle + dependency validation for app workers.
comp_dependency_injector.gd
Typed export / registry injection so Orchestrators avoid brittle $ paths.
clipboard_copier.gd
Context-agnostic clipboard worker β pairs with orchestrator toast pattern (see references).
comp_data_driven_config.gd
Resource-backed config for tool settings and form defaults.
comp_persistence_component.gd
MANDATORY for saveable UI/tool state β Registers Saveable group + get_save_data() without putting I/O in visuals.
comp_ability_sequencer.gd
Ordered multi-step tool workflows (wizard pages, export pipelines) as child steps.
comp_health_component.gd / comp_hitbox_component.gd
Only when an app/tool simulates entities; prefer godot-composition for real games.
The Orchestrator Pattern
Root script (LoginScreen.gd, UserProfile.gd, EditorPlugin dock root) is an Orchestrator:
- Math/Logic: 0% Β· State wiring: 100%
- Job: listen to component signals β call other component methods
MANDATORY: Extend patterns from comp_orchestrator_base.gd.
| Concept | App/UI Example |
|---|---|
| Orchestrator | UserProfile.gd / Editor dock root |
| Logic component | AuthValidator |
| VLS | AuthVisualSyncer via comp_logic_visual_syncer.gd |
| Theme ownership | Separate theme component β never mutated inside form logic |
| Focus ownership | Orchestrator grants/releases Control focus; components never steal siblings' focus |
Implementation Standards
- Type Safety β
class_nameon components; no untyped core architecture. - Dependency Injection β
@export var auth: AuthComponent(Inspector /%UniqueNames). NEVERget_node("Path/To/Child")for components. - Stateless workers β Orchestrator passes data into functions; components do not scrape sibling Controls.
NEVER Do (Expert Architectural Rules)
Hierarchy & Dependencies
- NEVER use get_parent() to fetch data β Inject via
@exportor function args. - NEVER talk sideways β Signal up; Orchestrator calls down.
- NEVER use brittle Node Paths β Prefer
@export/%.
Logic & State
- NEVER put business logic in the Orchestrator β Only
_on_signaldelegators. - NEVER store global state in individual components β Shared Context Resource or Autoload.
- NEVER assume a component's parent is a specific type β Rock Test failure.
Polish & Orchestration
- NEVER skip signal cleanup β Disconnect on exit / use CONNECT_ONE_SHOT where appropriate.
- NEVER let Logic know about Visuals β Emit; VLS / Orchestrator plays animations and applies Theme.
Fragile App Workflow: Saveable + Theme Ownership
Do not put save I/O or Theme mutation inside form Controls. Route through components:
- MANDATORY comp_persistence_component.gd on the Orchestrator (or a dedicated Saveable child) β
add_to_group("Saveable")+get_save_data(). - Theme / StyleBox changes belong in a theme component (theme_manager.gd) called down by the Orchestrator after logic signals success/failure.
- Focus: Orchestrator owns
grab_focus()after validation failures so logic stays Control-agnostic.
# settings_dock_orchestrator.gd (pattern β wire via @export, not $)
extends Control
@export var persistence: CompPersistenceComponent
@export var theme_mgr: Node # theme_manager.gd API
@export var form_logic: Node
func _ready() -> void:
form_logic.settings_valid.connect(_on_settings_valid)
form_logic.settings_invalid.connect(_on_settings_invalid)
func _on_settings_valid(payload: Dictionary) -> void:
theme_mgr.apply_user_theme(payload.get("theme_id"))
# Save systems collect via Saveable group β persistence component stays dumb
func _on_settings_invalid(field: StringName) -> void:
# Orchestrator owns focus; logic never touches sibling LineEdits
var target := get_node_or_null("%" + String(field))
if target is Control:
target.grab_focus()Expert Composition Patterns (Apps)
1. App-Level Service Locator
Prefer Engine.register_singleton() for lightweight non-Node services (Auth, Config) instead of dozens of Autoload Nodes [6].
2. Visual-Logic-Syncers (VLS)
MANDATORY comp_logic_visual_syncer.gd β logic never calls AnimationPlayer.play().
3. O(1) Component Registry
Orchestrator Dictionary registry for dashboard modules β still no sideways calls; registry is Orchestrator-private lookup.
MANDATORY for clipboard/share orchestrator examples and service-locator depth: app-orchestrator-examples.md. Do NOT Load when comp_orchestrator_base.gd covers your screen.
Reference
Progressive disclosure: open Official Documentation links only when researching a specific API; load Related Skills when routing to a peer domain β do not preload the whole lattice.
Official Documentation
- Scene organization β Canonical signal-up / call-down ownership so Orchestrators wire components without sibling coupling.
- When and how to avoid using nodes for everything β Prefer Resources/RefCounted for pure data and logic services so components stay lean and rock-testable.
- Godot interfaces β Duck-typed method contracts (
has_method) that let composition work without deep inheritance trees. - What are Godot classes? β Why Godot favors scene composition (Has-A) over classical Is-A hierarchies for reusable behaviors.
- Using signals β Upward componentβOrchestrator events that keep workers blind to parents and siblings.
- GDScript exports β Typed
@exportdependency injection that replaces brittleget_nodepaths in the Inspector. - Scene Unique Nodes β
%UniqueNamefor Orchestrator-local Control/Button wiring without string path fragility. - Resources β Data-driven
.tresconfigs so values stay outside logic components. - Groups β Mass registration (e.g. Saveable/Components) for Orchestrator registries without hard sibling refs.
- Autoloads versus regular nodes β When a scene-local Orchestrator beats a global Autoload for app/UI composition.
- Singletons (Autoload) β Safe registration of cross-scene services when a true app-level locator is justified.
- Saving games β Persistence patterns that map cleanly onto modular saveable components.
Related Skills
Prerequisites
- godot-project-foundations β Project layout, Autoload registration, and scene ownership that Orchestrators and components plug into.
- godot-gdscript-mastery β Typed exports, signals, and
class_namefluency required before dependency injection and rock-testable components. - godot-composition β Core Has-A component model (game-focused sibling); this skill specializes the same rules for Apps/Tools/UI.
Complements
- godot-signal-architecture β Connect flags, ghost cleanup, and EventBus patterns Orchestrators use for upward wiring.
- godot-autoload-architecture β Boot order and ownership when composition needs a thin global service instead of scene-local state.
- godot-resource-data-patterns β Custom Resources and hot-swap
.tresconfigs that feed data-driven components. - godot-ui-containers β Control trees that should signal intent upward while Orchestrators call down into layout.
- godot-ui-theming β Theme/visual syncers stay separate from auth/form logic under the VLS pattern.
- godot-scene-management β Scene swaps must re-inject exports and reconnect Orchestrator wiring without sideways sibling links.
- godot-testing-patterns β Rock-test and signal spies that prove components stay context-agnostic.
Downstream / consumers
- godot-save-load-systems β Consumes Saveable-group persistence components for modular app state.
- godot-ability-system β Ability nodes as child components sequenced by an Orchestrator without inheritance trees.
- godot-state-machine-advanced β State nodes compose beside logic/visual syncers; FSM owns transitions, not sibling chatter.
Master
- godot-master β Library router and mirrored module entry; open when discovering which Domain Skill owns a cross-cutting architecture concern.