Files
esengine/docs/guide/system.md
YHH cd6ef222d1 feat(ecs): 核心系统改进 - 句柄、调度、变更检测与查询编译 (#304)
新增功能:
- EntityHandle: 轻量级实体句柄 (28位索引 + 20位代数)
- SystemScheduler: 声明式系统调度,支持 @Stage/@Before/@After/@InSet 装饰器
- EpochManager: 帧级变更检测
- CompiledQuery: 预编译类型安全查询

API 改进:
- EntitySystem 添加 getBefore()/getAfter()/getSets() getter 方法
- Entity 添加 markDirty() 辅助方法
- IScene 添加 epochManager 属性
- CommandBuffer.pendingCount 修正为返回实际操作数

文档更新:
- 更新系统调度和查询相关文档
2025-12-15 09:17:00 +08:00

31 KiB
Raw Blame History

系统架构

在 ECS 架构中系统System是处理业务逻辑的地方。系统负责对拥有特定组件组合的实体执行操作是 ECS 架构的逻辑处理单元。

基本概念

系统是继承自 EntitySystem 抽象基类的具体类,用于:

  • 定义实体的处理逻辑(如移动、碰撞检测、渲染等)
  • 根据组件组合筛选需要处理的实体
  • 提供生命周期管理和性能监控
  • 管理实体的添加、移除事件

系统类型

框架提供了几种不同类型的系统基类:

EntitySystem - 基础系统

最基础的系统类,所有其他系统都继承自它:

import { EntitySystem, ECSSystem, Matcher } from '@esengine/ecs-framework';

@ECSSystem('Movement')
class MovementSystem extends EntitySystem {
  constructor() {
    // 使用 Matcher 定义需要处理的实体条件
    super(Matcher.all(Position, Velocity));
  }

  protected process(entities: readonly Entity[]): void {
    for (const entity of entities) {
      const position = entity.getComponent(Position);
      const velocity = entity.getComponent(Velocity);

      if (position && velocity) {
        position.x += velocity.dx * Time.deltaTime;
        position.y += velocity.dy * Time.deltaTime;
      }
    }
  }
}

ProcessingSystem - 处理系统

适用于不需要逐个处理实体的系统:

@ECSSystem('Physics')
class PhysicsSystem extends ProcessingSystem {
  constructor() {
    super(); // 不需要指定 Matcher
  }

  public processSystem(): void {
    // 执行物理世界步进
    this.physicsWorld.step(Time.deltaTime);
  }
}

PassiveSystem - 被动系统

被动系统不进行主动处理,主要用于监听实体的添加和移除事件:

@ECSSystem('EntityTracker')
class EntityTrackerSystem extends PassiveSystem {
  constructor() {
    super(Matcher.all(Health));
  }

  protected onAdded(entity: Entity): void {
    console.log(`生命值实体被添加: ${entity.name}`);
  }

  protected onRemoved(entity: Entity): void {
    console.log(`生命值实体被移除: ${entity.name}`);
  }
}

IntervalSystem - 间隔系统

按固定时间间隔执行的系统:

@ECSSystem('AutoSave')
class AutoSaveSystem extends IntervalSystem {
  constructor() {
    // 每 5 秒执行一次
    super(5.0, Matcher.all(SaveData));
  }

  protected process(entities: readonly Entity[]): void {
    console.log('执行自动保存...');
    // 保存游戏数据
    this.saveGameData(entities);
  }

  private saveGameData(entities: readonly Entity[]): void {
    // 保存逻辑
  }
}

WorkerEntitySystem - 多线程系统

基于Web Worker的多线程处理系统适用于计算密集型任务能够充分利用多核CPU性能。

Worker系统提供了真正的并行计算能力支持SharedArrayBuffer优化并具有自动降级支持。特别适合物理模拟、粒子系统、AI计算等场景。

详细内容请参考:Worker系统

实体匹配器 (Matcher)

Matcher 用于定义系统需要处理哪些实体。它提供了灵活的条件组合:

基本匹配条件

// 必须同时拥有 Position 和 Velocity 组件
const matcher1 = Matcher.all(Position, Velocity);

// 至少拥有 Health 或 Shield 组件之一
const matcher2 = Matcher.any(Health, Shield);

// 不能拥有 Dead 组件
const matcher3 = Matcher.none(Dead);

复合匹配条件

// 复杂的组合条件
const complexMatcher = Matcher.all(Position, Velocity)
  .any(Player, Enemy)
  .none(Dead, Disabled);

@ECSSystem('Combat')
class CombatSystem extends EntitySystem {
  constructor() {
    super(complexMatcher);
  }
}

特殊匹配条件

// 按标签匹配
const tagMatcher = Matcher.byTag(1); // 匹配标签为 1 的实体

// 按名称匹配
const nameMatcher = Matcher.byName("Player"); // 匹配名称为 "Player" 的实体

// 单组件匹配
const componentMatcher = Matcher.byComponent(Health); // 匹配拥有 Health 组件的实体

// 不匹配任何实体
const nothingMatcher = Matcher.nothing(); // 用于只需要生命周期回调的系统

空匹配器 vs Nothing 匹配器

// empty() - 空条件,匹配所有实体
const emptyMatcher = Matcher.empty();

// nothing() - 不匹配任何实体,用于只需要生命周期方法的系统
const nothingMatcher = Matcher.nothing();

// 使用场景:只需要 onBegin/onEnd 生命周期的系统
@ECSSystem('FrameTimer')
class FrameTimerSystem extends EntitySystem {
  constructor() {
    super(Matcher.nothing()); // 不处理任何实体
  }

  protected onBegin(): void {
    // 每帧开始时执行,例如:记录帧开始时间
    console.log('帧开始');
  }

  protected process(entities: readonly Entity[]): void {
    // 永远不会被调用,因为没有匹配的实体
  }

  protected onEnd(): void {
    // 每帧结束时执行
    console.log('帧结束');
  }
}

💡 提示:更多关于 Matcher 和实体查询的详细用法,请参考 实体查询系统 文档。

系统生命周期

系统提供了完整的生命周期回调:

@ECSSystem('Example')
class ExampleSystem extends EntitySystem {
  protected onInitialize(): void {
    console.log('系统初始化');
    // 系统被添加到场景时调用,用于初始化资源
  }

  protected onBegin(): void {
    // 每帧处理开始前调用
  }

  protected process(entities: readonly Entity[]): void {
    // 主要的处理逻辑
    for (const entity of entities) {
      // 处理每个实体
      // ✅ 可以安全地在这里添加/移除组件,不会影响当前迭代
    }
  }

  protected lateProcess(entities: readonly Entity[]): void {
    // 主处理之后的后期处理
    // ✅ 可以安全地在这里添加/移除组件,不会影响当前迭代
  }

  protected onEnd(): void {
    // 每帧处理结束后调用
  }

  protected onDestroy(): void {
    console.log('系统销毁');
    // 系统从场景移除时调用,用于清理资源
  }
}

实体事件监听

系统可以监听实体的添加和移除事件:

@ECSSystem('EnemyManager')
class EnemyManagerSystem extends EntitySystem {
  private enemyCount = 0;

  constructor() {
    super(Matcher.all(Enemy, Health));
  }

  protected onAdded(entity: Entity): void {
    this.enemyCount++;
    console.log(`敌人加入战斗,当前敌人数量: ${this.enemyCount}`);

    // 可以在这里为新敌人设置初始状态
    const health = entity.getComponent(Health);
    if (health) {
      health.current = health.max;
    }
  }

  protected onRemoved(entity: Entity): void {
    this.enemyCount--;
    console.log(`敌人被移除,剩余敌人数量: ${this.enemyCount}`);

    // 检查是否所有敌人都被消灭
    if (this.enemyCount === 0) {
      this.scene?.eventSystem.emitSync('all_enemies_defeated');
    }
  }
}

重要onAdded/onRemoved 的调用时机

⚠️ 注意onAddedonRemoved 回调是同步调用的,会在 addComponent/removeComponent 返回之前立即执行。

这意味着:

// ❌ 错误的用法:链式赋值在 onAdded 之后才执行
const comp = entity.addComponent(new ClickComponent());
comp.element = this._element;  // 此时 onAdded 已经执行完了!

// ✅ 正确的用法:通过构造函数传入初始值
const comp = entity.addComponent(new ClickComponent(this._element));

// ✅ 或者使用 createComponent 方法
const comp = entity.createComponent(ClickComponent, this._element);

为什么这样设计?

事件驱动设计确保 onAdded/onRemoved 回调不受系统注册顺序的影响。当组件被添加时,所有监听该组件的系统都会立即收到通知,而不是等到下一帧。

最佳实践:

  1. 组件的初始值应该通过构造函数传入
  2. 不要依赖 addComponent 返回后再设置属性
  3. 如果需要在 onAdded 中访问组件属性,确保这些属性在构造时已经设置

在 process/lateProcess 中安全地修改组件

processlateProcess 中迭代实体时,可以安全地添加或移除组件,不会影响当前的迭代过程:

@ECSSystem('Damage')
class DamageSystem extends EntitySystem {
  constructor() {
    super(Matcher.all(Health, DamageReceiver));
  }

  protected process(entities: readonly Entity[]): void {
    for (const entity of entities) {
      const health = entity.getComponent(Health);
      const damage = entity.getComponent(DamageReceiver);

      if (health && damage) {
        health.current -= damage.amount;

        // ✅ 安全:移除组件不会影响当前迭代
        entity.removeComponent(damage);

        if (health.current <= 0) {
          // ✅ 安全:添加组件也不会影响当前迭代
          entity.addComponent(new Dead());
        }
      }
    }
  }
}

框架会在每次 process/lateProcess 调用前创建实体列表的快照,确保迭代过程中的组件变化不会导致跳过实体或重复处理。

命令缓冲区 (CommandBuffer)

v2.3.0+

CommandBuffer 提供了一种延迟执行实体操作的机制。当你需要在迭代过程中销毁实体或进行其他可能影响迭代的操作时,使用 CommandBuffer 可以将这些操作推迟到帧末统一执行。

基本用法

每个 EntitySystem 都内置了 commands 属性:

@ECSSystem('Damage')
class DamageSystem extends EntitySystem {
  constructor() {
    super(Matcher.all(Health, DamageReceiver));
  }

  protected process(entities: readonly Entity[]): void {
    for (const entity of entities) {
      const health = entity.getComponent(Health);
      const damage = entity.getComponent(DamageReceiver);

      if (health && damage) {
        health.current -= damage.amount;

        // 使用命令缓冲区延迟移除组件
        this.commands.removeComponent(entity, DamageReceiver);

        if (health.current <= 0) {
          // 延迟添加死亡标记
          this.commands.addComponent(entity, new Dead());
          // 延迟销毁实体
          this.commands.destroyEntity(entity);
        }
      }
    }
  }
}

支持的命令

方法 说明
addComponent(entity, component) 延迟添加组件
removeComponent(entity, ComponentType) 延迟移除组件
destroyEntity(entity) 延迟销毁实体
setEntityActive(entity, active) 延迟设置实体激活状态

执行时机

命令缓冲区中的命令会在每帧的 lateUpdate 阶段之后自动执行。执行顺序与命令入队顺序一致。

场景更新流程:
1. onBegin()
2. process()
3. lateProcess()
4. onEnd()
5. flushCommandBuffers()  <-- 命令在这里执行

使用场景

CommandBuffer 适用于以下场景:

  1. 在迭代中销毁实体:避免修改正在遍历的集合
  2. 批量延迟操作:将多个操作合并到帧末执行
  3. 跨系统协调:一个系统标记,另一个系统响应
// 示例:敌人死亡系统
@ECSSystem('EnemyDeath')
class EnemyDeathSystem extends EntitySystem {
  constructor() {
    super(Matcher.all(Enemy, Health));
  }

  protected process(entities: readonly Entity[]): void {
    for (const entity of entities) {
      const health = entity.getComponent(Health);
      if (health && health.current <= 0) {
        // 播放死亡动画、掉落物品等
        this.spawnLoot(entity);

        // 延迟销毁,不影响当前迭代
        this.commands.destroyEntity(entity);
      }
    }
  }

  private spawnLoot(entity: Entity): void {
    // 掉落物品逻辑
  }
}

注意事项

  • 命令会跳过已销毁的实体(安全检查)
  • 单个命令执行失败不会影响其他命令
  • 命令按入队顺序执行
  • 每次 flush() 后命令队列会清空

系统属性和方法

重要属性

@ECSSystem('Example')
class ExampleSystem extends EntitySystem {
  showSystemInfo(): void {
    console.log(`系统名称: ${this.systemName}`);        // 系统名称
    console.log(`更新顺序: ${this.updateOrder}`);       // 更新时序
    console.log(`是否启用: ${this.enabled}`);            // 启用状态
    console.log(`实体数量: ${this.entities.length}`);   // 匹配的实体数量
    console.log(`所属场景: ${this.scene?.name}`);        // 所属场景
  }
}

实体访问

protected process(entities: readonly Entity[]): void {
  // 方式1使用参数中的实体列表
  for (const entity of entities) {
    // 处理实体
  }

  // 方式2使用 this.entities 属性(与参数相同)
  for (const entity of this.entities) {
    // 处理实体
  }
}

控制系统执行

@ECSSystem('Conditional')
class ConditionalSystem extends EntitySystem {
  private shouldProcess = true;

  protected onCheckProcessing(): boolean {
    // 返回 false 时跳过本次处理
    return this.shouldProcess && this.entities.length > 0;
  }

  public pause(): void {
    this.shouldProcess = false;
  }

  public resume(): void {
    this.shouldProcess = true;
  }
}

事件系统集成

系统可以方便地监听和发送事件:

@ECSSystem('GameLogic')
class GameLogicSystem extends EntitySystem {
  protected onInitialize(): void {
    // 添加事件监听器(系统销毁时自动清理)
    this.addEventListener('player_died', this.onPlayerDied.bind(this));
    this.addEventListener('level_complete', this.onLevelComplete.bind(this));
  }

  private onPlayerDied(data: any): void {
    console.log('玩家死亡,重新开始游戏');
    // 处理玩家死亡逻辑
  }

  private onLevelComplete(data: any): void {
    console.log('关卡完成,加载下一关');
    // 处理关卡完成逻辑
  }

  protected process(entities: readonly Entity[]): void {
    // 在处理过程中发送事件
    for (const entity of entities) {
      const health = entity.getComponent(Health);
      if (health && health.current <= 0) {
        this.scene?.eventSystem.emitSync('entity_died', { entity });
      }
    }
  }
}

性能监控

系统内置了性能监控功能:

@ECSSystem('Performance')
class PerformanceSystem extends EntitySystem {
  protected onEnd(): void {
    // 获取性能数据
    const perfData = this.getPerformanceData();
    if (perfData) {
      console.log(`执行时间: ${perfData.executionTime.toFixed(2)}ms`);
    }

    // 获取性能统计
    const stats = this.getPerformanceStats();
    if (stats) {
      console.log(`平均执行时间: ${stats.averageTime.toFixed(2)}ms`);
    }
  }

  public resetPerformance(): void {
    this.resetPerformanceData();
  }
}

系统管理

添加系统到场景

框架提供了两种方式添加系统:传入实例或传入类型(自动依赖注入)。

// 在场景子类中添加系统
class GameScene extends Scene {
  protected initialize(): void {
    // 方式1传入实例
    this.addSystem(new MovementSystem());
    this.addSystem(new RenderSystem());

    // 方式2传入类型自动依赖注入
    this.addEntityProcessor(PhysicsSystem);

    // 设置系统更新顺序
    const movementSystem = this.getSystem(MovementSystem);
    if (movementSystem) {
      movementSystem.updateOrder = 1;
    }
  }
}

系统依赖注入

系统实现了 IService 接口,支持通过依赖注入获取其他服务或系统:

import { ECSSystem, Injectable, Inject } from '@esengine/ecs-framework';

@Injectable()
@ECSSystem('Physics')
class PhysicsSystem extends EntitySystem {
  constructor(
    @Inject(CollisionService) private collision: CollisionService
  ) {
    super(Matcher.all(Transform, RigidBody));
  }

  protected process(entities: readonly Entity[]): void {
    // 使用注入的服务
    this.collision.detectCollisions(entities);
  }

  // 实现 IService 接口的 dispose 方法
  public dispose(): void {
    // 清理资源
  }
}

// 使用时传入类型即可,框架会自动注入依赖
class GameScene extends Scene {
  protected initialize(): void {
    // 自动依赖注入
    this.addEntityProcessor(PhysicsSystem);
  }
}

注意事项:

  • 使用 @Injectable() 装饰器标记需要依赖注入的系统
  • 在构造函数参数中使用 @Inject() 装饰器声明依赖
  • 系统必须实现 dispose() 方法IService 接口要求)
  • 使用 addEntityProcessor(类型) 而不是 addSystem(new 类型()) 来启用依赖注入

系统更新顺序

系统的执行顺序由 updateOrder 属性决定,数值越小越先执行:

@ECSSystem('Input')
class InputSystem extends EntitySystem {
  constructor() {
    super(Matcher.all(InputComponent));
    this.updateOrder = -100; // 输入系统优先执行
  }
}

@ECSSystem('Physics')
class PhysicsSystem extends EntitySystem {
  constructor() {
    super(Matcher.all(RigidBody));
    this.updateOrder = 0; // 默认顺序
  }
}

@ECSSystem('Render')
class RenderSystem extends EntitySystem {
  constructor() {
    super(Matcher.all(Sprite, Transform));
    this.updateOrder = 100; // 渲染系统最后执行
  }
}

稳定排序addOrder

当多个系统的 updateOrder 相同时,框架使用 addOrder(添加顺序)作为第二排序条件,确保排序结果稳定可预测:

// 这两个系统 updateOrder 都是默认值 0
@ECSSystem('SystemA')
class SystemA extends EntitySystem { /* ... */ }

@ECSSystem('SystemB')
class SystemB extends EntitySystem { /* ... */ }

// 添加顺序决定了执行顺序
scene.addSystem(new SystemA()); // addOrder = 0先执行
scene.addSystem(new SystemB()); // addOrder = 1后执行

注意addOrder 由框架在 addSystem 时自动设置,无需手动管理。这确保了相同 updateOrder 的系统按照添加顺序执行,避免了排序不稳定导致的随机行为。

声明式系统调度

v2.4.0+

除了使用 updateOrder 手动控制执行顺序外,框架还提供了声明式的系统调度机制,让你可以通过依赖关系来定义系统的执行顺序。

调度装饰器

import { EntitySystem, ECSSystem, Stage, Before, After, InSet } from '@esengine/ecs-framework';

// 使用装饰器声明系统调度
@ECSSystem('Movement')
@Stage('update')           // 在 update 阶段执行
@After('InputSystem')      // 在 InputSystem 之后执行
@Before('RenderSystem')    // 在 RenderSystem 之前执行
class MovementSystem extends EntitySystem {
    constructor() {
        super(Matcher.all(Position, Velocity));
    }

    protected process(entities: readonly Entity[]): void {
        // 移动逻辑
    }
}

// 使用系统集合进行分组
@ECSSystem('Physics')
@Stage('update')
@InSet('CoreSystems')      // 属于 CoreSystems 集合
class PhysicsSystem extends EntitySystem {
    // ...
}

@ECSSystem('Collision')
@Stage('update')
@After('set:CoreSystems')  // 在 CoreSystems 集合的所有系统之后执行
class CollisionSystem extends EntitySystem {
    // ...
}

系统执行阶段

框架定义了以下系统执行阶段,按顺序执行:

阶段 说明 典型用途
startup 启动阶段 一次性初始化
preUpdate 更新前阶段 输入处理、状态准备
update 主更新阶段(默认) 核心游戏逻辑
postUpdate 更新后阶段 物理、碰撞检测
cleanup 清理阶段 资源清理、状态重置

Fluent API 配置

如果不想使用装饰器,也可以使用 Fluent API 在运行时配置调度:

@ECSSystem('Movement')
class MovementSystem extends EntitySystem {
    constructor() {
        super(Matcher.all(Position, Velocity));

        // 使用 Fluent API 配置调度
        this.stage('update')
            .after('InputSystem')
            .before('RenderSystem')
            .inSet('CoreSystems');
    }
}

循环依赖检测

框架会自动检测循环依赖并抛出明确的错误:

// 这会导致循环依赖错误
@ECSSystem('SystemA')
@Before('SystemB')
class SystemA extends EntitySystem { }

@ECSSystem('SystemB')
@Before('SystemA')  // 错误A -> B -> A 形成循环
class SystemB extends EntitySystem { }

// 错误信息Cyclic dependency detected: SystemA -> SystemB -> SystemA

帧级变更检测

v2.4.0+

框架提供了基于 epoch 的帧级变更检测机制,让系统可以只处理发生变化的实体,大幅提升性能。

核心概念

  • Epoch:全局帧计数器,每帧递增
  • lastWriteEpoch:组件最后被修改时的 epoch
  • 变更检测:通过比较 epoch 判断组件是否在指定时间点后发生变化

标记组件为已修改

修改组件数据后,需要标记组件为已变更。有两种方式:

方式 1通过 Entity 辅助方法(推荐)

// 修改组件后通过 entity.markDirty() 标记
const pos = entity.getComponent(Position)!;
pos.x = 100;
pos.y = 200;
entity.markDirty(pos);

// 可以同时标记多个组件
const vel = entity.getComponent(Velocity)!;
vel.vx = 10;
entity.markDirty(pos, vel);

方式 2在组件内部封装

class VelocityComponent extends Component {
    private _vx: number = 0;
    private _vy: number = 0;

    // 提供修改方法,接收 epoch 参数
    public setVelocity(vx: number, vy: number, epoch: number): void {
        this._vx = vx;
        this._vy = vy;
        this.markDirty(epoch);
    }

    public get vx(): number { return this._vx; }
    public get vy(): number { return this._vy; }
}

// 在系统中使用
const vel = entity.getComponent(VelocityComponent)!;
vel.setVelocity(10, 20, this.currentEpoch);

在系统中使用变更检测

EntitySystem 提供了多个变更检测辅助方法:

@ECSSystem('Physics')
class PhysicsSystem extends EntitySystem {
    constructor() {
        super(Matcher.all(Position, Velocity));
    }

    protected process(entities: readonly Entity[]): void {
        // 方式1使用 forEachChanged 只处理变更的实体
        // 自动保存 epoch 检查点
        this.forEachChanged(entities, [Velocity], (entity) => {
            const pos = this.requireComponent(entity, Position);
            const vel = this.requireComponent(entity, Velocity);

            // 只有 Velocity 变化时才更新位置
            pos.x += vel.vx * Time.deltaTime;
            pos.y += vel.vy * Time.deltaTime;
        });
    }
}

@ECSSystem('Transform')
class TransformSystem extends EntitySystem {
    constructor() {
        super(Matcher.all(Transform, RigidBody));
    }

    protected process(entities: readonly Entity[]): void {
        // 方式2使用 filterChanged 获取变更的实体列表
        const changedEntities = this.filterChanged(entities, [RigidBody]);

        for (const entity of changedEntities) {
            // 处理物理状态变化的实体
            this.updatePhysics(entity);
        }

        // 手动保存 epoch 检查点
        this.saveEpoch();
    }

    protected updatePhysics(entity: Entity): void {
        // 物理更新逻辑
    }
}

变更检测 API 参考

方法 说明
forEachChanged(entities, [Types], callback) 遍历指定组件发生变更的实体,自动保存检查点
filterChanged(entities, [Types]) 返回指定组件发生变更的实体数组
hasChanged(entity, [Types]) 检查单个实体的指定组件是否发生变更
saveEpoch() 手动保存当前 epoch 作为检查点
lastProcessEpoch 获取上次保存的 epoch 检查点
currentEpoch 获取当前场景的 epoch

使用场景

变更检测特别适合以下场景:

  1. 脏标记优化:只在数据变化时更新渲染
  2. 物理同步:只同步位置/速度发生变化的实体
  3. 网络同步:只发送变化的组件数据
  4. 缓存失效:只在依赖数据变化时重新计算
@ECSSystem('NetworkSync')
class NetworkSyncSystem extends EntitySystem {
    constructor() {
        super(Matcher.all(NetworkComponent, Transform));
    }

    protected process(entities: readonly Entity[]): void {
        // 只同步变化的实体,大幅减少网络流量
        this.forEachChanged(entities, [Transform], (entity) => {
            const transform = this.requireComponent(entity, Transform);
            const network = this.requireComponent(entity, NetworkComponent);

            this.sendTransformUpdate(network.id, transform);
        });
    }

    private sendTransformUpdate(id: string, transform: Transform): void {
        // 发送网络更新
    }
}

复杂系统示例

碰撞检测系统

@ECSSystem('Collision')
class CollisionSystem extends EntitySystem {
  constructor() {
    super(Matcher.all(Transform, Collider));
  }

  protected process(entities: readonly Entity[]): void {
    // 简单的 n² 碰撞检测
    for (let i = 0; i < entities.length; i++) {
      for (let j = i + 1; j < entities.length; j++) {
        this.checkCollision(entities[i], entities[j]);
      }
    }
  }

  private checkCollision(entityA: Entity, entityB: Entity): void {
    const transformA = entityA.getComponent(Transform);
    const transformB = entityB.getComponent(Transform);
    const colliderA = entityA.getComponent(Collider);
    const colliderB = entityB.getComponent(Collider);

    if (this.isColliding(transformA, colliderA, transformB, colliderB)) {
      // 发送碰撞事件
      this.scene?.eventSystem.emitSync('collision', {
        entityA,
        entityB
      });
    }
  }

  private isColliding(transformA: Transform, colliderA: Collider,
                     transformB: Transform, colliderB: Collider): boolean {
    // 碰撞检测逻辑
    return false; // 简化示例
  }
}

状态机系统

@ECSSystem('StateMachine')
class StateMachineSystem extends EntitySystem {
  constructor() {
    super(Matcher.all(StateMachine));
  }

  protected process(entities: readonly Entity[]): void {
    for (const entity of entities) {
      const stateMachine = entity.getComponent(StateMachine);
      if (stateMachine) {
        stateMachine.updateTimer(Time.deltaTime);
        this.updateState(entity, stateMachine);
      }
    }
  }

  private updateState(entity: Entity, stateMachine: StateMachine): void {
    switch (stateMachine.currentState) {
      case EntityState.Idle:
        this.handleIdleState(entity, stateMachine);
        break;
      case EntityState.Moving:
        this.handleMovingState(entity, stateMachine);
        break;
      case EntityState.Attacking:
        this.handleAttackingState(entity, stateMachine);
        break;
    }
  }

  private handleIdleState(entity: Entity, stateMachine: StateMachine): void {
    // 空闲状态逻辑
  }

  private handleMovingState(entity: Entity, stateMachine: StateMachine): void {
    // 移动状态逻辑
  }

  private handleAttackingState(entity: Entity, stateMachine: StateMachine): void {
    // 攻击状态逻辑
  }
}

最佳实践

1. 系统单一职责

// ✅ 好的系统设计 - 职责单一
@ECSSystem('Movement')
class MovementSystem extends EntitySystem {
  constructor() {
    super(Matcher.all(Position, Velocity));
  }
}

@ECSSystem('Rendering')
class RenderingSystem extends EntitySystem {
  constructor() {
    super(Matcher.all(Sprite, Transform));
  }
}

// ❌ 避免的系统设计 - 职责过多
@ECSSystem('GameSystem')
class GameSystem extends EntitySystem {
  // 一个系统处理移动、渲染、音效等多种逻辑
}

2. 使用 @ECSSystem 装饰器

@ECSSystem 是系统类必须使用的装饰器,它为系统提供类型标识和元数据管理。

为什么必须使用

功能 说明
类型识别 提供稳定的系统名称,代码混淆后仍能正确识别
调试支持 在性能监控、日志和调试工具中显示可读的系统名称
系统管理 通过名称查找和管理系统
序列化支持 场景序列化时可以记录系统配置

基本语法

@ECSSystem(systemName: string)
  • systemName: 系统的名称,建议使用描述性的名称

使用示例

// ✅ 正确的用法
@ECSSystem('Physics')
class PhysicsSystem extends EntitySystem {
  // 系统实现
}

// ✅ 推荐:使用描述性的名称
@ECSSystem('PlayerMovement')
class PlayerMovementSystem extends EntitySystem {
  constructor() {
    super(Matcher.all(Player, Position, Velocity));
  }
}

// ❌ 错误的用法 - 没有装饰器
class BadSystem extends EntitySystem {
  // 这样定义的系统可能在生产环境出现问题:
  // 1. 代码压缩后类名变化,无法正确识别
  // 2. 性能监控和调试工具显示不正确的名称
}

系统名称的作用

@ECSSystem('Combat')
class CombatSystem extends EntitySystem {
  protected onInitialize(): void {
    // 使用 systemName 属性访问系统名称
    console.log(`系统 ${this.systemName} 已初始化`);  // 输出: 系统 Combat 已初始化
  }
}

// 通过名称查找系统
const combat = scene.getSystemByName('Combat');

// 性能监控中会显示系统名称
const perfData = combatSystem.getPerformanceData();
console.log(`${combatSystem.systemName} 执行时间: ${perfData?.executionTime}ms`);

3. 合理的更新顺序

// 按逻辑顺序设置系统的更新时序
@ECSSystem('Input')
class InputSystem extends EntitySystem {
  constructor() {
    super();
    this.updateOrder = -100; // 最先处理输入
  }
}

@ECSSystem('Logic')
class GameLogicSystem extends EntitySystem {
  constructor() {
    super();
    this.updateOrder = 0; // 处理游戏逻辑
  }
}

@ECSSystem('Render')
class RenderSystem extends EntitySystem {
  constructor() {
    super();
    this.updateOrder = 100; // 最后进行渲染
  }
}

4. 避免在系统间直接引用

// ❌ 避免:系统间直接引用
@ECSSystem('Bad')
class BadSystem extends EntitySystem {
  private otherSystem: SomeOtherSystem; // 避免直接引用其他系统
}

// ✅ 推荐:通过事件系统通信
@ECSSystem('Good')
class GoodSystem extends EntitySystem {
  protected process(entities: readonly Entity[]): void {
    // 通过事件系统与其他系统通信
    this.scene?.eventSystem.emitSync('data_updated', { entities });
  }
}

5. 及时清理资源

@ECSSystem('Resource')
class ResourceSystem extends EntitySystem {
  private resources: Map<string, any> = new Map();

  protected onDestroy(): void {
    // 清理资源
    for (const [key, resource] of this.resources) {
      if (resource.dispose) {
        resource.dispose();
      }
    }
    this.resources.clear();
  }
}

系统是 ECS 架构的逻辑处理核心,正确设计和使用系统能让你的游戏代码更加模块化、高效和易于维护。