/component-system
Use when building reusable node components — composition patterns, component communication, and interface design
$ npx -y skills add jame581/GodotPrompter --skill component-system --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/component-system
Context preview
The summary Claude sees to decide when to auto-load this skill.
Use when building reusable node components — composition patterns, component communication, and interface design
SKILL.md
component-system.SKILL.mdname: component-system
description: Use when building reusable node components — composition patterns, component communication, and interface design
Component System in Godot 4.3+
Build behavior through composition. Attach small, focused components to any entity rather than climbing an inheritance chain. All examples target Godot 4.3+ with no deprecated APIs.
> **Related skills:** **scene-organization** for scene tree composition, **event-bus** for decoupled component communication, **resource-pattern** for data-driven component configuration, **physics-system** for Area2D/3D overlap detection and collision shapes, **ability-system** for an AbilityComponent example built on this pattern.
---
1. Why Components
| Problem with inheritance | How components solve it | |--------------------------|-------------------------| | Deep chains are brittle — change one class, break many | Each component is an isolated scene with a single job | | Sharing behavior across unrelated entities requires awkward base classes | Drop a component onto any entity that needs that behavior | | Adding a new combination means a new subclass | Mix and match components freely at the scene level |
Key benefits:
- **Reuse across entities** — a `HealthComponent` works on a player, an enemy, a destructible crate, or a boss with no code changes.
- **Separation of concerns** — damage detection, health tracking, and state animation are each their own file. Debugging is local.
- **Mix-and-match behaviors** — give an enemy a `HitboxComponent` and a `PatrolComponent` independently. Removing one does not affect the other.
---
2. Component Design Rules
1. **One responsibility per component.** If you find yourself naming it `HealthAndShieldAndRegenComponent`, split it. 2. **Communicate via signals, not direct sibling access.** A component must not call `get_parent().get_node("SiblingComponent")`. Emit a signal instead. 3. **Stateless where possible.** Prefer deriving state from inputs and `@export` configuration over storing mutable state. When state is necessary, keep it private. 4. **Use `@export` for all configuration.** Damage amount, cooldown duration, and layer masks belong in the Inspector, not hardcoded constants.
---
3. Common Components
| Component | Purpose | Key Signals | |---|---|---| | `HealthComponent` | Tracks current and max HP, applies damage and healing | `health_changed(current, maximum)`, `died` | | `HitboxComponent` | Detects overlapping hurtboxes and triggers damage | `hit(target_hurtbox)` | | `HurtboxComponent` | Receives hits, routes damage to `HealthComponent` | `hurt(damage_amount)` | | `InteractableComponent` | Marks an entity as interactable and fires on player overlap | `interacted(interactor)` | | `StateMachineComponent` | Delegates `_process` and `_physics_process` to child state nodes | `state_changed(from, to)` |
---
4. HitboxComponent
Attach to any entity that deals damage. Configure `damage` in the Inspector.
GDScript (`hitbox_component.gd`)
class_name HitboxComponent
extends Area2D
## Damage dealt to the target hurtbox on contact.
@export var damage: int = 10
## Minimum seconds between successive hits (0 = no cooldown).
@export var cooldown_duration: float = 0.5
signal hit(target_hurtbox: HurtboxComponent)
var _on_cooldown: bool = false
@onready var _cooldown_timer: Timer = _build_timer()
func _ready() -> void:
area_entered.connect(_on_area_entered)
func _on_area_entered(area: Area2D) -> void:
if _on_cooldown:
return
if area is not HurtboxComponent:
return
hit.emit(area)
area.receive_hit(damage)
if cooldown_duration > 0.0:
_on_cooldown = true
_cooldown_timer.start(cooldown_duration)
func _on_cooldown_timeout() -> void:
_on_cooldown = false
func _build_timer() -> Timer:
var t := Timer.new()
t.one_shot = true
t.timeout.connect(_on_cooldown_timeout)
add_child(t)
return t
C# (`HitboxComponent.cs`)
using Godot;
public partial class HitboxComponent : Area2D
{
/// <summary>Damage dealt to the target hurtbox on contact.</summary>
[Export] public int Damage { get; set; } = 10;
/// <summary>Minimum seconds between successive hits (0 = no cooldown).</summary>
[Export] public float CooldownDuration { get; set; } = 0.5f;
[Signal] public delegate void HitEventHandler(HurtboxComponent targetHurtbox);
private bool _onCooldown;
private Timer _cooldownTimer;
public override void _Ready()
{
_cooldownTimer = new Timer { OneShot = true };
_cooldownTimer.Timeout += OnCooldownTimeout;
AddChild(_cooldownTimer);
AreaEntered += OnAreaEntered;
}
private void OnAreaEntered(Area2D area)
{
if (_onCooldown) return;
if (area is not HurtboxComponent hurtbox) return;
EmitSignal(SignalName.Hit, hurtbox);
hurtbox.ReceiveHit(Damage);
if (CooldownDuration > 0f)
{
_onCooldown = true;
_cooldownTimer.Start(CooldownDuration);
}
}
private void OnCooldownTimeout() => _onCooldown = false;
}---
5. HurtboxComponent
Attach to any entity that can take damage. Wire it to a sibling `HealthComponent` via `@export`.
GDScript (`hurtbox_component.gd`)
class_name HurtboxComponent
extends Area2D
## Reference to the HealthComponent on the same entity.
@export var health_component: HealthComponent
## Invincibility frame duration in seconds (0 = none).
@export var invincibility_duration: float = 0.0
signal hurt(damage_amount: int)
var _invincible: bool = false
@onready var _iframes_timer: Timer = _build_timer()
func receive_hit(damage: int) -> void:
if _invincible:
return
hurt.emit(damage)
if health_component:
health_component.take_damage(damage)
if invincibility_duration > 0.0:
_invincible = true
_iframes_timer.start(invincibility_duration)
func _on_iframes_timeout() -> void:
_invincible = false
func _build_timer() ->
Read more
name: component-system description: Use when building reusable node components — composition patterns, component communication, and interface design
Component System in Godot 4.3+
Build behavior through composition. Attach small, focused components to any entity rather than climbing an inheritance chain. All examples target Godot 4.3+ with no deprecated APIs.
> **Related skills:** **scene-organization** for scene tree composition, **event-bus** for decoupled component communication, **resource-pattern** for data-driven component configuration, **physics-system** for Area2D/3D overlap detection and collision shapes, **ability-system** for an AbilityComponent example built on this pattern.
---
1. Why Components
| Problem with inheritance | How components solve it | |--------------------------|-------------------------| | Deep chains are brittle — change one class, break many | Each component is an isolated scene with a single job | | Sharing behavior across unrelated entities requires awkward base classes | Drop a component onto any entity that needs that behavior | | Adding a new combination means a new subclass | Mix and match components freely at the scene level |
Key benefits:
- **Reuse across entities** — a `HealthComponent` works on a player, an enemy, a destructible crate, or a boss with no code changes.
- **Separation of concerns** — damage detection, health tracking, and state animation are each their own file. Debugging is local.
- **Mix-and-match behaviors** — give an enemy a `HitboxComponent` and a `PatrolComponent` independently. Removing one does not affect the other.
---
2. Component Design Rules
1. **One responsibility per component.** If you find yourself naming it `HealthAndShieldAndRegenComponent`, split it. 2. **Communicate via signals, not direct sibling access.** A component must not call `get_parent().get_node("SiblingComponent")`. Emit a signal instead. 3. **Stateless where possible.** Prefer deriving state from inputs and `@export` configuration over storing mutable state. When state is necessary, keep it private. 4. **Use `@export` for all configuration.** Damage amount, cooldown duration, and layer masks belong in the Inspector, not hardcoded constants.
---
3. Common Components
| Component | Purpose | Key Signals | |---|---|---| | `HealthComponent` | Tracks current and max HP, applies damage and healing | `health_changed(current, maximum)`, `died` | | `HitboxComponent` | Detects overlapping hurtboxes and triggers damage | `hit(target_hurtbox)` | | `HurtboxComponent` | Receives hits, routes damage to `HealthComponent` | `hurt(damage_amount)` | | `InteractableComponent` | Marks an entity as interactable and fires on player overlap | `interacted(interactor)` | | `StateMachineComponent` | Delegates `_process` and `_physics_process` to child state nodes | `state_changed(from, to)` |
---
4. HitboxComponent
Attach to any entity that deals damage. Configure `damage` in the Inspector.
GDScript (`hitbox_component.gd`)
class_name HitboxComponent extends Area2D ## Damage dealt to the target hurtbox on contact. @export var damage: int = 10 ## Minimum seconds between successive hits (0 = no cooldown). @export var cooldown_duration: float = 0.5 signal hit(target_hurtbox: HurtboxComponent) var _on_cooldown: bool = false @onready var _cooldown_timer: Timer = _build_timer() func _ready() -> void: area_entered.connect(_on_area_entered) func _on_area_entered(area: Area2D) -> void: if _on_cooldown: return if area is not HurtboxComponent: return hit.emit(area) area.receive_hit(damage) if cooldown_duration > 0.0: _on_cooldown = true _cooldown_timer.start(cooldown_duration) func _on_cooldown_timeout() -> void: _on_cooldown = false func _build_timer() -> Timer: var t := Timer.new() t.one_shot = true t.timeout.connect(_on_cooldown_timeout) add_child(t) return t
C# (`HitboxComponent.cs`)
using Godot;
public partial class HitboxComponent : Area2D
{
/// <summary>Damage dealt to the target hurtbox on contact.</summary>
[Export] public int Damage { get; set; } = 10;
/// <summary>Minimum seconds between successive hits (0 = no cooldown).</summary>
[Export] public float CooldownDuration { get; set; } = 0.5f;
[Signal] public delegate void HitEventHandler(HurtboxComponent targetHurtbox);
private bool _onCooldown;
private Timer _cooldownTimer;
public override void _Ready()
{
_cooldownTimer = new Timer { OneShot = true };
_cooldownTimer.Timeout += OnCooldownTimeout;
AddChild(_cooldownTimer);
AreaEntered += OnAreaEntered;
}
private void OnAreaEntered(Area2D area)
{
if (_onCooldown) return;
if (area is not HurtboxComponent hurtbox) return;
EmitSignal(SignalName.Hit, hurtbox);
hurtbox.ReceiveHit(Damage);
if (CooldownDuration > 0f)
{
_onCooldown = true;
_cooldownTimer.Start(CooldownDuration);
}
}
private void OnCooldownTimeout() => _onCooldown = false;
}---
5. HurtboxComponent
Attach to any entity that can take damage. Wire it to a sibling `HealthComponent` via `@export`.
GDScript (`hurtbox_component.gd`)
class_name HurtboxComponent extends Area2D ## Reference to the HealthComponent on the same entity. @export var health_component: HealthComponent ## Invincibility frame duration in seconds (0 = none). @export var invincibility_duration: float = 0.0 signal hurt(damage_amount: int) var _invincible: bool = false @onready var _iframes_timer: Timer = _build_timer() func receive_hit(damage: int) -> void: if _invincible: return hurt.emit(damage) if health_component: health_component.take_damage(damage) if invincibility_duration > 0.0: _invincible = true _iframes_timer.start(invincibility_duration) func _on_iframes_timeout() -> void: _invincible = false func _build_timer() ->
Agentic skills framework for Godot 4.x game development. Gives AI coding agents domain-specific expertise for GDScript and C# projects.
Other skills on godot-prompter.
- /authoring-godot-prompter-skills
Use when writing or editing a SKILL.md or an agent definition in this repo — required frontmatter, section ordering, and the GDScript-then-C# example convention.
Open skill - /releasing-godot-prompter
Use when cutting a GodotPrompter release or bumping its version — the version-bump sequence, tag-triggered workflow, and the marketplace manifests that must follow.
Open skill - /2d-essentials
Use when working with 2D-specific systems — TileMaps, parallax scrolling, 2D lights and shadows, canvas layers, particles 2D, custom drawing, and 2D meshes in Godot 4.3+
Open skill - /3d-essentials
Use when working with 3D-specific systems — materials, lighting, shadows, environment, global illumination, fog, LOD, occlusion culling, and decals in Godot 4.3+
Open skill - /ability-system
Use when building character abilities — Resource-based abilities with cost/cooldown/cast, buffs/debuffs, stat modifiers, gameplay tags, and HUD binding
Open skill - /addon-development
Use when creating Godot editor plugins — EditorPlugin, @tool scripts, custom inspectors, and dock panels
Open skill

