第21章 游戏架构设计模式
21.1 组合模式
# 通过组合节点构建复杂对象,而非深层继承
# HealthComponent.gd
extends Node
signal health_changed(current: int, maximum: int)
signal died
@export var max_health: int = 100
var health: int
func _ready() -> void:
health = max_health
func take_damage(amount: int) -> void:
health = clampi(health - amount, 0, max_health)
health_changed.emit(health, max_health)
if health <= 0:
died.emit()
func heal(amount: int) -> void:
health = clampi(health + amount, 0, max_health)
health_changed.emit(health, max_health)
# HitboxComponent.gd
extends Area2D
@export var damage: int = 10
@export var knockback: float = 300.0
# HurtboxComponent.gd
extends Area2D
signal hurt(damage: int, knockback_dir: Vector2)
func _ready() -> void:
area_entered.connect(_on_area_entered)
func _on_area_entered(area: Area2D) -> void:
if area.get("damage") != null:
var dir := (global_position - area.global_position).normalized()
hurt.emit(area.damage, dir * area.knockback)
# 使用组合构建角色
# Player (CharacterBody2D)
# ├── Sprite2D
# ├── CollisionShape2D
# ├── HealthComponent
# ├── HitboxComponent (攻击判定)
# ├── HurtboxComponent (受击区域)
# └── InteractionComponent (交互)
# Player.gd - 只负责移动和状态,不关心具体系统
extends CharacterBody2D
func _ready() -> void:
$HealthComponent.died.connect(_on_died)
$HurtboxComponent.hurt.connect(_on_hurt)
func _on_hurt(damage: int, knockback: Vector2) -> void:
$HealthComponent.take_damage(damage)
velocity += knockback
21.2 状态模式
# 脚本化状态方案:每个状态是一个独立脚本
class_name State
extends Node
signal finished(next_state: String)
var state_machine: Node
var actor: CharacterBody2D
func enter() -> void:
pass
func exit() -> void:
pass
func update(delta: float) -> void:
pass
func physics_update(delta: float) -> void:
pass
# StateMachine.gd
extends Node
@export var initial_state: State
var current_state: State
var states: Dictionary = {}
func _ready() -> void:
for child in get_children():
if child is State:
states[child.name.to_lower()] = child
child.state_machine = self
child.actor = get_parent()
if initial_state:
initial_state.enter()
current_state = initial_state
func _process(delta: float) -> void:
if current_state:
current_state.update(delta)
func _physics_process(delta: float) -> void:
if current_state:
current_state.physics_update(delta)
func transition_to(state_name: String) -> void:
var new_state = states.get(state_name.to_lower())
if new_state == null or new_state == current_state:
return
current_state.exit()
current_state = new_state
current_state.enter()
# IdleState.gd
extends State
func enter() -> void:
actor.velocity.x = 0
actor.get_node("AnimatedSprite2D").play("idle")
func physics_update(delta: float) -> void:
var input_dir := Input.get_axis("move_left", "move_right")
if absf(input_dir) > 0.1:
finished.emit("run")
elif Input.is_action_just_pressed("jump"):
finished.emit("jump")
21.3 观察者模式(信号解耦)
# 使用信号实现松耦合通信
# 发送者不需要知道接收者是谁
# 敌人死亡时发送信号
# Enemy.gd
signal died(enemy_type: String, position: Vector2)
func die() -> void:
died.emit("slime", global_position)
queue_free()
# 任何系统都可以监听
# ScoreSystem.gd
func _ready() -> void:
# 连接到场景中所有敌人的 died 信号
get_tree().node_added.connect(_on_node_added)
func _on_node_added(node: Node) -> void:
if node.has_signal("died"):
node.died.connect(_on_enemy_died)
func _on_enemy_died(type: String, pos: Vector2) -> void:
GameManager.add_score(get_score_for_type(type))
21.4 事件总线
# event_bus.gd - Autoload
extends Node
# 游戏事件
signal enemy_killed(enemy_type: String, position: Vector2)
signal item_collected(item_type: String, amount: int)
signal player_damaged(damage: int)
signal boss_defeated(boss_name: String)
signal level_up(new_level: int)
# 任何地方都可以发送和监听
# 发送:EventBus.enemy_killed.emit("slime", position)
# 监听:EventBus.enemy_killed.connect(_on_enemy_killed)
21.5 依赖注入
# 避免硬编码引用,通过注入依赖解耦
# ❌ 硬耦合
extends Node2D
func _ready() -> void:
var player = get_node("/root/Main/Player") # 路径写死
player.health_changed.connect(_on_health_changed)
# ✅ 依赖注入
extends Node2D
@export var player: CharacterBody2D # 在编辑器中拖入
# ✅ 使用组查找
extends Node2D
func _ready() -> void:
var players := get_tree().get_nodes_in_group("player")
if not players.is_empty():
players[0].health_changed.connect(_on_health_changed)
# ✅ 使用 Autoload
extends Node2D
func _ready() -> void:
EventBus.player_damaged.connect(_on_player_damaged)
实操练习
练习:组件化敌人系统
# 创建以下组件:
# 1. HealthComponent - 生命值管理
# 2. HitboxComponent - 攻击判定
# 3. HurtboxComponent - 受击区域
# 4. PatrolComponent - 巡逻行为
# 5. ChaseComponent - 追踪行为
# 使用组件组合不同类型的敌人:
# Slime = Health + Hurtbox + Patrol
# Skeleton = Health + Hitbox + Hurtbox + Patrol + Chase
# Boss = Health + Hitbox + Hurtbox + Chase + 特殊攻击