Loading...
Loading...
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 skill4agent add thedivergentai/gd-agentic-skills godot-compositionplayer.gdPlayer > Entity > LivingThing > Nodeget_node("Path/To/Thing")$@export# 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.class_nameclass_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 do_logic(delta: float) -> void:
# Perform specific task
passmove_dirjump_pressedattack_just_pressedclass_name InputComponent extends Node
var move_dir: Vector2
var jump_pressed: bool
func update() -> void:
# Called by Orchestrator every frame
move_dir = Input.get_vector("left", "right", "up", "down")
jump_pressed = Input.is_action_just_pressed("jump")class_name MovementComponent extends Node
@export var body: CharacterBody3D # The thing we move
@export var speed: float = 8.0
@export var jump_velocity: float = 12.0
func tick(delta: float, direction: Vector2, wants_jump: bool) -> void:
if not body: return
# Handle Gravity
if not body.is_on_floor():
body.velocity.y -= 9.8 * delta
# Handle Movement
if direction:
body.velocity.x = direction.x * speed
body.velocity.z = direction.y * speed # 3D conversion
else:
body.velocity.x = move_toward(body.velocity.x, 0, speed)
body.velocity.z = move_toward(body.velocity.z, 0, speed)
# Handle Jump
if wants_jump and body.is_on_floor():
body.velocity.y = jump_velocity
body.move_and_slide()class_name HealthComponent extends Node
signal died
signal health_changed(current, max)
@export var max_health: float = 100.0
var current_health: float
func _ready():
current_health = max_health
func damage(amount: float):
current_health = clamp(current_health - amount, 0, max_health)
health_changed.emit(current_health, max_health)
if current_health == 0:
died.emit()player.gd_physics_processclass_name Player extends CharacterBody3D
@onready var input: InputComponent = %InputComponent
@onready var move: MovementComponent = %MovementComponent
@onready var health: HealthComponent = %HealthComponent
func _ready():
# Connect signals (The ears)
health.died.connect(_on_death)
func _physics_process(delta):
# 1. Update Senses
input.update()
# 2. Pass Data to Workers (State Management)
# The Player script decides that "Input Direction" maps to "Movement Direction"
move.tick(delta, input.move_dir, input.jump_pressed)
func _on_death():
queue_free()Node