21、游戏架构设计模式

第21章 游戏架构设计模式

21.1 组合模式

gdscript 复制代码
# 通过组合节点构建复杂对象,而非深层继承

# 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)
gdscript 复制代码
# HitboxComponent.gd
extends Area2D

@export var damage: int = 10
@export var knockback: float = 300.0
gdscript 复制代码
# 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)
gdscript 复制代码
# 使用组合构建角色
# 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 状态模式

gdscript 复制代码
# 脚本化状态方案:每个状态是一个独立脚本
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
gdscript 复制代码
# 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()
gdscript 复制代码
# 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 观察者模式(信号解耦)

gdscript 复制代码
# 使用信号实现松耦合通信
# 发送者不需要知道接收者是谁

# 敌人死亡时发送信号
# 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 事件总线

gdscript 复制代码
# 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 依赖注入

gdscript 复制代码
# 避免硬编码引用,通过注入依赖解耦

# ❌ 硬耦合
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)

实操练习

练习:组件化敌人系统

gdscript 复制代码
# 创建以下组件:
# 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 + 特殊攻击
相关推荐
暴走约伯5 小时前
【VFX工具链源码拆解 09】终极工业化闭环:基于 ID 规则驱动的全自动场景建档、骨骼挂载与男转女场景迁移
unity·c#·游戏引擎·材质
寂寞的船1 天前
【Unity知识速递】第13期 Unity 内存泄漏排查实战:从 Memory Profiler 到 Lua 引用泄漏的生产级全链路诊断
unity·junit·c#·游戏引擎·lua
寂寞的船2 天前
【Unity Bug挑战】第12期 Awaitable 二次 await 死锁之谜 —— 为什么同一个异步操作等两次就卡死?
unity·c#·游戏引擎·bug
Hello_Pyhx2 天前
Pavise v3.3.0.8 配置指南:从游戏识别到会话恢复
游戏
探求虚无3 天前
用 AI 在 Unity 里做一款 RTS:最难的不是生成代码,而是证明它真的能玩
人工智能·unity·游戏引擎
johnsong4 天前
Claude Fable 5.2 登顶:65 分、单发做游戏,和一份 $26.54 一单的账单
人工智能·游戏
寂寞的船5 天前
【Unity知识速递】第10期 Unity GC 零分配实战:从 UnityEngine.Pool 到手动 GC 控制的全链路优化
jvm·unity·c#·游戏引擎·lucene
shadowcz0075 天前
屿北:站在新旧时代交接处,赌一场AI原生无限游戏#Infinite Talk
游戏·ai-native
Martina_03216 天前
AI生成3D场景的霓虹灯一开就糊成光团?用6步检查Emissive、Bloom与曝光
人工智能·3d·ai·自然语言处理·游戏引擎·材质·贴图
又过二十7 天前
结合Godot八向图实现网页角色设计器测试
ai·aigc·godot·game·qwen-image·游戏素材