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 + 特殊攻击
相关推荐
烛阴13 小时前
Godot 4 入门(04):CharacterBody2D 实现八方向移动
godot
bj_bluewei_tech17 小时前
拯救者游戏本维修卡 logo 无限重启,确认显卡虚焊故障
游戏·电脑
Jae den2 天前
网络延迟为什么会影响游戏和实时业务?
网络·游戏
思盛iOS签名上架2 天前
ipa企业签名闪退是什么原因?
游戏·ios·testfight
狂人开飞机2 天前
14、游戏手感与视觉打磨
游戏·游戏引擎·godot
狂人开飞机2 天前
15、音频系统
游戏引擎·godot
狂人开飞机2 天前
16、粒子线条与视觉特效
游戏引擎·godot
心前阳光2 天前
Unity发布PC软件图标不能改变
unity·游戏引擎
笨鸟先飞的橘猫3 天前
系统设计第十七天决策卡
学习·游戏