事件系统
ECS 框架内置了强大的类型安全事件系统,支持同步/异步事件、优先级、批处理等高级功能。事件系统是实现组件间通信、系统间协作的核心机制。
事件系统提供了发布-订阅模式的实现,包含以下核心概念:
- 事件发布者:发射事件的对象
- 事件监听者:监听并处理特定事件的对象
- 事件类型:字符串标识,用于区分不同类型的事件
- 事件数据:事件携带的相关信息
事件系统架构
Section titled “事件系统架构”框架提供了两层事件系统:
- TypeSafeEventSystem - 底层高性能事件系统
- EventBus - 上层增强事件总线,提供更多便利功能
在场景中使用事件系统
Section titled “在场景中使用事件系统”每个场景都有内置的事件系统:
class GameScene extends Scene { protected initialize(): void { // 监听事件 this.eventSystem.on('player_died', this.onPlayerDied.bind(this)); this.eventSystem.on('enemy_spawned', this.onEnemySpawned.bind(this)); this.eventSystem.on('score_changed', this.onScoreChanged.bind(this)); }
private onPlayerDied(data: { player: Entity, cause: string }): void { console.log(`玩家死亡,原因: ${data.cause}`); // 处理玩家死亡逻辑 }
private onEnemySpawned(data: { enemy: Entity, position: { x: number, y: number } }): void { console.log('敌人生成于:', data.position); // 处理敌人生成逻辑 }
private onScoreChanged(data: { newScore: number, oldScore: number }): void { console.log(`分数变化: ${data.oldScore} -> ${data.newScore}`); // 更新UI显示 }
// 在系统中发射事件 someGameLogic(): void { // 发射同步事件 this.eventSystem.emitSync('score_changed', { newScore: 1000, oldScore: 800 }); }}在系统中使用事件
Section titled “在系统中使用事件”系统可以方便地监听和发送事件:
@ECSSystem('Combat')class CombatSystem extends EntitySystem { constructor() { super(Matcher.all(Health, Combat)); }
protected onInitialize(): void { // 使用系统提供的事件监听方法(自动清理) this.addEventListener('player_attack', this.onPlayerAttack.bind(this)); this.addEventListener('enemy_death', this.onEnemyDeath.bind(this)); }
private onPlayerAttack(data: { damage: number, target: Entity }): void { // 处理玩家攻击事件 const health = data.target.getComponent(Health); if (health) { health.current -= data.damage;
if (health.current <= 0) { // 发送敌人死亡事件 this.scene?.eventSystem.emitSync('enemy_death', { enemy: data.target, killer: 'player' }); } } }
private onEnemyDeath(data: { enemy: Entity, killer: string }): void { // 处理敌人死亡 data.enemy.destroy();
// 发送经验奖励事件 this.scene?.eventSystem.emitSync('experience_gained', { amount: 100, source: 'enemy_kill' }); }
protected process(entities: readonly Entity[]): void { for (const entity of entities) { const combat = entity.getComponent(Combat); if (combat && combat.shouldAttack()) { // 发射攻击事件 this.scene?.eventSystem.emitSync('player_attack', { damage: combat.damage, target: combat.target }); } } }}一次性监听器
Section titled “一次性监听器”class GameScene extends Scene { protected initialize(): void { // 只监听一次的事件 this.eventSystem.once('game_start', this.onGameStart.bind(this));
// 或者使用配置对象 this.eventSystem.on('level_complete', this.onLevelComplete.bind(this), { once: true // 只执行一次 }); }
private onGameStart(): void { console.log('游戏开始!'); // 这个方法只会被调用一次 }
private onLevelComplete(): void { console.log('关卡完成!'); // 这个方法也只会被调用一次 }}class GameScene extends Scene { protected initialize(): void { // 高优先级监听器先执行 this.eventSystem.on('damage_dealt', this.onDamageDealt.bind(this), { priority: 100 // 高优先级 });
// 普通优先级 this.eventSystem.on('damage_dealt', this.updateUI.bind(this), { priority: 0 // 默认优先级 });
// 低优先级最后执行 this.eventSystem.on('damage_dealt', this.logDamage.bind(this), { priority: -100 // 低优先级 }); }
private onDamageDealt(data: any): void { // 最先执行 - 处理核心游戏逻辑 }
private updateUI(data: any): void { // 中等优先级 - 更新界面 }
private logDamage(data: any): void { // 最后执行 - 记录日志 }}异步事件处理
Section titled “异步事件处理”class GameScene extends Scene { protected initialize(): void { // 监听异步事件 this.eventSystem.onAsync('save_game', this.onSaveGame.bind(this)); this.eventSystem.onAsync('load_data', this.onLoadData.bind(this)); }
private async onSaveGame(data: { saveSlot: number }): Promise<void> { console.log(`开始保存游戏到槽位 ${data.saveSlot}`);
// 模拟异步保存操作 await this.saveGameData(data.saveSlot);
console.log('游戏保存完成'); }
private async onLoadData(data: { url: string }): Promise<void> { try { const response = await fetch(data.url); const gameData = await response.json(); // 处理加载的数据 } catch (error) { console.error('数据加载失败:', error); } }
private async saveGameData(slot: number): Promise<void> { // 模拟保存操作 return new Promise(resolve => setTimeout(resolve, 1000)); }
// 发射异步事件 public async triggerSave(): Promise<void> { // 使用 emit 而不是 emitSync 来触发异步监听器 await this.eventSystem.emit('save_game', { saveSlot: 1 }); console.log('所有异步保存操作完成'); }}事件统计和调试
Section titled “事件统计和调试”class GameScene extends Scene { protected initialize(): void { this.eventSystem.on('debug_event', this.onDebugEvent.bind(this)); }
private onDebugEvent(): void { // 处理调试事件 }
public showEventStats(): void { // 获取特定事件的统计信息 const stats = this.eventSystem.getStats('debug_event') as any; if (stats) { console.log('事件统计:'); console.log(`- 事件类型: ${stats.eventType}`); console.log(`- 监听器数量: ${stats.listenerCount}`); console.log(`- 触发次数: ${stats.triggerCount}`); console.log(`- 平均执行时间: ${stats.averageExecutionTime.toFixed(2)}ms`); }
// 获取所有事件统计 const allStats = this.eventSystem.getStats() as Map<string, any>; console.log(`总共有 ${allStats.size} 种事件类型`); }
public resetEventStats(): void { // 重置特定事件的统计 this.eventSystem.resetStats('debug_event');
// 或重置所有统计 this.eventSystem.resetStats(); }}全局事件总线
Section titled “全局事件总线”对于跨场景的事件通信,可以使用全局事件总线:
import { GlobalEventBus } from '@esengine/ecs-framework';
class GameManager { private eventBus = GlobalEventBus.getInstance();
constructor() { this.setupGlobalEvents(); }
private setupGlobalEvents(): void { // 监听全局事件 this.eventBus.on('player_level_up', this.onPlayerLevelUp.bind(this)); this.eventBus.on('achievement_unlocked', this.onAchievementUnlocked.bind(this)); this.eventBus.onAsync('upload_score', this.onUploadScore.bind(this)); }
private onPlayerLevelUp(data: { level: number, experience: number }): void { console.log(`玩家升级到 ${data.level} 级!`); // 处理全局升级逻辑 }
private onAchievementUnlocked(data: { achievementId: string, name: string }): void { console.log(`解锁成就: ${data.name}`); // 显示成就通知 }
private async onUploadScore(data: { score: number, playerName: string }): Promise<void> { // 异步上传分数到服务器 try { await this.uploadToServer(data); console.log('分数上传成功'); } catch (error) { console.error('分数上传失败:', error); } }
public triggerGlobalEvent(): void { // 发射全局事件 this.eventBus.emit('player_level_up', { level: 10, experience: 2500 }); }
private async uploadToServer(data: any): Promise<void> { // 模拟服务器上传 return new Promise(resolve => setTimeout(resolve, 2000)); }}对于高频事件,可以使用批处理来提升性能:
class MovementSystem extends EntitySystem { protected onInitialize(): void { // 设置位置更新事件的批处理 this.scene?.eventSystem.setBatchConfig('position_updated', { batchSize: 50, // 批处理大小 delay: 16, // 延迟时间(毫秒) enabled: true });
// 监听批处理事件 this.addEventListener('position_updated:batch', this.onPositionBatch.bind(this)); }
private onPositionBatch(batchData: any): void { console.log(`批处理位置更新,共 ${batchData.count} 个事件`);
// 批量处理所有位置更新 for (const event of batchData.events) { this.updateMinimap(event.entityId, event.position); } }
protected process(entities: readonly Entity[]): void { for (const entity of entities) { const position = entity.getComponent(Position); if (position && position.hasChanged) { // 发射高频位置更新事件(会被批处理) this.scene?.eventSystem.emitSync('position_updated', { entityId: entity.id, position: { x: position.x, y: position.y } }); } } }
private updateMinimap(entityId: number, position: { x: number, y: number }): void { // 更新小地图显示 }
public flushPositionUpdates(): void { // 立即处理所有待处理的位置更新 this.scene?.eventSystem.flushBatch('position_updated'); }}预定义的 ECS 事件
Section titled “预定义的 ECS 事件”框架提供了一些预定义的 ECS 生命周期事件:
import { ECSEventType } from '@esengine/ecs-framework';
class ECSMonitor { private eventBus = GlobalEventBus.getInstance();
constructor() { this.setupECSEvents(); }
private setupECSEvents(): void { // 监听实体生命周期事件 this.eventBus.onEntityCreated(this.onEntityCreated.bind(this)); this.eventBus.on(ECSEventType.ENTITY_DESTROYED, this.onEntityDestroyed.bind(this));
// 监听组件生命周期事件 this.eventBus.onComponentAdded(this.onComponentAdded.bind(this)); this.eventBus.on(ECSEventType.COMPONENT_REMOVED, this.onComponentRemoved.bind(this));
// 监听系统事件 this.eventBus.on(ECSEventType.SYSTEM_ADDED, this.onSystemAdded.bind(this)); this.eventBus.onSystemError(this.onSystemError.bind(this));
// 监听性能警告 this.eventBus.onPerformanceWarning(this.onPerformanceWarning.bind(this)); }
private onEntityCreated(data: any): void { console.log(`实体创建: ${data.entityName} (ID: ${data.entity.id})`); }
private onEntityDestroyed(data: any): void { console.log(`实体销毁: ${data.entity.name} (ID: ${data.entity.id})`); }
private onComponentAdded(data: any): void { console.log(`组件添加: ${data.componentType} 到实体 ${data.entity.name}`); }
private onComponentRemoved(data: any): void { console.log(`组件移除: ${data.componentType} 从实体 ${data.entity.name}`); }
private onSystemAdded(data: any): void { console.log(`系统添加: ${data.systemName}`); }
private onSystemError(data: any): void { console.error(`系统错误: ${data.systemName}`, data.error); }
private onPerformanceWarning(data: any): void { console.warn(`性能警告: ${data.systemName} 执行时间过长 (${data.executionTime}ms)`); }}1. 事件命名规范
Section titled “1. 事件命名规范”// ✅ 好的事件命名this.eventSystem.emitSync('player:health_changed', data);this.eventSystem.emitSync('enemy:spawned', data);this.eventSystem.emitSync('ui:score_updated', data);this.eventSystem.emitSync('game:paused', data);
// ❌ 避免的事件命名this.eventSystem.emitSync('event1', data);this.eventSystem.emitSync('update', data);this.eventSystem.emitSync('change', data);2. 类型安全的事件数据
Section titled “2. 类型安全的事件数据”// 定义事件数据接口interface PlayerHealthChangedEvent { entityId: number; oldHealth: number; newHealth: number; cause: 'damage' | 'healing';}
interface EnemySpawnedEvent { enemyType: string; position: { x: number, y: number }; level: number;}
// 使用类型安全的事件class HealthSystem extends EntitySystem { private onHealthChanged(data: PlayerHealthChangedEvent): void { // TypeScript 会提供完整的类型检查 console.log(`生命值变化: ${data.oldHealth} -> ${data.newHealth}`); }}3. 避免事件循环
Section titled “3. 避免事件循环”// ❌ 避免:可能导致无限循环class BadEventHandler { private onScoreChanged(data: any): void { // 在处理分数变化时又触发分数变化事件 this.scene?.eventSystem.emitSync('score_changed', newData); // 危险! }}
// ✅ 正确:使用不同的事件类型或条件判断class GoodEventHandler { private isProcessingScore = false;
private onScoreChanged(data: any): void { if (this.isProcessingScore) return; // 防止循环
this.isProcessingScore = true; // 处理分数变化 this.updateUI(data); this.isProcessingScore = false; }}4. 及时清理事件监听器
Section titled “4. 及时清理事件监听器”class TemporaryUI { private listenerId: string;
constructor(scene: Scene) { // 保存监听器ID用于后续清理 this.listenerId = scene.eventSystem.on('ui_update', this.onUpdate.bind(this)); }
private onUpdate(data: any): void { // 处理UI更新 }
public destroy(): void { // 清理事件监听器 if (this.listenerId) { scene.eventSystem.off('ui_update', this.listenerId); } }}5. 性能考虑
Section titled “5. 性能考虑”class OptimizedEventHandler { protected onInitialize(): void { // 对于高频事件,使用批处理 this.scene?.eventSystem.setBatchConfig('movement_update', { batchSize: 100, delay: 16, enabled: true });
// 对于低频但重要的事件,使用高优先级 this.addEventListener('game_over', this.onGameOver.bind(this), { priority: 1000 });
// 对于一次性事件,使用 once this.addEventListener('level_start', this.onLevelStart.bind(this), { once: true }); }}事件系统是 ECS 框架中实现松耦合架构的重要工具,正确使用事件系统能让你的游戏代码更加模块化、可维护和可扩展。