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

262 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 事件系统
<cite>
**本文档引用文件**
- [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)
</cite>
## 目录
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)