From ccbfa78070a8709c795a35f68b6a34d4668f3ad5 Mon Sep 17 00:00:00 2001 From: YHH <359807859@qq.com> Date: Wed, 6 Aug 2025 09:39:08 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86QuerySystem=E5=9C=A8?= =?UTF-8?q?=E9=94=80=E6=AF=81=E5=AE=9E=E4=BD=93=E6=97=B6=E7=9A=84=E5=86=85?= =?UTF-8?q?=E5=AD=98=E6=B3=84=E6=BC=8F=E9=97=AE=E9=A2=98=20=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E4=BA=86=E5=AE=8C=E6=95=B4=E7=9A=84onAdded/onRemoved?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E7=B3=BB=E7=BB=9F=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=BA=86override=E4=BF=AE=E9=A5=B0=E7=AC=A6=E5=92=8C=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E5=85=BC=E5=AE=B9=E6=80=A7=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/simple-matcher-usage.ts | 121 ------------------ src/ECS/Entity.ts | 12 +- src/ECS/Scene.ts | 8 +- src/ECS/Systems/EntitySystem.ts | 75 +++++++++-- src/Utils/Extensions/TypeUtils.ts | 2 +- .../Core/ComponentStorage.comparison.test.ts | 18 ++- .../ECS/Core/SystemMultipleInitialize.test.ts | 9 +- 7 files changed, 105 insertions(+), 140 deletions(-) delete mode 100644 examples/simple-matcher-usage.ts diff --git a/examples/simple-matcher-usage.ts b/examples/simple-matcher-usage.ts deleted file mode 100644 index 94aef834..00000000 --- a/examples/simple-matcher-usage.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * 简化后的Matcher使用示例 - * 展示框架自动处理QuerySystem的优雅设计 - */ - -import { Scene } from '../src/ECS/Scene'; -import { Component } from '../src/ECS/Component'; -import { Matcher } from '../src/ECS/Utils/Matcher'; - -// 示例组件 -class Position extends Component { - constructor(public x: number = 0, public y: number = 0) { - super(); - } -} - -class Velocity extends Component { - constructor(public vx: number = 0, public vy: number = 0) { - super(); - } -} - -class Health extends Component { - constructor(public hp: number = 100) { - super(); - } -} - -class Dead extends Component {} - -function demonstrateSimplifiedMatcher() { - console.log('=== 简化的Matcher API示例 ===\n'); - - // 创建场景 - QuerySystem自动创建 - const scene = new Scene(); - scene.begin(); - - // 创建测试实体 - const player = scene.createEntity('Player'); - player.addComponent(new Position(100, 200)); - player.addComponent(new Velocity(5, 0)); - player.addComponent(new Health(100)); - - const enemy = scene.createEntity('Enemy'); - enemy.addComponent(new Position(300, 200)); - enemy.addComponent(new Health(50)); - - const corpse = scene.createEntity('Corpse'); - corpse.addComponent(new Position(150, 150)); - corpse.addComponent(new Dead()); - - // ===== 推荐的新API ===== - - // 1. 直接使用scene.querySystem创建Matcher - const movingEntities = Matcher.create(scene.querySystem) - .all(Position, Velocity) - .query(); - - console.log('移动实体:', movingEntities.map(e => e.name)); - - // 2. 复合查询 - 活着的实体 - const aliveEntities = Matcher.create(scene.querySystem) - .all(Health) - .none(Dead) - .query(); - - console.log('活着的实体:', aliveEntities.map(e => e.name)); - - // 3. 实用方法 - const healthMatcher = Matcher.create(scene.querySystem).all(Health); - console.log(`有血量的实体数量: ${healthMatcher.count()}`); - console.log(`玩家是否有血量: ${healthMatcher.matches(player)}`); - - // 4. 系统中使用Matcher的典型模式 - class MovementSystem { - private movementMatcher: Matcher; - - constructor(scene: Scene) { - // 在系统初始化时创建Matcher - this.movementMatcher = Matcher.create(scene.querySystem) - .all(Position, Velocity); - } - - update() { - // 高效的批量查询 - const movableEntities = this.movementMatcher.query(); - - for (const entity of movableEntities) { - const pos = entity.getComponent(Position)!; - const vel = entity.getComponent(Velocity)!; - - pos.x += vel.vx; - pos.y += vel.vy; - } - } - } - - // 5. 创建并使用系统 - const movementSystem = new MovementSystem(scene); - movementSystem.update(); - - console.log('玩家更新后位置:', player.getComponent(Position)); - - scene.end(); -} - -// ===== 展示设计哲学 ===== -function designPhilosophy() { - console.log('\n=== 设计哲学 ==='); - console.log('✅ QuerySystem是框架核心,总是存在'); - console.log('✅ Matcher强制要求QuerySystem,避免回退复杂性'); - console.log('✅ 清晰的错误提示,引导正确使用'); - console.log('✅ 旧API保持兼容,但明确标记deprecated'); - console.log('✅ 新API简洁明了,符合现代设计原则'); -} - -// 运行示例 -if (require.main === module) { - demonstrateSimplifiedMatcher(); - designPhilosophy(); -} \ No newline at end of file diff --git a/src/ECS/Entity.ts b/src/ECS/Entity.ts index 644abbdd..cb89fd05 100644 --- a/src/ECS/Entity.ts +++ b/src/ECS/Entity.ts @@ -933,8 +933,16 @@ export class Entity { this.removeAllComponents(); // 从场景中移除 - if (this.scene && this.scene.entities) { - this.scene.entities.remove(this); + if (this.scene) { + // 从查询系统中移除 + if (this.scene.querySystem) { + this.scene.querySystem.removeEntity(this); + } + + // 从实体列表中移除 + if (this.scene.entities) { + this.scene.entities.remove(this); + } } } diff --git a/src/ECS/Scene.ts b/src/ECS/Scene.ts index b3904808..c9dda952 100644 --- a/src/ECS/Scene.ts +++ b/src/ECS/Scene.ts @@ -5,7 +5,7 @@ import { IdentifierPool } from './Utils/IdentifierPool'; import { EntitySystem } from './Systems/EntitySystem'; import { ComponentStorageManager } from './Core/ComponentStorage'; import { QuerySystem } from './Core/QuerySystem'; -import { TypeSafeEventSystem, GlobalEventSystem } from './Core/EventSystem'; +import { TypeSafeEventSystem } from './Core/EventSystem'; import { EventBus } from './Core/EventBus'; /** @@ -164,6 +164,9 @@ export class Scene { // 移除所有实体 this.entities.removeAllEntities(); + // 清理查询系统中的实体引用和缓存 + this.querySystem.setEntities([]); + // 清空组件存储 this.componentStorageManager.clear(); @@ -281,6 +284,9 @@ export class Scene { */ public destroyAllEntities() { this.entities.removeAllEntities(); + + // 清理查询系统中的实体引用和缓存 + this.querySystem.setEntities([]); } /** diff --git a/src/ECS/Systems/EntitySystem.ts b/src/ECS/Systems/EntitySystem.ts index 72ed1cf6..8f46c238 100644 --- a/src/ECS/Systems/EntitySystem.ts +++ b/src/ECS/Systems/EntitySystem.ts @@ -35,6 +35,8 @@ export abstract class EntitySystem implements ISystemBase { private _systemName: string; private _initialized: boolean = false; private _matcher: Matcher; + private _trackedEntities: Set = new Set(); + private _lastQueryResult: Entity[] = []; /** * 获取系统处理的实体列表(动态查询) @@ -134,6 +136,10 @@ export abstract class EntitySystem implements ISystemBase { * 子类可以重写此方法进行初始化操作。 */ protected onInitialize(): void { + // 初始化时触发一次实体查询,以便正确跟踪现有实体 + if (this.scene) { + this.queryEntities(); + } // 子类可以重写此方法进行初始化 } @@ -144,6 +150,8 @@ export abstract class EntitySystem implements ISystemBase { */ public reset(): void { this._initialized = false; + this._trackedEntities.clear(); + this._lastQueryResult = []; } /** @@ -151,24 +159,30 @@ export abstract class EntitySystem implements ISystemBase { */ private queryEntities(): Entity[] { if (!this.scene?.querySystem || !this._matcher) { + this._lastQueryResult = []; return []; } const condition = this._matcher.getCondition(); const querySystem = this.scene.querySystem; + let currentEntities: Entity[] = []; // 空条件返回所有实体 if (this._matcher.isEmpty()) { - return querySystem.getAllEntities(); + currentEntities = querySystem.getAllEntities(); + } else if (this.isSingleCondition(condition)) { + // 单一条件优化查询 + currentEntities = this.executeSingleConditionQuery(condition, querySystem); + } else { + // 复合查询 + currentEntities = this.executeComplexQuery(condition, querySystem); } - // 单一条件优化查询 - if (this.isSingleCondition(condition)) { - return this.executeSingleConditionQuery(condition, querySystem); - } - - // 复合查询 - return this.executeComplexQuery(condition, querySystem); + // 检查实体变化并触发回调 + this.updateEntityTracking(currentEntities); + + this._lastQueryResult = currentEntities; + return currentEntities; } /** @@ -436,4 +450,49 @@ export abstract class EntitySystem implements ISystemBase { return `${this._systemName}[${entityCount} entities]${perfInfo}`; } + + /** + * 更新实体跟踪,检查新增和移除的实体 + */ + private updateEntityTracking(currentEntities: Entity[]): void { + const currentSet = new Set(currentEntities); + + // 检查新增的实体 + for (const entity of currentEntities) { + if (!this._trackedEntities.has(entity)) { + this._trackedEntities.add(entity); + this.onAdded(entity); + } + } + + // 检查移除的实体 + for (const entity of this._trackedEntities) { + if (!currentSet.has(entity)) { + this._trackedEntities.delete(entity); + this.onRemoved(entity); + } + } + } + + /** + * 当实体被添加到系统时调用 + * + * 子类可以重写此方法来处理实体添加事件。 + * + * @param entity 被添加的实体 + */ + protected onAdded(_entity: Entity): void { + // 子类可以重写此方法 + } + + /** + * 当实体从系统中移除时调用 + * + * 子类可以重写此方法来处理实体移除事件。 + * + * @param entity 被移除的实体 + */ + protected onRemoved(_entity: Entity): void { + // 子类可以重写此方法 + } } \ No newline at end of file diff --git a/src/Utils/Extensions/TypeUtils.ts b/src/Utils/Extensions/TypeUtils.ts index d124e20a..287634d1 100644 --- a/src/Utils/Extensions/TypeUtils.ts +++ b/src/Utils/Extensions/TypeUtils.ts @@ -8,7 +8,7 @@ export class TypeUtils { * @param obj 对象 * @returns 对象的构造函数 */ - public static getType(obj: Record & { constructor: Function }) { + public static getType(obj: any) { return obj.constructor; } } \ No newline at end of file diff --git a/tests/ECS/Core/ComponentStorage.comparison.test.ts b/tests/ECS/Core/ComponentStorage.comparison.test.ts index 3b024ba0..d1cbb45d 100644 --- a/tests/ECS/Core/ComponentStorage.comparison.test.ts +++ b/tests/ECS/Core/ComponentStorage.comparison.test.ts @@ -9,8 +9,9 @@ class TestPositionComponent extends Component { public y: number = 0; public z: number = 0; - constructor(x = 0, y = 0, z = 0) { + constructor(...args: unknown[]) { super(); + const [x = 0, y = 0, z = 0] = args as [number?, number?, number?]; this.x = x; this.y = y; this.z = z; @@ -24,8 +25,9 @@ class TestVelocityComponent extends Component { public vz: number = 0; public maxSpeed: number = 100; - constructor(vx = 0, vy = 0, vz = 0) { + constructor(...args: unknown[]) { super(); + const [vx = 0, vy = 0, vz = 0] = args as [number?, number?, number?]; this.vx = vx; this.vy = vy; this.vz = vz; @@ -38,8 +40,9 @@ class TestHealthComponent extends Component { public max: number = 100; public regeneration: number = 1; - constructor(current = 100, max = 100) { + constructor(...args: unknown[]) { super(); + const [current = 100, max = 100] = args as [number?, number?]; this.current = current; this.max = max; } @@ -51,8 +54,9 @@ class OriginalPositionComponent extends Component { public y: number = 0; public z: number = 0; - constructor(x = 0, y = 0, z = 0) { + constructor(...args: unknown[]) { super(); + const [x = 0, y = 0, z = 0] = args as [number?, number?, number?]; this.x = x; this.y = y; this.z = z; @@ -65,8 +69,9 @@ class OriginalVelocityComponent extends Component { public vz: number = 0; public maxSpeed: number = 100; - constructor(vx = 0, vy = 0, vz = 0) { + constructor(...args: unknown[]) { super(); + const [vx = 0, vy = 0, vz = 0] = args as [number?, number?, number?]; this.vx = vx; this.vy = vy; this.vz = vz; @@ -78,8 +83,9 @@ class OriginalHealthComponent extends Component { public max: number = 100; public regeneration: number = 1; - constructor(current = 100, max = 100) { + constructor(...args: unknown[]) { super(); + const [current = 100, max = 100] = args as [number?, number?]; this.current = current; this.max = max; } diff --git a/tests/ECS/Core/SystemMultipleInitialize.test.ts b/tests/ECS/Core/SystemMultipleInitialize.test.ts index 74a67f19..a48676da 100644 --- a/tests/ECS/Core/SystemMultipleInitialize.test.ts +++ b/tests/ECS/Core/SystemMultipleInitialize.test.ts @@ -24,9 +24,16 @@ class TrackingSystem extends EntitySystem { const wasInitialized = (this as any)._initialized; super.initialize(); - // 只有在真正执行初始化时才增加计数 + // 只有在真正执行初始化时才增加计数和处理实体 if (!wasInitialized) { this.initializeCallCount++; + + // 处理所有现有实体 + if (this.scene) { + for (const entity of this.scene.entities.buffer) { + this.onChanged(entity); + } + } } }