262 lines
9.7 KiB
Markdown
262 lines
9.7 KiB
Markdown
# 事件系统
|
||
|
||
<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) |