Files
pixelheros/.qoder/repowiki/zh/content/核心系统/事件系统.md
panw 4235e3b776 refactor(game): 移除已弃用的事件常量
- 删除 UpdateHero 和 UpdateFightHero 事件
- 移除 MISSION_UPDATE 事件常量
- 优化游戏事件枚举定义
2025-10-28 16:15:47 +08:00

9.7 KiB
Raw Blame History

事件系统

**本文档引用文件** - [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:英雄详情界面打开
  • GuideStartGuideEnd:新手引导流程控制

地图移动事件

  • MAP_MOVE_END_LEFT:地图向左移动到达边界
  • MAP_MOVE_END_RIGHT:地图向右移动到达边界
  • CardsClose:卡牌选择界面关闭

卡牌与技能事件

  • CardRefresh:卡牌刷新时触发
  • UseHeroCard:使用英雄卡牌
  • UseSkillCard:使用技能卡牌
  • UseTalentCard:使用天赋卡牌
  • CastSkill:技能施放

Section sources

事件监听机制与内存管理

持续监听与单次监听的差异

oops.message.on() 用于注册持续监听的事件,监听器会一直存在直到显式移除。适用于需要长期响应的事件,如资源更新、状态变化等。

oops.message.on(GameEvent.GOLD_UPDATE, this.onGoldUpdate, this);

oops.message.once() 用于注册只触发一次的事件监听,事件响应后监听器自动移除。适用于一次性流程,如初始化完成、首次加载等场景。

oops.message.once(GameEvent.GameServerConnected, this.onHandler, this);

MessageManager.ts 的实现可以看出,once() 方法通过创建一个包装函数 _listener,在事件触发后立即调用 off() 移除自身,确保只执行一次。

内存泄漏防范措施

为防止内存泄漏,必须在对象销毁时移除所有事件监听。通常在 onDestroy()reset() 生命周期方法中执行:

protected onDestroy() {
    oops.message.off(GameEvent.GOLD_UPDATE, this.onGoldUpdate, this);
}

MessageManager 在注册事件时会检查重复注册,并发出警告,避免同一对象对同一事件的重复监听。

Section sources

事件系统解耦设计与应用示例

解耦模块间通信

事件系统通过发布-订阅模式实现模块间的松耦合通信。发送方无需知道接收方的存在,接收方也无需主动轮询状态变化。

英雄升级事件HeroLvUp的完整流程

  1. 事件触发:当英雄经验值满足升级条件时,触发 HeroLvUp 事件
  2. UI更新UI组件监听 HeroLvUp 事件,更新英雄等级显示和属性面板
  3. 成就判断:成就系统监听该事件,判断是否达成"快速升级"等成就条件
  4. 天赋系统响应:某些天赋(如"每升5级攻击力+10%")在等级变化时触发效果

金币更新事件的实际应用

SingletonModuleComp.ts 中,当金币数量变化时触发 GOLD_UPDATE 事件:

updateGold(gold: number) {
    this.vmdata.gold += gold;
    // ... 更新云端数据
    oops.message.dispatchEvent(GameEvent.GOLD_UPDATE);
}

TopComp.ts顶部UI组件监听该事件并执行视觉反馈

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_LEFTMAP_MOVE_END_RIGHT 事件的闭环设计:

  1. 地图移动组件监听边界到达事件
  2. 当到达边界时,重置位置并派发对应的边界事件
  3. 其他组件可以监听这些事件执行相应逻辑
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

Section sources

事件命名规范与作用域管理

命名规范

事件名称采用大写常量格式,使用有意义的描述性名称。遵循以下原则:

  • 使用名词或名词短语,如 GOLD_UPDATEHeroLvUp
  • 模块相关事件添加前缀,如 MAP_MOVE_END_LEFT
  • 业务流程事件使用动词,如 MissionWinFightStart
  • 避免缩写,确保名称清晰可读

作用域管理

事件系统通过第三个参数 this 管理作用域,确保回调函数在正确的上下文中执行。这解决了 JavaScript/TypeScript 中常见的 this 上下文丢失问题。

在注册事件时,必须传入正确的对象引用,以便在移除监听时能够精确匹配。

oops.message.on(GameEvent.GOLD_UPDATE, this.onGoldUpdate, this);

这里的 this 确保了:

  1. 回调函数在正确的对象实例上下文中执行
  2. 移除监听时能够准确找到对应的监听器
  3. 避免不同实例间的监听器混淆

Section sources

事件调试与常见问题解决方案

调试技巧

事件监听器dump

可以通过访问 MessageManager 的内部 events 对象来查看当前所有注册的事件监听器,便于调试和性能分析。

事件触发跟踪

在开发环境中,可以临时修改 dispatchEvent 方法,添加日志输出,跟踪所有事件的触发情况。

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