thedivergentai/godot-composition
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.
npx skills add https://github.com/thedivergentai/GD-Agentic-Skills --skill godot-composition
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).
player.gd) does no logic. It only manages state and passes data between components.| Situation | Choose |
|-----------|--------|
| Gameplay entity behaviors (HP, hitbox, move, interact) | Composition — child components + orchestrator (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" |
Specialized Node for managing lifespan, damage logic, and death signals across any entity.
Area-based component for intercepting damage and delegating it to a HealthComponent.
Area-based component for dealing damage specifically to HitBoxComponents.
Encapsulated movement and acceleration logic for reuse across Players and Enemies.
Decoupled interaction handler using injecting Callable logic for context-aware actions.
Decoupled tracking logic using NodePath injection for smooth entity following.
Component-based state machine pattern using child nodes as individual states.
Managing temporary modifiers (buffs/debuffs) by stacking effect scenes as children.
Separating logical state (velocity/direction) from visual representation (sprite flipping).
MANDATORY first read — Orchestrator wiring via typed @export (Inspector / %UniqueNames in the scene). Matches NEVER: no $ / get_node for components.
Player > Entity > LivingThing > Node) — Creates brittle "God Classes" that are hard to refactor [21].get_node() or $ for components — This breaks if the scene tree is rearranged. Always use @export or %UniqueNames [22].ShootingComponent, don't make it inherit from ShooterEnemy.CombatComponent needs HealthComponent, look it up in _ready() or inject it via the parent [11]._process) and signals. If you only need data, use a Resource._enter_tree() and _exit_tree() for setup/cleanup that must happen regardless of the parent's state.NodePath or Callable properties so the parent can wire the component in the Inspector [13].Do not rely on tree order. Use explicit dependency injection via @export with static typing.
The "Godot Way" for strict godot-composition:
# 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.
Components must define class_name to be recognized as types.
Standard Component Boilerplate:
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
> Inline Input/Movement/Health recipes removed. MANDATORY: start from composition_root_init.gd, then load the matching script:
Typed @export wiring stays under Implementation Standards above.
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 — do not paste an inline StateMachine. For deeper VSM / hierarchical FSMs, open godot-state-machine-advanced.
Avoid slow tree traversal for sibling communication. Catalog children in a Dictionary at ready (by name or group).
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)
Fail fast with @export asserts, not get_node_or_null paths (paths break when the tree is rearranged).
@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. Do NOT Load when composition_root_init.gd + one component script suffice.
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.
> 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.
class_name components over deep inheritance trees for gameplay entities.health_depleted, state_changed) parents connect without reverse dependencies.@export slots for Inspector dependency injection instead of brittle $ paths.%Name lookups that survive scene-tree reorders when wiring composition roots._ready / enter-tree timing for validating and connecting component dependencies.class_name, typed @export, Callables, and assert patterns required for typed component APIs.move_and_slide.Take thedivergentai/godot-composition from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.