Files
esengine/docs/guide/system.md
YHH 13a149c3a2 perf(core): 优化 EntitySystem 迭代性能,添加 CommandBuffer 延迟命令 (#281)
* perf(core): 优化 EntitySystem 迭代性能,添加 CommandBuffer 延迟命令

ReactiveQuery 快照优化:
- 添加快照机制,避免每帧拷贝数组
- 只在实体列表变化时创建新快照
- 静态场景下多个系统共享同一快照

CommandBuffer 延迟命令系统:
- 支持延迟添加/移除组件、销毁实体、设置实体激活状态
- 每个系统拥有独立的 commands 属性
- 命令在帧末统一执行,避免迭代过程中修改实体列表

Scene 更新:
- 在 lateUpdate 后自动刷新所有系统的命令缓冲区

文档:
- 更新系统文档,添加 CommandBuffer 使用说明

* fix(ci): upgrade first-interaction action to v1.3.0

Fix Docker build failure in welcome workflow.

* fix(ci): upgrade pnpm/action-setup to v4 and fix unused import

- Upgrade pnpm/action-setup@v2 to v4 in all workflow files
- Remove unused CommandType import in CommandBuffer.test.ts

* fix(ci): remove duplicate pnpm version specification
2025-12-05 17:24:33 +08:00

924 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 系统架构
在 ECS 架构中系统System是处理业务逻辑的地方。系统负责对拥有特定组件组合的实体执行操作是 ECS 架构的逻辑处理单元。
## 基本概念
系统是继承自 `EntitySystem` 抽象基类的具体类,用于:
- 定义实体的处理逻辑(如移动、碰撞检测、渲染等)
- 根据组件组合筛选需要处理的实体
- 提供生命周期管理和性能监控
- 管理实体的添加、移除事件
## 系统类型
框架提供了几种不同类型的系统基类:
### EntitySystem - 基础系统
最基础的系统类,所有其他系统都继承自它:
```typescript
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 - 处理系统
适用于不需要逐个处理实体的系统:
```typescript
@ECSSystem('Physics')
class PhysicsSystem extends ProcessingSystem {
constructor() {
super(); // 不需要指定 Matcher
}
public processSystem(): void {
// 执行物理世界步进
this.physicsWorld.step(Time.deltaTime);
}
}
```
### PassiveSystem - 被动系统
被动系统不进行主动处理,主要用于监听实体的添加和移除事件:
```typescript
@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 - 间隔系统
按固定时间间隔执行的系统:
```typescript
@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系统](/guide/worker-system)**
## 实体匹配器 (Matcher)
Matcher 用于定义系统需要处理哪些实体。它提供了灵活的条件组合:
### 基本匹配条件
```typescript
// 必须同时拥有 Position 和 Velocity 组件
const matcher1 = Matcher.all(Position, Velocity);
// 至少拥有 Health 或 Shield 组件之一
const matcher2 = Matcher.any(Health, Shield);
// 不能拥有 Dead 组件
const matcher3 = Matcher.none(Dead);
```
### 复合匹配条件
```typescript
// 复杂的组合条件
const complexMatcher = Matcher.all(Position, Velocity)
.any(Player, Enemy)
.none(Dead, Disabled);
@ECSSystem('Combat')
class CombatSystem extends EntitySystem {
constructor() {
super(complexMatcher);
}
}
```
### 特殊匹配条件
```typescript
// 按标签匹配
const tagMatcher = Matcher.byTag(1); // 匹配标签为 1 的实体
// 按名称匹配
const nameMatcher = Matcher.byName("Player"); // 匹配名称为 "Player" 的实体
// 单组件匹配
const componentMatcher = Matcher.byComponent(Health); // 匹配拥有 Health 组件的实体
// 不匹配任何实体
const nothingMatcher = Matcher.nothing(); // 用于只需要生命周期回调的系统
```
### 空匹配器 vs Nothing 匹配器
```typescript
// 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 和实体查询的详细用法,请参考 [实体查询系统](/guide/entity-query) 文档。
## 系统生命周期
系统提供了完整的生命周期回调:
```typescript
@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('系统销毁');
// 系统从场景移除时调用,用于清理资源
}
}
```
## 实体事件监听
系统可以监听实体的添加和移除事件:
```typescript
@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 的调用时机
> ⚠️ **注意**`onAdded` 和 `onRemoved` 回调是**同步调用**的,会在 `addComponent`/`removeComponent` 返回**之前**立即执行。
这意味着:
```typescript
// ❌ 错误的用法:链式赋值在 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 中安全地修改组件
`process``lateProcess` 中迭代实体时,可以安全地添加或移除组件,不会影响当前的迭代过程:
```typescript
@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.2.22+**
CommandBuffer 提供了一种延迟执行实体操作的机制。当你需要在迭代过程中销毁实体或进行其他可能影响迭代的操作时,使用 CommandBuffer 可以将这些操作推迟到帧末统一执行。
### 基本用法
每个 EntitySystem 都内置了 `commands` 属性:
```typescript
@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. **跨系统协调**:一个系统标记,另一个系统响应
```typescript
// 示例:敌人死亡系统
@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()` 后命令队列会清空
## 系统属性和方法
### 重要属性
```typescript
@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}`); // 所属场景
}
}
```
### 实体访问
```typescript
protected process(entities: readonly Entity[]): void {
// 方式1使用参数中的实体列表
for (const entity of entities) {
// 处理实体
}
// 方式2使用 this.entities 属性(与参数相同)
for (const entity of this.entities) {
// 处理实体
}
}
```
### 控制系统执行
```typescript
@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;
}
}
```
## 事件系统集成
系统可以方便地监听和发送事件:
```typescript
@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 });
}
}
}
}
```
## 性能监控
系统内置了性能监控功能:
```typescript
@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();
}
}
```
## 系统管理
### 添加系统到场景
框架提供了两种方式添加系统:传入实例或传入类型(自动依赖注入)。
```typescript
// 在场景子类中添加系统
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` 接口,支持通过依赖注入获取其他服务或系统:
```typescript
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` 属性决定,数值越小越先执行:
```typescript
@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`(添加顺序)作为第二排序条件,确保排序结果稳定可预测:
```typescript
// 这两个系统 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` 的系统按照添加顺序执行,避免了排序不稳定导致的随机行为。
## 复杂系统示例
### 碰撞检测系统
```typescript
@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; // 简化示例
}
}
```
### 状态机系统
```typescript
@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. 系统单一职责
```typescript
// ✅ 好的系统设计 - 职责单一
@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` 是系统类必须使用的装饰器,它为系统提供类型标识和元数据管理。
#### 为什么必须使用
| 功能 | 说明 |
|------|------|
| **类型识别** | 提供稳定的系统名称,代码混淆后仍能正确识别 |
| **调试支持** | 在性能监控、日志和调试工具中显示可读的系统名称 |
| **系统管理** | 通过名称查找和管理系统 |
| **序列化支持** | 场景序列化时可以记录系统配置 |
#### 基本语法
```typescript
@ECSSystem(systemName: string)
```
- `systemName`: 系统的名称,建议使用描述性的名称
#### 使用示例
```typescript
// ✅ 正确的用法
@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. 性能监控和调试工具显示不正确的名称
}
```
#### 系统名称的作用
```typescript
@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. 合理的更新顺序
```typescript
// 按逻辑顺序设置系统的更新时序
@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. 避免在系统间直接引用
```typescript
// ❌ 避免:系统间直接引用
@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. 及时清理资源
```typescript
@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 架构的逻辑处理核心,正确设计和使用系统能让你的游戏代码更加模块化、高效和易于维护。