299 lines
12 KiB
TypeScript
299 lines
12 KiB
TypeScript
|
|
import { Scene } from '../../../src/ECS/Scene';
|
||
|
|
import { Entity } from '../../../src/ECS/Entity';
|
||
|
|
import { Component } from '../../../src/ECS/Component';
|
||
|
|
import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem';
|
||
|
|
import { Matcher } from '../../../src/ECS/Utils/Matcher';
|
||
|
|
import { ComponentTypeManager } from '../../../src/ECS/Utils/ComponentTypeManager';
|
||
|
|
|
||
|
|
class PositionComponent extends Component {
|
||
|
|
constructor(public x: number = 0, public y: number = 0) {
|
||
|
|
super();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class VelocityComponent extends Component {
|
||
|
|
constructor(public vx: number = 0, public vy: number = 0) {
|
||
|
|
super();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class HealthComponent extends Component {
|
||
|
|
constructor(public health: number = 100) {
|
||
|
|
super();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class MovementSystem extends EntitySystem {
|
||
|
|
public processedEntities: Entity[] = [];
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
super(Matcher.empty().all(PositionComponent, VelocityComponent));
|
||
|
|
}
|
||
|
|
|
||
|
|
public override onChanged(entity: Entity): void {
|
||
|
|
if (this.matcher.isInterestedEntity(entity)) {
|
||
|
|
if (!this.processedEntities.includes(entity)) {
|
||
|
|
this.processedEntities.push(entity);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
const index = this.processedEntities.indexOf(entity);
|
||
|
|
if (index !== -1) {
|
||
|
|
this.processedEntities.splice(index, 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class HealthSystem extends EntitySystem {
|
||
|
|
public processedEntities: Entity[] = [];
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
super(Matcher.empty().all(HealthComponent).exclude(VelocityComponent));
|
||
|
|
}
|
||
|
|
|
||
|
|
public override onChanged(entity: Entity): void {
|
||
|
|
if (this.matcher.isInterestedEntity(entity)) {
|
||
|
|
if (!this.processedEntities.includes(entity)) {
|
||
|
|
this.processedEntities.push(entity);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
const index = this.processedEntities.indexOf(entity);
|
||
|
|
if (index !== -1) {
|
||
|
|
this.processedEntities.splice(index, 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class CombatSystem extends EntitySystem {
|
||
|
|
public processedEntities: Entity[] = [];
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
super(Matcher.empty()
|
||
|
|
.all(PositionComponent)
|
||
|
|
.one(VelocityComponent, HealthComponent));
|
||
|
|
}
|
||
|
|
|
||
|
|
public override onChanged(entity: Entity): void {
|
||
|
|
if (this.matcher.isInterestedEntity(entity)) {
|
||
|
|
if (!this.processedEntities.includes(entity)) {
|
||
|
|
this.processedEntities.push(entity);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
const index = this.processedEntities.indexOf(entity);
|
||
|
|
if (index !== -1) {
|
||
|
|
this.processedEntities.splice(index, 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('Matcher时序集成测试', () => {
|
||
|
|
let scene: Scene;
|
||
|
|
let movementSystem: MovementSystem;
|
||
|
|
let healthSystem: HealthSystem;
|
||
|
|
let combatSystem: CombatSystem;
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
ComponentTypeManager.instance.reset();
|
||
|
|
scene = new Scene();
|
||
|
|
movementSystem = new MovementSystem();
|
||
|
|
healthSystem = new HealthSystem();
|
||
|
|
combatSystem = new CombatSystem();
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('先添加实体,再添加系统', () => {
|
||
|
|
test('MovementSystem应该正确过滤实体', () => {
|
||
|
|
// 创建各种类型的实体
|
||
|
|
const movingEntity = scene.createEntity('MovingEntity');
|
||
|
|
movingEntity.addComponent(new PositionComponent(10, 20));
|
||
|
|
movingEntity.addComponent(new VelocityComponent(1, 1));
|
||
|
|
|
||
|
|
const staticEntity = scene.createEntity('StaticEntity');
|
||
|
|
staticEntity.addComponent(new PositionComponent(30, 40));
|
||
|
|
|
||
|
|
const healthEntity = scene.createEntity('HealthEntity');
|
||
|
|
healthEntity.addComponent(new HealthComponent(80));
|
||
|
|
|
||
|
|
const complexEntity = scene.createEntity('ComplexEntity');
|
||
|
|
complexEntity.addComponent(new PositionComponent(50, 60));
|
||
|
|
complexEntity.addComponent(new VelocityComponent(2, 2));
|
||
|
|
complexEntity.addComponent(new HealthComponent(120));
|
||
|
|
|
||
|
|
// 添加系统 - 应该发现已存在的匹配实体
|
||
|
|
scene.addEntityProcessor(movementSystem);
|
||
|
|
|
||
|
|
// MovementSystem需要Position + Velocity
|
||
|
|
expect(movementSystem.processedEntities).toHaveLength(2);
|
||
|
|
expect(movementSystem.processedEntities).toContain(movingEntity);
|
||
|
|
expect(movementSystem.processedEntities).toContain(complexEntity);
|
||
|
|
expect(movementSystem.processedEntities).not.toContain(staticEntity);
|
||
|
|
expect(movementSystem.processedEntities).not.toContain(healthEntity);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('HealthSystem应该正确过滤实体', () => {
|
||
|
|
// 创建实体
|
||
|
|
const healthOnlyEntity = scene.createEntity('HealthOnly');
|
||
|
|
healthOnlyEntity.addComponent(new HealthComponent(100));
|
||
|
|
|
||
|
|
const movingHealthEntity = scene.createEntity('MovingHealth');
|
||
|
|
movingHealthEntity.addComponent(new HealthComponent(80));
|
||
|
|
movingHealthEntity.addComponent(new VelocityComponent(1, 1));
|
||
|
|
|
||
|
|
const positionHealthEntity = scene.createEntity('PositionHealth');
|
||
|
|
positionHealthEntity.addComponent(new PositionComponent(10, 20));
|
||
|
|
positionHealthEntity.addComponent(new HealthComponent(90));
|
||
|
|
|
||
|
|
// 添加系统
|
||
|
|
scene.addEntityProcessor(healthSystem);
|
||
|
|
|
||
|
|
// HealthSystem需要Health但不要Velocity
|
||
|
|
expect(healthSystem.processedEntities).toHaveLength(2);
|
||
|
|
expect(healthSystem.processedEntities).toContain(healthOnlyEntity);
|
||
|
|
expect(healthSystem.processedEntities).toContain(positionHealthEntity);
|
||
|
|
expect(healthSystem.processedEntities).not.toContain(movingHealthEntity); // 被exclude排除
|
||
|
|
});
|
||
|
|
|
||
|
|
test('CombatSystem复杂匹配应该正确工作', () => {
|
||
|
|
// 创建实体
|
||
|
|
const warriorEntity = scene.createEntity('Warrior');
|
||
|
|
warriorEntity.addComponent(new PositionComponent(10, 20));
|
||
|
|
warriorEntity.addComponent(new VelocityComponent(1, 1));
|
||
|
|
|
||
|
|
const guardEntity = scene.createEntity('Guard');
|
||
|
|
guardEntity.addComponent(new PositionComponent(30, 40));
|
||
|
|
guardEntity.addComponent(new HealthComponent(100));
|
||
|
|
|
||
|
|
const archerEntity = scene.createEntity('Archer');
|
||
|
|
archerEntity.addComponent(new PositionComponent(50, 60));
|
||
|
|
archerEntity.addComponent(new VelocityComponent(2, 2));
|
||
|
|
archerEntity.addComponent(new HealthComponent(80));
|
||
|
|
|
||
|
|
const structureEntity = scene.createEntity('Structure');
|
||
|
|
structureEntity.addComponent(new PositionComponent(70, 80));
|
||
|
|
|
||
|
|
// 添加系统
|
||
|
|
scene.addEntityProcessor(combatSystem);
|
||
|
|
|
||
|
|
// CombatSystem需要Position + (Velocity OR Health)
|
||
|
|
expect(combatSystem.processedEntities).toHaveLength(3);
|
||
|
|
expect(combatSystem.processedEntities).toContain(warriorEntity); // Position + Velocity
|
||
|
|
expect(combatSystem.processedEntities).toContain(guardEntity); // Position + Health
|
||
|
|
expect(combatSystem.processedEntities).toContain(archerEntity); // Position + Both
|
||
|
|
expect(combatSystem.processedEntities).not.toContain(structureEntity); // 只有Position
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('先添加系统,再添加实体', () => {
|
||
|
|
test('系统应该动态发现新添加的实体', () => {
|
||
|
|
// 先添加系统
|
||
|
|
scene.addEntityProcessor(movementSystem);
|
||
|
|
scene.addEntityProcessor(healthSystem);
|
||
|
|
scene.addEntityProcessor(combatSystem);
|
||
|
|
|
||
|
|
expect(movementSystem.processedEntities).toHaveLength(0);
|
||
|
|
expect(healthSystem.processedEntities).toHaveLength(0);
|
||
|
|
expect(combatSystem.processedEntities).toHaveLength(0);
|
||
|
|
|
||
|
|
// 添加匹配MovementSystem的实体
|
||
|
|
const movingEntity = scene.createEntity('MovingEntity');
|
||
|
|
movingEntity.addComponent(new PositionComponent(10, 20));
|
||
|
|
movingEntity.addComponent(new VelocityComponent(1, 1));
|
||
|
|
|
||
|
|
expect(movementSystem.processedEntities).toHaveLength(1);
|
||
|
|
expect(movementSystem.processedEntities).toContain(movingEntity);
|
||
|
|
expect(combatSystem.processedEntities).toContain(movingEntity); // 也匹配CombatSystem
|
||
|
|
|
||
|
|
// 添加只匹配HealthSystem的实体
|
||
|
|
const healthEntity = scene.createEntity('HealthEntity');
|
||
|
|
healthEntity.addComponent(new HealthComponent(100));
|
||
|
|
|
||
|
|
expect(healthSystem.processedEntities).toHaveLength(1);
|
||
|
|
expect(healthSystem.processedEntities).toContain(healthEntity);
|
||
|
|
expect(movementSystem.processedEntities).toHaveLength(1); // 不变
|
||
|
|
|
||
|
|
// 添加匹配CombatSystem但不匹配其他的实体
|
||
|
|
const guardEntity = scene.createEntity('GuardEntity');
|
||
|
|
guardEntity.addComponent(new PositionComponent(30, 40));
|
||
|
|
guardEntity.addComponent(new HealthComponent(120));
|
||
|
|
|
||
|
|
expect(combatSystem.processedEntities).toHaveLength(2);
|
||
|
|
expect(combatSystem.processedEntities).toContain(guardEntity);
|
||
|
|
expect(movementSystem.processedEntities).toHaveLength(1); // 不变
|
||
|
|
expect(healthSystem.processedEntities).toHaveLength(2); // 增加
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('混合时序和动态组件变化', () => {
|
||
|
|
test('实体组件的动态添加移除应该正确更新所有系统', () => {
|
||
|
|
// 先添加一些系统
|
||
|
|
scene.addEntityProcessor(movementSystem);
|
||
|
|
|
||
|
|
// 创建一个只有位置的实体
|
||
|
|
const entity = scene.createEntity('DynamicEntity');
|
||
|
|
entity.addComponent(new PositionComponent(10, 20));
|
||
|
|
|
||
|
|
expect(movementSystem.processedEntities).toHaveLength(0); // 缺少Velocity
|
||
|
|
|
||
|
|
// 后添加健康系统
|
||
|
|
scene.addEntityProcessor(healthSystem);
|
||
|
|
expect(healthSystem.processedEntities).toHaveLength(0); // 实体没有Health
|
||
|
|
|
||
|
|
// 添加速度组件 - 应该被MovementSystem发现
|
||
|
|
entity.addComponent(new VelocityComponent(1, 1));
|
||
|
|
expect(movementSystem.processedEntities).toHaveLength(1);
|
||
|
|
expect(movementSystem.processedEntities).toContain(entity);
|
||
|
|
|
||
|
|
// 添加战斗系统
|
||
|
|
scene.addEntityProcessor(combatSystem);
|
||
|
|
expect(combatSystem.processedEntities).toContain(entity); // Position + Velocity
|
||
|
|
|
||
|
|
// 添加健康组件
|
||
|
|
entity.addComponent(new HealthComponent(100));
|
||
|
|
expect(healthSystem.processedEntities).toHaveLength(0); // 被Velocity排除
|
||
|
|
expect(combatSystem.processedEntities).toContain(entity); // 仍然匹配
|
||
|
|
|
||
|
|
// 移除速度组件
|
||
|
|
const velocityComponent = entity.getComponent(VelocityComponent);
|
||
|
|
if (velocityComponent) {
|
||
|
|
entity.removeComponent(velocityComponent);
|
||
|
|
}
|
||
|
|
|
||
|
|
expect(movementSystem.processedEntities).toHaveLength(0); // 不再匹配
|
||
|
|
expect(healthSystem.processedEntities).toContain(entity); // 现在匹配了
|
||
|
|
expect(combatSystem.processedEntities).toContain(entity); // Position + Health
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('场景生命周期测试', () => {
|
||
|
|
test('场景begin()后系统过滤仍然正常工作', () => {
|
||
|
|
// 添加实体和系统
|
||
|
|
const entity1 = scene.createEntity('Entity1');
|
||
|
|
entity1.addComponent(new PositionComponent(10, 20));
|
||
|
|
entity1.addComponent(new VelocityComponent(1, 1));
|
||
|
|
|
||
|
|
scene.addEntityProcessor(movementSystem);
|
||
|
|
|
||
|
|
expect(movementSystem.processedEntities).toContain(entity1);
|
||
|
|
|
||
|
|
// 调用场景begin
|
||
|
|
scene.begin();
|
||
|
|
|
||
|
|
// 添加新实体应该仍然正常工作
|
||
|
|
const entity2 = scene.createEntity('Entity2');
|
||
|
|
entity2.addComponent(new PositionComponent(30, 40));
|
||
|
|
entity2.addComponent(new VelocityComponent(2, 2));
|
||
|
|
|
||
|
|
expect(movementSystem.processedEntities).toHaveLength(2);
|
||
|
|
expect(movementSystem.processedEntities).toContain(entity2);
|
||
|
|
|
||
|
|
// 动态添加系统也应该正常工作
|
||
|
|
scene.addEntityProcessor(healthSystem);
|
||
|
|
|
||
|
|
const healthEntity = scene.createEntity('HealthEntity');
|
||
|
|
healthEntity.addComponent(new HealthComponent(100));
|
||
|
|
|
||
|
|
expect(healthSystem.processedEntities).toContain(healthEntity);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|