662 lines
19 KiB
Markdown
662 lines
19 KiB
Markdown
# 系统(System)详解指南
|
||
|
||
系统是ECS架构中的"S",负责处理拥有特定组件的实体。本指南详细介绍框架中的各种系统类型及其使用方法。
|
||
|
||
## 系统基础概念
|
||
|
||
### 什么是系统?
|
||
|
||
系统是处理游戏逻辑的地方,它们:
|
||
- **专注单一职责** - 每个系统只处理一种类型的逻辑
|
||
- **自动执行** - 系统会在每帧自动被调用
|
||
- **基于组件过滤** - 只处理包含特定组件的实体
|
||
- **高性能** - 利用ECS的数据局部性优势
|
||
|
||
### 系统的工作原理
|
||
|
||
```typescript
|
||
// 系统的基本工作流程:
|
||
// 1. 查询符合条件的实体
|
||
// 2. 遍历这些实体
|
||
// 3. 读取/修改实体的组件数据
|
||
// 4. 执行游戏逻辑
|
||
|
||
class MovementSystem extends EntitySystem {
|
||
process(entities: Entity[]) {
|
||
for (const entity of entities) {
|
||
const position = entity.getComponent(PositionComponent);
|
||
const velocity = entity.getComponent(VelocityComponent);
|
||
|
||
// 更新位置 = 当前位置 + 速度 * 时间
|
||
position.x += velocity.x * Time.deltaTime;
|
||
position.y += velocity.y * Time.deltaTime;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 系统类型详解
|
||
|
||
### 1. EntitySystem - 基础系统
|
||
|
||
最常用的系统类型,每帧处理所有符合条件的实体。
|
||
|
||
```typescript
|
||
import { EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
|
||
|
||
class HealthSystem extends EntitySystem {
|
||
constructor() {
|
||
// 使用Matcher创建查询条件
|
||
super(Matcher.all(HealthComponent));
|
||
// 或者使用链式语法
|
||
// super(Matcher.empty().all(HealthComponent));
|
||
}
|
||
|
||
// 主要处理逻辑
|
||
protected process(entities: Entity[]) {
|
||
// 直接使用传入的entities参数,已经是匹配的实体
|
||
for (const entity of entities) {
|
||
const health = entity.getComponent(HealthComponent)!;
|
||
|
||
// 处理生命值逻辑
|
||
if (health.currentHealth <= 0) {
|
||
this.handleDeath(entity);
|
||
} else if (health.currentHealth < health.maxHealth) {
|
||
this.handleRegeneration(health);
|
||
}
|
||
}
|
||
}
|
||
|
||
private handleDeath(entity: Entity) {
|
||
// 添加死亡标记
|
||
entity.addComponent(new DeadComponent());
|
||
|
||
// 触发死亡事件
|
||
const eventBus = this.scene.entityManager.eventBus;
|
||
eventBus.emit('entity:died', {
|
||
entityId: entity.id,
|
||
entityName: entity.name
|
||
});
|
||
}
|
||
|
||
private handleRegeneration(health: HealthComponent) {
|
||
// 缓慢恢复生命值
|
||
health.currentHealth += health.regenRate * Time.deltaTime;
|
||
health.currentHealth = Math.min(health.currentHealth, health.maxHealth);
|
||
}
|
||
}
|
||
```
|
||
|
||
**适用场景:**
|
||
- 移动系统
|
||
- 渲染系统
|
||
- 碰撞检测系统
|
||
- AI系统
|
||
|
||
### 2. ProcessingSystem - 简化处理系统
|
||
|
||
不需要处理具体实体,主要用于执行全局逻辑或不依赖特定实体的系统处理。
|
||
|
||
```typescript
|
||
import { ProcessingSystem, Matcher } from '@esengine/ecs-framework';
|
||
|
||
class GameLogicSystem extends ProcessingSystem {
|
||
constructor() {
|
||
// ProcessingSystem可以不指定Matcher,或使用空Matcher
|
||
super(Matcher.empty());
|
||
}
|
||
|
||
// 处理系统逻辑(每帧执行)
|
||
public processSystem() {
|
||
// 执行全局游戏逻辑
|
||
this.updateGameState();
|
||
this.checkWinConditions();
|
||
this.updateUI();
|
||
}
|
||
|
||
private updateGameState() {
|
||
// 更新游戏状态逻辑
|
||
console.log("更新游戏状态");
|
||
}
|
||
|
||
private checkWinConditions() {
|
||
// 检查胜利条件
|
||
const players = this.scene.findEntitiesByTag(EntityTags.PLAYER);
|
||
const enemies = this.scene.findEntitiesByTag(EntityTags.ENEMY);
|
||
|
||
if (enemies.length === 0) {
|
||
this.triggerVictory();
|
||
} else if (players.length === 0) {
|
||
this.triggerGameOver();
|
||
}
|
||
}
|
||
|
||
private updateUI() {
|
||
// 更新UI显示
|
||
const gameTime = Time.totalTime;
|
||
console.log(`游戏时间: ${gameTime.toFixed(1)}秒`);
|
||
}
|
||
|
||
private triggerVictory() {
|
||
console.log("游戏胜利!");
|
||
// 处理胜利逻辑
|
||
}
|
||
|
||
private triggerGameOver() {
|
||
console.log("游戏结束!");
|
||
// 处理游戏结束逻辑
|
||
}
|
||
}
|
||
```
|
||
|
||
**适用场景:**
|
||
- 全局游戏逻辑系统
|
||
- 胜负判断系统
|
||
- UI更新系统
|
||
- 不依赖特定实体的处理
|
||
|
||
## AI系统示例
|
||
|
||
下面是一个完整的AI系统示例,展示EntitySystem的典型用法:
|
||
|
||
```typescript
|
||
import { EntitySystem, Matcher, Entity } from '@esengine/ecs-framework';
|
||
|
||
enum AIState {
|
||
IDLE,
|
||
PATROL,
|
||
CHASE,
|
||
ATTACK
|
||
}
|
||
|
||
class AISystem extends EntitySystem {
|
||
constructor() {
|
||
// 复杂匹配条件可以使用链式语法
|
||
super(Matcher.empty().all(AIComponent, PositionComponent));
|
||
// 或者使用简洁语法
|
||
// super(Matcher.all(AIComponent, PositionComponent));
|
||
}
|
||
|
||
// 处理所有匹配的实体
|
||
protected process(entities: Entity[]) {
|
||
for (const entity of entities) {
|
||
this.processEntity(entity);
|
||
}
|
||
}
|
||
|
||
// 处理单个实体的逻辑(自定义方法)
|
||
private processEntity(entity: Entity) {
|
||
const ai = entity.getComponent(AIComponent);
|
||
const position = entity.getComponent(PositionComponent);
|
||
|
||
switch (ai.state) {
|
||
case AIState.IDLE:
|
||
this.processIdle(entity, ai);
|
||
break;
|
||
case AIState.PATROL:
|
||
this.processPatrol(entity, ai, position);
|
||
break;
|
||
case AIState.CHASE:
|
||
this.processChase(entity, ai, position);
|
||
break;
|
||
case AIState.ATTACK:
|
||
this.processAttack(entity, ai);
|
||
break;
|
||
}
|
||
}
|
||
|
||
private processIdle(entity: Entity, ai: AIComponent) {
|
||
ai.idleTimer += Time.deltaTime;
|
||
|
||
if (ai.idleTimer >= ai.idleTime) {
|
||
ai.state = AIState.PATROL;
|
||
ai.idleTimer = 0;
|
||
}
|
||
|
||
// 检查附近是否有玩家
|
||
const nearbyPlayer = this.findNearbyPlayer(entity, ai.detectionRange);
|
||
if (nearbyPlayer) {
|
||
ai.state = AIState.CHASE;
|
||
ai.target = nearbyPlayer;
|
||
}
|
||
}
|
||
|
||
private processPatrol(entity: Entity, ai: AIComponent, position: PositionComponent) {
|
||
// 简单的来回巡逻
|
||
if (!ai.patrolTarget) {
|
||
ai.patrolTarget = this.getNextPatrolPoint(ai);
|
||
}
|
||
|
||
const direction = ai.patrolTarget.subtract(position);
|
||
const distance = direction.length();
|
||
|
||
if (distance < 10) {
|
||
ai.patrolTarget = this.getNextPatrolPoint(ai);
|
||
} else {
|
||
const normalized = direction.normalize();
|
||
position.x += normalized.x * ai.moveSpeed * Time.deltaTime;
|
||
position.y += normalized.y * ai.moveSpeed * Time.deltaTime;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**适用场景:**
|
||
- 全局游戏逻辑系统
|
||
- 胜负判断系统
|
||
- UI更新系统
|
||
- 不依赖特定实体的处理
|
||
|
||
### 3. IntervalSystem - 间隔执行系统
|
||
|
||
不是每帧都执行,而是按指定间隔执行的系统,适合不需要高频更新的逻辑。
|
||
|
||
```typescript
|
||
import { IntervalSystem, Matcher } from '@esengine/ecs-framework';
|
||
|
||
class SpawnSystem extends IntervalSystem {
|
||
private spawnPoints: { x: number; y: number }[] = [
|
||
{ x: 100, y: 100 },
|
||
{ x: 700, y: 100 },
|
||
{ x: 400, y: 500 }
|
||
];
|
||
|
||
// 每2秒执行一次
|
||
constructor() {
|
||
// IntervalSystem需要指定间隔时间和Matcher
|
||
super(2.0, Matcher.all(SpawnerComponent));
|
||
}
|
||
|
||
// 间隔执行的逻辑(重写process方法)
|
||
protected process(entities: Entity[]) {
|
||
// entities就是匹配的生成器实体
|
||
|
||
for (const spawner of entities) {
|
||
const spawnerComp = spawner.getComponent(SpawnerComponent);
|
||
|
||
if (this.shouldSpawn(spawnerComp)) {
|
||
this.spawnEnemy(spawner, spawnerComp);
|
||
}
|
||
}
|
||
}
|
||
|
||
private shouldSpawn(spawner: SpawnerComponent): boolean {
|
||
// 检查是否应该生成
|
||
const currentEnemyCount = this.getCurrentEnemyCount();
|
||
return currentEnemyCount < spawner.maxEnemies &&
|
||
Math.random() < spawner.spawnChance;
|
||
}
|
||
|
||
private spawnEnemy(spawnerEntity: Entity, spawner: SpawnerComponent) {
|
||
// 随机选择生成点
|
||
const spawnPoint = this.spawnPoints[
|
||
Math.floor(Math.random() * this.spawnPoints.length)
|
||
];
|
||
|
||
// 创建敌人实体
|
||
const enemy = this.scene.createEntity("Enemy");
|
||
enemy.addComponent(new PositionComponent(spawnPoint.x, spawnPoint.y));
|
||
enemy.addComponent(new HealthComponent(50));
|
||
enemy.addComponent(new AIComponent());
|
||
enemy.addComponent(new VelocityComponent(0, 0));
|
||
enemy.tag = EntityTags.ENEMY;
|
||
|
||
// 更新生成器统计
|
||
spawner.spawnedCount++;
|
||
spawner.lastSpawnTime = Time.totalTime;
|
||
|
||
// 发送生成事件
|
||
const eventBus = this.scene.entityManager.eventBus;
|
||
eventBus.emit('enemy:spawned', {
|
||
enemyId: enemy.id,
|
||
spawnPoint: spawnPoint,
|
||
spawnerEntity: spawnerEntity.id
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
**适用场景:**
|
||
- 敌人生成系统
|
||
- 自动保存系统
|
||
- 资源回收系统
|
||
- 定期数据同步
|
||
|
||
### 4. PassiveSystem - 被动系统
|
||
|
||
不处理实体的系统,主要用于事件监听和响应。
|
||
|
||
```typescript
|
||
import { PassiveSystem, Matcher, Core } from '@esengine/ecs-framework';
|
||
|
||
class ScoreSystem extends PassiveSystem {
|
||
private score: number = 0;
|
||
private multiplier: number = 1;
|
||
private combo: number = 0;
|
||
|
||
constructor() {
|
||
// PassiveSystem也需要Matcher,即使不使用
|
||
super(Matcher.empty());
|
||
}
|
||
|
||
initialize() {
|
||
super.initialize();
|
||
|
||
// 监听游戏事件(使用EntityManager的事件系统)
|
||
const eventBus = this.scene.entityManager.eventBus;
|
||
eventBus.on('enemy:killed', this.onEnemyKilled, { context: this });
|
||
eventBus.on('item:collected', this.onItemCollected, { context: this });
|
||
eventBus.on('combo:broken', this.onComboBroken, { context: this });
|
||
}
|
||
|
||
// PassiveSystem被移除时清理
|
||
destroy() {
|
||
// 事件监听会在系统销毁时自动清理
|
||
// 如需手动清理,可以保存listenerId并调用eventBus.off()
|
||
}
|
||
|
||
private onEnemyKilled(data: { enemyType: string; position: { x: number; y: number } }) {
|
||
// 根据敌人类型给分
|
||
let baseScore = this.getScoreForEnemyType(data.enemyType);
|
||
|
||
// 连击奖励
|
||
this.combo++;
|
||
if (this.combo > 3) {
|
||
this.multiplier = Math.min(this.combo * 0.1, 3.0); // 最多3倍
|
||
}
|
||
|
||
const finalScore = Math.floor(baseScore * this.multiplier);
|
||
this.addScore(finalScore);
|
||
|
||
// 显示分数奖励
|
||
this.showScorePopup(data.position, finalScore);
|
||
}
|
||
|
||
private addScore(points: number) {
|
||
this.score += points;
|
||
|
||
// 发送分数更新事件
|
||
const eventBus = this.scene.entityManager.eventBus;
|
||
eventBus.emit('score:updated', {
|
||
score: this.score,
|
||
points: points,
|
||
multiplier: this.multiplier,
|
||
combo: this.combo
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
**适用场景:**
|
||
- 分数统计系统
|
||
- 音效播放系统
|
||
- UI更新系统
|
||
- 成就系统
|
||
|
||
## 系统生命周期方法
|
||
|
||
系统提供了多个生命周期方法,可以根据需要重写:
|
||
|
||
### 重要的生命周期方法
|
||
|
||
```typescript
|
||
class ExampleSystem extends EntitySystem {
|
||
/**
|
||
* 系统初始化回调 - 系统被添加到场景时调用
|
||
* 用于设置事件监听器、初始化资源等
|
||
* 注意:不要重写initialize()方法,而是重写onInitialize()
|
||
*/
|
||
protected onInitialize() {
|
||
// 设置事件监听
|
||
const eventBus = this.scene.entityManager.eventBus;
|
||
eventBus.on('someEvent', this.handleEvent, { context: this });
|
||
|
||
console.log('系统已初始化');
|
||
}
|
||
|
||
/**
|
||
* 每帧处理开始前调用
|
||
*/
|
||
protected onBegin() {
|
||
// 预处理逻辑,如重置计数器
|
||
this.frameCounter++;
|
||
}
|
||
|
||
/**
|
||
* 主要处理逻辑 - 每帧调用
|
||
* @param entities 符合条件的实体列表
|
||
*/
|
||
protected process(entities: Entity[]) {
|
||
for (const entity of entities) {
|
||
// 处理每个实体
|
||
this.processEntity(entity);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 后期处理 - 在process之后调用
|
||
* @param entities 符合条件的实体列表
|
||
*/
|
||
protected lateProcess(entities: Entity[]) {
|
||
// 后期处理逻辑,如碰撞检测后的响应
|
||
this.handlePostProcessing();
|
||
}
|
||
|
||
/**
|
||
* 每帧处理结束后调用
|
||
*/
|
||
protected onEnd() {
|
||
// 后处理逻辑,如统计数据更新
|
||
this.updateStatistics();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 生命周期执行顺序
|
||
|
||
系统的生命周期方法按以下顺序执行:
|
||
|
||
1. **initialize()** - 系统被添加到场景时执行一次(框架调用)
|
||
- **onInitialize()** - 用户可重写的初始化回调
|
||
2. 每帧循环:
|
||
- **onBegin()** - 帧开始前(用户可重写)
|
||
- **process(entities)** - 主要处理逻辑(用户必须实现)
|
||
- **lateProcess(entities)** - 后期处理(用户可重写)
|
||
- **onEnd()** - 帧结束后(用户可重写)
|
||
|
||
## 系统管理和注册
|
||
|
||
### 在场景中添加系统
|
||
|
||
```typescript
|
||
import { Scene, Core } from '@esengine/ecs-framework';
|
||
|
||
const scene = new Scene();
|
||
|
||
// 添加各种系统(使用addEntityProcessor方法)
|
||
scene.addEntityProcessor(new MovementSystem());
|
||
scene.addEntityProcessor(new GameLogicSystem());
|
||
scene.addEntityProcessor(new SpawnSystem());
|
||
scene.addEntityProcessor(new ScoreSystem());
|
||
|
||
// 设置系统的执行优先级
|
||
const movementSystem = scene.getEntityProcessor(MovementSystem);
|
||
if (movementSystem) {
|
||
movementSystem.updateOrder = 10; // 数值越小越先执行
|
||
}
|
||
|
||
const renderSystem = scene.getEntityProcessor(RenderSystem);
|
||
if (renderSystem) {
|
||
renderSystem.updateOrder = 100; // 渲染系统最后执行
|
||
}
|
||
|
||
// 设置为当前场景
|
||
Core.setScene(scene;
|
||
```
|
||
|
||
### 系统的启用和禁用
|
||
|
||
```typescript
|
||
// 暂时禁用某个系统
|
||
const gameLogicSystem = scene.getEntityProcessor(GameLogicSystem);
|
||
if (gameLogicSystem) {
|
||
gameLogicSystem.enabled = false;
|
||
}
|
||
|
||
// 重新启用
|
||
if (gameLogicSystem) {
|
||
gameLogicSystem.enabled = true;
|
||
}
|
||
|
||
// 移除系统
|
||
scene.removeEntityProcessor(gameLogicSystem);
|
||
```
|
||
|
||
## 系统设计最佳实践
|
||
|
||
### 1. 单一职责原则
|
||
|
||
```typescript
|
||
// 好的设计:每个系统只负责一件事
|
||
class MovementSystem extends EntitySystem {
|
||
// 只负责移动
|
||
}
|
||
|
||
class CollisionSystem extends EntitySystem {
|
||
// 只负责碰撞检测
|
||
}
|
||
|
||
class RenderSystem extends EntitySystem {
|
||
// 只负责渲染
|
||
}
|
||
|
||
// 不好的设计:一个系统做太多事情
|
||
class GameplaySystem extends EntitySystem {
|
||
// 既处理移动,又处理碰撞,还处理渲染...
|
||
}
|
||
```
|
||
|
||
### 2. 合理的系统执行顺序
|
||
|
||
```typescript
|
||
// 设置合理的执行顺序
|
||
scene.addEntityProcessor(new InputSystem()).updateOrder = 0; // 输入最先
|
||
scene.addEntityProcessor(new GameLogicSystem()).updateOrder = 10; // 游戏逻辑
|
||
scene.addEntityProcessor(new MovementSystem()).updateOrder = 20; // 移动计算
|
||
scene.addEntityProcessor(new CollisionSystem()).updateOrder = 30; // 碰撞检测
|
||
scene.addEntityProcessor(new HealthSystem()).updateOrder = 40; // 生命值处理
|
||
scene.addEntityProcessor(new RenderSystem()).updateOrder = 100; // 渲染最后
|
||
```
|
||
|
||
### 3. 系统间通信
|
||
|
||
```typescript
|
||
// 使用事件进行系统间通信
|
||
class CollisionSystem extends EntitySystem {
|
||
process(entities: Entity[]) {
|
||
// ... 碰撞检测逻辑
|
||
|
||
if (collision) {
|
||
// 发送碰撞事件,让其他系统响应
|
||
const eventBus = this.scene.entityManager.eventBus;
|
||
eventBus.emit('collision:detected', {
|
||
entity1: collider1,
|
||
entity2: collider2,
|
||
collisionPoint: point
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
class HealthSystem extends PassiveSystem {
|
||
initialize() {
|
||
super.initialize();
|
||
|
||
// 监听碰撞事件
|
||
const eventBus = this.scene.entityManager.eventBus;
|
||
eventBus.on('collision:detected', this.onCollision, { context: this });
|
||
}
|
||
|
||
private onCollision(data: CollisionEventData) {
|
||
// 处理碰撞伤害
|
||
if (data.entity1.hasComponent(HealthComponent)) {
|
||
const health = data.entity1.getComponent(HealthComponent);
|
||
health.takeDamage(10);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4. 性能优化
|
||
|
||
```typescript
|
||
class OptimizedMovementSystem extends EntitySystem {
|
||
private lastUpdateTime: number = 0;
|
||
private readonly UPDATE_INTERVAL = 16; // 60FPS
|
||
|
||
process(entities: Entity[]) {
|
||
const currentTime = Time.totalTime;
|
||
|
||
// 限制更新频率
|
||
if (currentTime - this.lastUpdateTime < this.UPDATE_INTERVAL) {
|
||
return;
|
||
}
|
||
|
||
// 批量处理
|
||
this.processBatch(entities);
|
||
|
||
this.lastUpdateTime = currentTime;
|
||
}
|
||
|
||
private processBatch(entities: Entity[]) {
|
||
// 使用for循环而不是forEach,性能更好
|
||
for (let i = 0; i < entities.length; i++) {
|
||
const entity = entities[i];
|
||
// 处理逻辑...
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 常见问题
|
||
|
||
### Q: 系统的执行顺序重要吗?
|
||
|
||
A: 非常重要!合理的执行顺序可以避免逻辑错误:
|
||
|
||
```typescript
|
||
// 正确顺序:
|
||
// 1. 输入系统(收集玩家输入)
|
||
// 2. AI系统(敌人决策)
|
||
// 3. 移动系统(更新位置)
|
||
// 4. 碰撞系统(检测碰撞)
|
||
// 5. 渲染系统(显示画面)
|
||
```
|
||
|
||
### Q: 什么时候使用哪种系统类型?
|
||
|
||
A:
|
||
- **EntitySystem** - 大部分游戏逻辑(移动、AI、碰撞等)
|
||
- **ProcessingSystem** - 不依赖特定实体的全局处理(游戏状态管理、全局逻辑)
|
||
- **IntervalSystem** - 不需要每帧执行的逻辑(生成器、自动保存)
|
||
- **PassiveSystem** - 事件响应系统(分数、音效、UI更新)
|
||
|
||
### Q: 系统可以访问其他系统吗?
|
||
|
||
A: 不建议直接访问。推荐使用事件系统进行系统间通信,保持松耦合。
|
||
|
||
### Q: 如何调试系统性能?
|
||
|
||
A: 使用框架内置的性能监控:
|
||
|
||
```typescript
|
||
const monitor = PerformanceMonitor.instance;
|
||
monitor.startFrame('MovementSystem');
|
||
// 系统逻辑...
|
||
monitor.endFrame('MovementSystem');
|
||
|
||
// 查看性能报告
|
||
console.log(monitor.getReport());
|
||
```
|
||
|
||
通过合理使用这些系统类型,你可以构建出高性能、易维护的游戏逻辑! |