--- name: godot-composition description: "Expert architectural standards for building scalable Godot GAMES (RPGs, Platformers, Shooters) using the Composition pattern (Entity-Component). Use when designing player controllers, NPCs, enemies, weapons, or complex gameplay systems. Enforces \"Has-A\" relationships for game entities. Trigger keywords: Entity-Component, ECS, Gameplay, Actors, NPCs, Enemies, Weapons, Hitboxes, Game Loop, Level Design." --- ## Core Philosophy This skill enforces **Composition over Inheritance** ("Has-a" vs "Is-a"). In Godot, Nodes **are** components. A complex entity (Player) is simply an Orchestrator managing specialized Worker Nodes (Components). ### The Golden Rules 1. **Single Responsibility**: One script = One job. 2. **Encapsulation**: Components are "selfish." They handle their internal logic but don't know *who* owns them. 3. **The Orchestrator**: The root script (e.g., `player.gd`) does **no logic**. It only manages state and passes data between components. 4. **Decoupling**: Components communicate via **Signals** (up) and **Methods** (down). ## Decision Tree — Composition vs Autoload vs Inheritance | Situation | Choose | |-----------|--------| | Gameplay entity behaviors (HP, hitbox, move, interact) | **Composition** — child components + orchestrator ([composition_root_init.gd](scripts/composition_root_init.gd)) | | Cross-scene services (audio bus, save, net, economy ledger) | **Autoload** — not a component on the player | | True is-a engine specialization (custom Control/Node with shared lifecycle) | **Inheritance exception** — rare; never for "adds a gun" / "adds HP" | --- ## Available Scripts ### [health_component.gd](scripts/health_component.gd) Specialized Node for managing lifespan, damage logic, and death signals across any entity. ### [hit_box_component.gd](scripts/hit_box_component.gd) Area-based component for intercepting damage and delegating it to a HealthComponent. ### [hurt_box_component.gd](scripts/hurt_box_component.gd) Area-based component for dealing damage specifically to HitBoxComponents. ### [velocity_component.gd](scripts/velocity_component.gd) Encapsulated movement and acceleration logic for reuse across Players and Enemies. ### [interaction_component.gd](scripts/interaction_component.gd) Decoupled interaction handler using injecting `Callable` logic for context-aware actions. ### [follower_component.gd](scripts/follower_component.gd) Decoupled tracking logic using `NodePath` injection for smooth entity following. ### [state_component_vsm.gd](scripts/state_component_vsm.gd) Component-based state machine pattern using child nodes as individual states. ### [status_effect_component.gd](scripts/status_effect_component.gd) Managing temporary modifiers (buffs/debuffs) by stacking effect scenes as children. ### [visual_sync_component.gd](scripts/visual_sync_component.gd) Separating logical state (velocity/direction) from visual representation (sprite flipping). ### [composition_root_init.gd](scripts/composition_root_init.gd) **MANDATORY first read** — Orchestrator wiring via typed `@export` (Inspector / `%UniqueNames` in the scene). Matches NEVER: no `$` / `get_node` for components. ## NEVER Do in Composition - **NEVER use deep inheritance chains** (e.g., `Player > Entity > LivingThing > Node`) — Creates brittle "God Classes" that are hard to refactor [21]. - **NEVER use `get_node()` or `$` for components** — This breaks if the scene tree is rearranged. Always use `@export` or `%UniqueNames` [22]. - **NEVER let a component reference its parent script directly** — This makes the component impossible to reuse. Use signals or dependency injection [23]. - **NEVER mix Input, Physics, and Game Logic in one script** — This violates Single Responsibility. Split them into specialized components [24, 13]. - **NEVER create components that require a specific SceneTree structure** — A component should be "selfish" and only care about its own properties and direct children. - **NEVER use inheritance to "add a feature"** — If you want an enemy to shoot, add a `ShootingComponent`, don't make it inherit from `ShooterEnemy`. - **NEVER hardcode component dependencies** — If `CombatComponent` needs `HealthComponent`, look it up in `_ready()` or inject it via the parent [11]. - **NEVER treat Godot nodes as pure data** — Nodes provide lifecycle (`_process`) and signals. If you only need data, use a `Resource`. - **NEVER ignore the Node lifecycle in components** — Use `_enter_tree()` and `_exit_tree()` for setup/cleanup that must happen regardless of the parent's state. - **NEVER hide component points of access** — Expose `NodePath` or `Callable` properties so the parent can wire the component in the Inspector [13]. --- ## Implementation Standards ### 1. Connection Strategy: Typed Exports Do not rely on tree order. Use explicit dependency injection via `@export` with static typing. **The "Godot Way" for strict godot-composition:** ```gdscript # The Orchestrator (e.g., player.gd) class_name Player extends CharacterBody3D # Dependency Injection: Define the "slots" in the backpack @export var health_component: HealthComponent @export var movement_component: MovementComponent @export var input_component: InputComponent # Use Scene Unique Names (%) for auto-assignment in Editor # or drag-and-drop in the Inspector. ``` ### 2. Component Mindset Components must define `class_name` to be recognized as types. **Standard Component Boilerplate:** ```gdscript class_name MyComponent extends Node # Use Node for logic, Node3D/2D if it needs position @export var stats: Resource # Components can hold their own data signal happened_something(value) func _ready() -> void: _validate_dependencies() func _validate_dependencies() -> void: # 2. Dependency-Validation: Fail early during development if setup is wrong [2] # NOTE: assert() is stripped in release builds [10]. assert(stats != null, "Stats Resource missing on %s" % name) func do_logic(delta: float) -> void: # Perform specific task pass ``` --- ## Standard Components — Use Scripts > Inline Input/Movement/Health recipes removed. **MANDATORY**: start from [composition_root_init.gd](scripts/composition_root_init.gd), then load the matching script: - Health / death: [health_component.gd](scripts/health_component.gd) - Damage areas: [hit_box_component.gd](scripts/hit_box_component.gd), [hurt_box_component.gd](scripts/hurt_box_component.gd) - Motion: [velocity_component.gd](scripts/velocity_component.gd) - Interact / follow / VFX sync: [interaction_component.gd](scripts/interaction_component.gd), [follower_component.gd](scripts/follower_component.gd), [visual_sync_component.gd](scripts/visual_sync_component.gd) - States / statuses: [state_component_vsm.gd](scripts/state_component_vsm.gd), [status_effect_component.gd](scripts/status_effect_component.gd) Typed `@export` wiring stays under **Implementation Standards** above. ## Expert Composition Patterns ### 1. State-Component Pattern (FSM) Encapsulate complex behaviors into child nodes that act as states. The parent StateComponent delegates lifecycle calls to the active child [4, 6]. > **MANDATORY**: Read [state_component_vsm.gd](scripts/state_component_vsm.gd) — do not paste an inline StateMachine. For deeper VSM / hierarchical FSMs, open [godot-state-machine-advanced](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-state-machine-advanced/SKILL.md). ### 2. Component-Registry (O(1) Lookup) Avoid slow tree traversal for sibling communication. Catalog children in a Dictionary at ready (by name or group). ```gdscript var _components: Dictionary = {} func _ready() -> void: for child in get_children(): _components[child.name] = child for group in child.get_groups(): _components[group] = child func get_comp(key: StringName) -> Node: return _components.get(key) ``` ### 3. Dependency-Validation Fail fast with **`@export` asserts**, not `get_node_or_null` paths (paths break when the tree is rearranged). ```gdscript @export var health_component: HealthComponent @export var input_component: InputComponent func _ready() -> void: assert(health_component != null, "Missing HealthComponent export!") assert(input_component != null, "Missing InputComponent export!") ``` > **MANDATORY** for Input/Movement/Health orchestrator recipes and registry depth: [orchestrator-recipes.md](references/orchestrator-recipes.md). **Do NOT Load** when [composition_root_init.gd](scripts/composition_root_init.gd) + one component script suffice. ## Performance Note Nodes are lightweight. Do not fear adding 10-20 nodes per entity. The organizational benefit of Composition vastly outweighs the negligible memory cost of `Node` instances. ## 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](https://docs.godotengine.org/en/stable/tutorials/best_practices/scene_organization.html) — Canonical signal-up / call-down ownership so orchestrators wire components without sibling hard-coupling. - [Nodes and Scenes](https://docs.godotengine.org/en/stable/getting_started/step_by_step/nodes_and_scenes.html) — Why Godot treats nodes as reusable building blocks (components) assembled into entity scenes. - [What are Godot classes](https://docs.godotengine.org/en/stable/tutorials/best_practices/what_are_godot_classes.html) — Prefer scene composition and `class_name` components over deep inheritance trees for gameplay entities. - [When and how to avoid using nodes for everything](https://docs.godotengine.org/en/stable/tutorials/best_practices/node_alternatives.html) — Keep pure data in Resources; reserve Nodes for lifecycle, signals, and process ticks. - [Logic preferences](https://docs.godotengine.org/en/stable/tutorials/best_practices/logic_preferences.html) — Placement of game logic across scene trees so parents orchestrate and children stay single-purpose. - [Data preferences](https://docs.godotengine.org/en/stable/tutorials/best_practices/data_preferences.html) — Choose Node vs Resource vs plain data for stats and config that components consume. - [Using signals](https://docs.godotengine.org/en/stable/getting_started/step_by_step/signals.html) — Past-tense component events (`health_depleted`, `state_changed`) parents connect without reverse dependencies. - [GDScript exported properties](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_exports.html) — Typed `@export` slots for Inspector dependency injection instead of brittle `$` paths. - [Scene Unique Nodes](https://docs.godotengine.org/en/stable/tutorials/scripting/scene_unique_nodes.html) — `%Name` lookups that survive scene-tree reorders when wiring composition roots. - [Groups](https://docs.godotengine.org/en/stable/tutorials/scripting/groups.html) — Tag components for O(1)-style registry / interface-like lookup without inheritance. - [Godot notifications](https://docs.godotengine.org/en/stable/tutorials/best_practices/godot_notifications.html) — Safe `_ready` / enter-tree timing for validating and connecting component dependencies. - [Resources](https://docs.godotengine.org/en/stable/tutorials/scripting/resources.html) — Share tunables (max health, speeds) as Resources so components stay reusable across entities. ### Related Skills #### Prerequisites - [godot-project-foundations](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-project-foundations/SKILL.md) — Scene ownership, project layout, and Inspector wiring conventions every composition root assumes. - [godot-gdscript-mastery](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-gdscript-mastery/SKILL.md) — `class_name`, typed `@export`, Callables, and assert patterns required for typed component APIs. - [godot-signal-architecture](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-signal-architecture/SKILL.md) — Signal-up / call-down connect hygiene so selfish components never grab parent scripts. #### Complements - [godot-resource-data-patterns](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-resource-data-patterns/SKILL.md) — Stats and effect definitions as Resources; composition nodes own runtime mutation and emit change events. - [godot-state-machine-advanced](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-state-machine-advanced/SKILL.md) — Child-node FSM / VSM patterns that plug in as a StateComponent without bloating the orchestrator. - [godot-input-handling](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-input-handling/SKILL.md) — Sense-layer InputComponents that only sample actions; parents pass directions into movement components. - [godot-characterbody-2d](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-characterbody-2d/SKILL.md) — Physics-body movement APIs VelocityComponents and composition roots call via `move_and_slide`. - [godot-2d-physics](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-2d-physics/SKILL.md) — Area2D layers/masks and overlap rules HitBox/HurtBox/Interaction components depend on. - [godot-scene-management](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-scene-management/SKILL.md) — Spawn/despawn entities as composed scenes and re-wire exports when instances are swapped. #### Downstream / consumers - [godot-combat-system](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-combat-system/SKILL.md) — Damage pipelines assemble Health/HitBox/HurtBox components under combat orchestrators. - [godot-ability-system](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-ability-system/SKILL.md) — Abilities attach as composed workers (cooldowns, targeting) rather than subclassing every caster. - [godot-rpg-stats](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-rpg-stats/SKILL.md) — Stat sheets feed Health/StatusEffect components as Resources plus change signals. - [godot-monte-carlo-balancer](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-monte-carlo-balancer/SKILL.md) — Simulate tunable component exports (HP, damage, speeds) before locking entity kits. - [godot-composition-apps](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-composition-apps/SKILL.md) — Same Has-A node composition applied to tools/apps rather than gameplay entities. #### Master - [godot-master](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-master/SKILL.md) — Library router and mirrored module entry; open when discovering which Domain Skill owns a cross-cutting architecture concern.