# 事件系统 **本文档引用文件** - [GameEvent.ts](file://assets/script/game/common/config/GameEvent.ts) - [event.md](file://doc/core/common/event.md) - [MessageManager.ts](file://extensions/oops-plugin-framework/assets/core/common/event/MessageManager.ts) - [SingletonModuleComp.ts](file://assets/script/game/common/SingletonModuleComp.ts) - [TopComp.ts](file://assets/script/game/map/TopComp.ts) - [MInfoComp.ts](file://assets/script/game/map/MInfoComp.ts) - [move.ts](file://assets/script/game/map/move.ts) - [TalComp.ts](file://assets/script/game/hero/TalComp.ts) ## 目录 1. [事件常量分类与业务含义](#事件常量分类与业务含义) 2. [事件监听机制与内存管理](#事件监听机制与内存管理) 3. [事件系统解耦设计与应用示例](#事件系统解耦设计与应用示例) 4. [事件命名规范与作用域管理](#事件命名规范与作用域管理) 5. [事件调试与常见问题解决方案](#事件调试与常见问题解决方案) ## 事件常量分类与业务含义 基于 `GameEvent.ts` 枚举文件,游戏全局事件按功能模块可分为以下几类: ### 战斗流程事件 - `MissionStart`:关卡开始时触发,初始化战斗环境 - `MissionWin`:玩家胜利时触发,进入结算流程 - `MissionLoss`:玩家失败时触发,显示失败界面 - `FightStart`:战斗正式开始,激活战斗逻辑 - `FightEnd`:战斗结束,无论胜负均触发 - `NewWave`:新一波敌人出现,用于刷新怪物生成 ### 英雄相关事件 - `HeroLvUp`:英雄升级时触发,用于属性更新和UI反馈 - `HeroUnlock`:新英雄解锁时触发,通知UI展示获取动画 - `HeroDead`:英雄死亡时触发,处理死亡逻辑和成就判断 - `HeroSelect`:选择出战英雄时触发,更新队伍配置 - `HeroSkillSelect`:技能选择阶段触发,激活技能选择界面 ### 资源更新事件 - `GOLD_UPDATE`:金币数量变化时触发,用于UI金币显示更新 - `DIAMOND_UPDATE`:钻石数量变化时触发 - `MEAT_UPDATE`:游戏内特定资源"肉"的数量更新 - `MISSION_UPDATE`:关卡进度更新,用于顶部UI显示当前关卡 ### 界面与交互事件 - `ShopOpen`:商店界面打开时触发 - `HerosOpen`:英雄列表界面打开 - `RestOpen`:休息界面打开 - `HeroInfoOpen`:英雄详情界面打开 - `GuideStart`、`GuideEnd`:新手引导流程控制 ### 地图移动事件 - `MAP_MOVE_END_LEFT`:地图向左移动到达边界 - `MAP_MOVE_END_RIGHT`:地图向右移动到达边界 - `CardsClose`:卡牌选择界面关闭 ### 卡牌与技能事件 - `CardRefresh`:卡牌刷新时触发 - `UseHeroCard`:使用英雄卡牌 - `UseSkillCard`:使用技能卡牌 - `UseTalentCard`:使用天赋卡牌 - `CastSkill`:技能施放 **Section sources** - [GameEvent.ts](file://assets/script/game/common/config/GameEvent.ts) ## 事件监听机制与内存管理 ### 持续监听与单次监听的差异 `oops.message.on()` 用于注册持续监听的事件,监听器会一直存在直到显式移除。适用于需要长期响应的事件,如资源更新、状态变化等。 ```typescript oops.message.on(GameEvent.GOLD_UPDATE, this.onGoldUpdate, this); ``` `oops.message.once()` 用于注册只触发一次的事件监听,事件响应后监听器自动移除。适用于一次性流程,如初始化完成、首次加载等场景。 ```typescript oops.message.once(GameEvent.GameServerConnected, this.onHandler, this); ``` 从 `MessageManager.ts` 的实现可以看出,`once()` 方法通过创建一个包装函数 `_listener`,在事件触发后立即调用 `off()` 移除自身,确保只执行一次。 ### 内存泄漏防范措施 为防止内存泄漏,必须在对象销毁时移除所有事件监听。通常在 `onDestroy()` 或 `reset()` 生命周期方法中执行: ```typescript protected onDestroy() { oops.message.off(GameEvent.GOLD_UPDATE, this.onGoldUpdate, this); } ``` `MessageManager` 在注册事件时会检查重复注册,并发出警告,避免同一对象对同一事件的重复监听。 **Section sources** - [event.md](file://doc/core/common/event.md) - [MessageManager.ts](file://extensions/oops-plugin-framework/assets/core/common/event/MessageManager.ts) ## 事件系统解耦设计与应用示例 ### 解耦模块间通信 事件系统通过发布-订阅模式实现模块间的松耦合通信。发送方无需知道接收方的存在,接收方也无需主动轮询状态变化。 #### 英雄升级事件(HeroLvUp)的完整流程 1. **事件触发**:当英雄经验值满足升级条件时,触发 `HeroLvUp` 事件 2. **UI更新**:UI组件监听 `HeroLvUp` 事件,更新英雄等级显示和属性面板 3. **成就判断**:成就系统监听该事件,判断是否达成"快速升级"等成就条件 4. **天赋系统响应**:某些天赋(如"每升5级攻击力+10%")在等级变化时触发效果 #### 金币更新事件的实际应用 在 `SingletonModuleComp.ts` 中,当金币数量变化时触发 `GOLD_UPDATE` 事件: ```typescript updateGold(gold: number) { this.vmdata.gold += gold; // ... 更新云端数据 oops.message.dispatchEvent(GameEvent.GOLD_UPDATE); } ``` 在 `TopComp.ts` 中,顶部UI组件监听该事件并执行视觉反馈: ```typescript onGoldUpdate(event: string, data: any) { tween(this.node.getChildByName("bar").getChildByName("gold").getChildByName("num").getComponent(Label).node) .to(0.1, { scale: v3(1.2, 1.2, 1) }) .to(0.1, { scale: v3(1, 1, 1) }) .start(); } ``` 这种设计使得金币逻辑与UI展示完全分离,任何模块都可以独立修改而不影响其他部分。 ### 地图移动事件的循环机制 `move.ts` 文件展示了 `MAP_MOVE_END_LEFT` 和 `MAP_MOVE_END_RIGHT` 事件的闭环设计: 1. 地图移动组件监听边界到达事件 2. 当到达边界时,重置位置并派发对应的边界事件 3. 其他组件可以监听这些事件执行相应逻辑 ```mermaid flowchart TD A[地图移动] --> B{到达右边界?} B --> |是| C[重置到左边界] C --> D[派发MAP_MOVE_END_LEFT] D --> E[其他组件响应] B --> |否| F{到达左边界?} F --> |是| G[重置到右边界] G --> H[派发MAP_MOVE_END_RIGHT] H --> I[其他组件响应] ``` **Diagram sources** - [SingletonModuleComp.ts](file://assets/script/game/common/SingletonModuleComp.ts) - [TopComp.ts](file://assets/script/game/map/TopComp.ts) - [move.ts](file://assets/script/game/map/move.ts) **Section sources** - [SingletonModuleComp.ts](file://assets/script/game/common/SingletonModuleComp.ts) - [TopComp.ts](file://assets/script/game/map/TopComp.ts) - [MInfoComp.ts](file://assets/script/game/map/MInfoComp.ts) - [move.ts](file://assets/script/game/map/move.ts) ## 事件命名规范与作用域管理 ### 命名规范 事件名称采用大写常量格式,使用有意义的描述性名称。遵循以下原则: - 使用名词或名词短语,如 `GOLD_UPDATE`、`HeroLvUp` - 模块相关事件添加前缀,如 `MAP_MOVE_END_LEFT` - 业务流程事件使用动词,如 `MissionWin`、`FightStart` - 避免缩写,确保名称清晰可读 ### 作用域管理 事件系统通过第三个参数 `this` 管理作用域,确保回调函数在正确的上下文中执行。这解决了 JavaScript/TypeScript 中常见的 `this` 上下文丢失问题。 在注册事件时,必须传入正确的对象引用,以便在移除监听时能够精确匹配。 ```typescript oops.message.on(GameEvent.GOLD_UPDATE, this.onGoldUpdate, this); ``` 这里的 `this` 确保了: 1. 回调函数在正确的对象实例上下文中执行 2. 移除监听时能够准确找到对应的监听器 3. 避免不同实例间的监听器混淆 **Section sources** - [MessageManager.ts](file://extensions/oops-plugin-framework/assets/core/common/event/MessageManager.ts) ## 事件调试与常见问题解决方案 ### 调试技巧 #### 事件监听器dump 可以通过访问 `MessageManager` 的内部 `events` 对象来查看当前所有注册的事件监听器,便于调试和性能分析。 #### 事件触发跟踪 在开发环境中,可以临时修改 `dispatchEvent` 方法,添加日志输出,跟踪所有事件的触发情况。 ```mermaid flowchart LR A[事件注册] --> B[事件派发] B --> C{是否有监听器?} C --> |是| D[执行所有监听器] D --> E[回调函数执行] C --> |否| F[无操作] ``` ### 常见问题及解决方案 #### 事件重复注册 **问题**:同一对象对同一事件多次注册,导致回调执行多次。 **解决方案**: 1. 在 `onLoad()` 中注册事件,在 `onDestroy()` 中移除 2. 使用 `MessageManager` 的重复注册警告功能及时发现 3. 在复杂场景下,使用标志位确保只注册一次 #### this上下文丢失 **问题**:回调函数中的 `this` 指向错误的对象。 **解决方案**: 1. 严格按照 `oops.message.on(event, handler, this)` 格式注册 2. 避免使用箭头函数作为回调,除非明确不需要绑定作用域 3. 在 TypeScript 中利用类型检查确保第三个参数正确 #### 内存泄漏 **问题**:对象销毁后事件监听器未移除,导致对象无法被垃圾回收。 **解决方案**: 1. 严格遵守生命周期,在 `onDestroy()` 中调用 `off()` 2. 对于临时组件,使用 `once()` 替代 `on()` 3. 使用事件管理器批量管理事件的注册和移除 #### 事件命名冲突 **问题**:不同模块使用相同名称的事件导致意外行为。 **解决方案**: 1. 使用模块前缀,如 `MAP_`、`HERO_` 2. 在 `GameEvent` 枚举中统一管理所有事件名称 3. 避免使用过于通用的名称 **Section sources** - [MessageManager.ts](file://extensions/oops-plugin-framework/assets/core/common/event/MessageManager.ts) - [TalComp.ts](file://assets/script/game/hero/TalComp.ts)