# 游戏架构设计模式

前人总结的架构智慧——站在巨人肩膀上设计

前置知识:第01章 系统设计方法论 + 第07章 游戏系统设计实战


阅读指南(初学者必看)

为什么你需要学习游戏架构设计模式?

第01章学了设计模式的基础,本章是设计模式在游戏架构中的深度应用。ECS、对象池、行为树、状态机、空间分区——这些不是理论,是游戏工业界每天都在用的架构模式。

学完本章,你能回答:

  • ECS 和传统 OOP 有什么本质区别?什么时候该用 ECS?
  • 状态机和行为树怎么选?2~5个状态用状态机,复杂AI用行为树?
  • 空间分区算法怎么选?四叉树、八叉树、空间哈希、BVH各适合什么场景?

本文结构

第一部分:ECS(Entity Component System)——组合优于继承
第二部分:对象池与命令模式——性能与回放
第三部分:状态机与行为树——AI 行为设计
第四部分:空间分区——空间查询优化

一、ECS(Entity Component System)

生活类比:ECS 就像"自助餐"。传统面向对象是"套餐"(固定搭配),ECS 是"自选"(自由组合)。

传统 OOP:
class Monster extends Character {
  int hp;
  int attack;
  void update() { ai(); move(); render(); }
}

ECS:
Entity = 只是一个 ID
Component = 纯数据(HealthComponent, AttackComponent, PositionComponent)
System = 纯逻辑(AISystem, MoveSystem, RenderSystem)

优势:
1. 组合优于继承 ── 冰冻怪物?加个 FrozenComponent 就行
2. 数据导向 ── Component 连续存储,缓存友好
3. 并行友好 ── System 之间独立,可以并行执行
4. 热重载 ── 修改 System 不影响 Entity

劣势:
1. 学习曲线陡
2. 简单场景反而更复杂
3. 调试困难(Entity 只是个 ID)

ECS完整实现

// World:管理所有Entity和Component
class World {
  private nextEntityId: number = 1;
  private entities: Set<Entity> = new Set();
  private components: Map<string, Map<Entity, any>> = new Map();
  private systems: System[] = [];
  
  createEntity(): Entity {
    const entity = this.nextEntityId++;
    this.entities.add(entity);
    return entity;
  }
  
  destroyEntity(entity: Entity) {
    this.entities.delete(entity);
    for (const componentMap of this.components.values()) {
      componentMap.delete(entity);
    }
  }
  
  addComponent<T>(entity: Entity, componentName: string, component: T) {
    if (!this.components.has(componentName)) {
      this.components.set(componentName, new Map());
    }
    this.components.get(componentName)!.set(entity, component);
  }
  
  getComponent<T>(entity: Entity, componentName: string): T | undefined {
    const componentMap = this.components.get(componentName);
    return componentMap?.get(entity);
  }
  
  hasComponent(entity: Entity, componentName: string): boolean {
    const componentMap = this.components.get(componentName);
    return componentMap?.has(entity) || false;
  }
  
  addSystem(system: System) {
    this.systems.push(system);
  }
  
  update(dt: number) {
    for (const system of this.systems) {
      system.update(this, dt);
    }
  }
  
  query(...componentNames: string[]): Entity[] {
    const result: Entity[] = [];
    for (const entity of this.entities) {
      let hasAll = true;
      for (const name of componentNames) {
        if (!this.hasComponent(entity, name)) {
          hasAll = false;
          break;
        }
      }
      if (hasAll) result.push(entity);
    }
    return result;
  }
}

abstract class System {
  abstract update(world: World, dt: number): void;
}

ECS应用:子弹系统

interface Bullet { damage: number; ownerId: Entity; }
interface Lifetime { remaining: number; }

class BulletSystem extends System {
  update(world: World, dt: number) {
    // 移动子弹
    const bullets = world.query('Bullet', 'Position', 'Velocity');
    for (const bullet of bullets) {
      const pos = world.getComponent<Position>(bullet, 'Position')!;
      const vel = world.getComponent<Velocity>(bullet, 'Velocity')!;
      pos.x += vel.vx * dt;
      pos.y += vel.vy * dt;
    }
    
    // 检测碰撞
    for (const bullet of bullets) {
      const bulletComp = world.getComponent<Bullet>(bullet, 'Bullet')!;
      const pos = world.getComponent<Position>(bullet, 'Position')!;
      const targets = world.query('Health', 'Position');
      for (const target of targets) {
        if (target === bulletComp.ownerId) continue;
        const targetPos = world.getComponent<Position>(target, 'Position')!;
        const dist = Math.sqrt((pos.x - targetPos.x) ** 2 + (pos.y - targetPos.y) ** 2);
        if (dist < 10) {
          const health = world.getComponent<Health>(target, 'Health')!;
          health.current -= bulletComp.damage;
          world.destroyEntity(bullet);
          break;
        }
      }
    }
  }
}

二、对象池与命令模式

对象池

应用:子弹、粒子、怪物复用
类比:自行车租赁站,还了再借

优势:
- 减少GC压力
- 减少内存分配开销
- 创建/销毁高频对象时性能提升明显

命令模式

应用:技能系统、回放系统
类比:遥控器按钮,按了执行命令

优势:
- 操作可记录(用于回放)
- 操作可撤销(用于悔棋)
- 操作可队列(用于技能连招)

三、状态机与行为树

状态机(FSM)── 适合简单AI
角色状态:待机 → 移动 → 攻击 → 死亡
触发条件:发现敌人/到达目标/HP归零

层次状态机(HFSM):
"战斗"状态内部嵌套"追击/攻击/逃跑"子状态
比纯状态机灵活,比行为树简单

行为树(BT)── 适合复杂AI
已在其他项目中详解

选择指南:
- 2~5个状态的简单AI → 状态机
- 需要灵活组合的复杂AI → 行为树
- 需要长期规划的AI → GOAP/HTN

四、空间分区

空间分区算法选择:

| 算法 | 2D/3D | 动态/静态 | 适用场景 |
|------|-------|----------|---------|
| 四叉树 | 2D | 静态 | 地图碰撞 |
| 八叉树 | 3D | 静态 | 3D场景管理 |
| 空间哈希 | 2D/3D | 动态 | 角色碰撞 |
| BVH | 2D/3D | 动态 | 射线检测 |
| 网格 | 2D | 静态 | 瓦片地图 |

详见 4_2_game-math-physics 项目

自问自答

Q:ECS 适合所有游戏吗? A:不是。ECS 适合实体多、组合多、需要高性能的场景(成千上万个子弹、粒子)。如果游戏实体少(棋牌、AVG),传统 OOP 更简单直观。不要为了用 ECS 而用 ECS。

Q:对象池和 ECS 的 Component 怎么配合? A:Component 本身就是纯数据,非常适合用对象池管理。比如子弹的 Component(Position + Velocity + Damage),用完还回池子,新子弹从池子里取。ECS + 对象池是游戏开发中经典的性能优化组合。

Q:四叉树和空间哈希哪个好? A:看场景。四叉树适合静态/半静态场景(地图、障碍物),查询效率高但更新成本高;空间哈希适合动态场景(大量角色移动),更新快但查询效率取决于哈希函数。游戏中的角色碰撞用空间哈希更多。

Q:状态机可以嵌套吗? A:可以,这叫层次状态机(HFSM)。比如"战斗"状态内部嵌套"追击/攻击/逃跑"子状态。HFSM 是状态机和行为树之间的折中方案——比纯状态机灵活,比行为树简单。


实践任务

  • 任务1:用 ECS 架构实现一个简单的游戏(至少3种 Entity、5种 Component、3种 System)
  • 任务2:用传统 OOP 实现同样的游戏,对比两种架构的代码组织和扩展性
  • 任务3:实现一个层次状态机(HFSM),管理角色的待机/移动/战斗/逃跑状态
  • 任务4:实现空间哈希,对比暴力遍历 O(n²) 的碰撞检测性能
  • 任务5:在 ECS 中集成对象池,实现子弹的创建/销毁/复用,对比 GC 表现

与其他章节的关联

本章内容 关联章节 关联点
ECS 第01章 系统设计方法论 第01章的设计模式是 ECS 的理论基础
状态机 第07章 游戏系统设计实战 战斗系统的 TurnManager 可以用状态机实现
行为树 第07章 游戏系统设计实战 战斗系统的 AI 行为用行为树设计
空间分区 4_2_game-math-physics 碰撞检测和寻路是空间分区的两大应用
对象池 第03章 帧同步Lockstep 帧同步的输入缓冲可以用对象池管理

上一章:09-游戏运营与商业化 下一章:11-LuaCSharp脚本与热更新