godot-composition
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGodot Composition Architecture
Godot组合架构
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).
本技能遵循组合优于继承(“拥有” vs “是”)原则。
在Godot中,Node就是组件。一个复杂实体(如玩家)只是一个协调器(Orchestrator),负责管理专门的工作节点(Worker Nodes,即组件)。
The Golden Rules
黄金准则
- Single Responsibility: One script = One job.
- Encapsulation: Components are "selfish." They handle their internal logic but don't know who owns them.
- The Orchestrator: The root script (e.g., ) does no logic. It only manages state and passes data between components.
player.gd - Decoupling: Components communicate via Signals (up) and Methods (down).
- 单一职责:一个脚本只负责一项工作。
- 封装性:组件是“独立”的。它们处理自身内部逻辑,但无需知晓自己的所属对象。
- 协调器原则:根脚本(如)不处理任何逻辑,仅负责管理状态以及在组件间传递数据。
player.gd - 解耦原则:组件通过**信号(Signals,向上传递)和方法(Methods,向下调用)**进行通信。
Anti-Patterns (NEVER Do This)
反模式(绝对不要这样做)
- NEVER use deep inheritance chains (e.g., ). This creates brittle "God Classes."
Player > Entity > LivingThing > Node - NEVER use or
get_node("Path/To/Thing")syntax for components. This breaks if the scene tree changes.$ - NEVER let components reference the Parent directly (unless absolutely necessary via typed injection).
- NEVER mix Input, Physics, and Game Logic in a single script.
- 绝对不要使用深度继承链(如)。这会导致脆弱的“上帝类”(God Classes)。
Player > Entity > LivingThing > Node - 绝对不要对组件使用或
get_node("Path/To/Thing")语法。如果场景树结构变化,这种写法会失效。$ - 绝对不要让组件直接引用父节点(除非通过类型注入的绝对必要场景)。
- 绝对不要在单个脚本中混合输入、物理和游戏逻辑。
Implementation Standards
实现标准
1. Connection Strategy: Typed Exports
1. 连接策略:类型化导出
Do not rely on tree order. Use explicit dependency injection via with static typing.
@exportThe "Godot Way" for strict godot-composition:
gdscript
undefined不要依赖节点树顺序。使用带静态类型的实现显式依赖注入。
@export严格遵循Godot组合模式的“Godot方式”:
gdscript
undefinedThe Orchestrator (e.g., player.gd)
The Orchestrator (e.g., player.gd)
class_name Player extends CharacterBody3D
class_name Player extends CharacterBody3D
Dependency Injection: Define the "slots" in the backpack
Dependency Injection: Define the "slots" in the backpack
@export var health_component: HealthComponent
@export var movement_component: MovementComponent
@export var input_component: InputComponent
@export var health_component: HealthComponent
@export var movement_component: MovementComponent
@export var input_component: InputComponent
Use Scene Unique Names (%) for auto-assignment in Editor
Use Scene Unique Names (%) for auto-assignment in Editor
or drag-and-drop in the Inspector.
or drag-and-drop in the Inspector.
undefinedundefined2. Component Mindset
组件设计思路
Components must define to be recognized as types.
class_nameStandard Component Boilerplate:
gdscript
class_name MyComponent extends Node组件必须定义才能被识别为类型。
class_name标准组件模板:
gdscript
class_name MyComponent extends NodeUse Node for logic, Node3D/2D if it needs position
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
pass
---@export var stats: Resource # Components can hold their own data
signal happened_something(value)
func do_logic(delta: float) -> void:
# Perform specific task
pass
---Standard Components
标准组件
The Input Component (The Senses)
输入组件(感知模块)
Responsibility: Read hardware state. Store it. Do NOT act on it.
State: , , .
move_dirjump_pressedattack_just_pressedgdscript
class_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")职责:读取硬件状态并存储,不执行任何动作。
状态: (移动方向)、(跳跃触发)、(攻击触发)。
move_dirjump_pressedattack_just_pressedgdscript
class_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")The Movement Component (The Legs)
移动组件(行动模块)
Responsibility: Manipulate physics body. Handle velocity/gravity.
Constraint: Requires a reference to the physics body it moves.
gdscript
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()职责:操控物理体,处理速度与重力。
约束:需要持有被移动物理体的引用。
gdscript
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()The Health Component (The Life)
生命值组件(生命模块)
Responsibility: Manage HP, Clamp values, Signal changes.
Context Agnostic: Can be put on a Player, Enemy, or a Wooden Crate.
gdscript
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()职责:管理生命值(HP)、限制数值范围、状态变化时发送信号。
无上下文依赖性:可应用于玩家、敌人甚至木箱等任何实体。
gdscript
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()The Orchestrator (Putting it Together)
协调器(整合所有组件)
The Orchestrator () binds the components in the . It acts as the bridge.
player.gd_physics_processgdscript
class_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()协调器(如)在方法中绑定所有组件,充当组件间的桥梁。
player.gd_physics_processgdscript
class_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()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 instances.
NodeNode是轻量级的,无需担心每个实体添加10-20个节点。组合模式带来的架构组织优势,远超过实例带来的可忽略的内存开销。
NodeReference
参考资料
- Master Skill: godot-master
- 核心技能:godot-master