Files
zeling_v2/Docs/DesignSpec/02_EventMessaging.md
2026-05-08 11:04:00 +08:00

174 lines
7.4 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.
# 02 · 事件与消息系统规范
> **所属文档集** [← 返回索引](./README.md)
> **摘要**:定义游戏全局的事件通信规范、事件目录与发布/订阅契约。
---
## 目录
1. [事件系统设计原则](#1-事件系统设计原则)
2. [事件类型体系](#2-事件类型体系)
3. [发布/订阅契约](#3-发布订阅契约)
4. [全局事件目录](#4-全局事件目录)
5. [事件设计指导](#5-事件设计指导)
---
## 1. 事件系统设计原则
| 原则 | 说明 |
|------|------|
| **最小载荷** | 事件只携带消费方需要的最少信息 |
| **类型安全** | 每个事件有明确的载荷类型,不使用无类型通用事件 |
| **无副作用传递** | 事件不携带可修改的对象引用,防止订阅方意外修改状态 |
| **单向通知** | 事件只通知"发生了什么",不携带"如何处理"的指令 |
| **有限生命周期** | 事件在发出后立即处理,不在系统间长期持有 |
---
## 2. 事件类型体系
### 2.1 按载荷分类
| 类型 | 描述 | 适用场景 |
|------|------|---------|
| `Event<void>` | 无载荷事件,纯通知 | 玩家死亡、游戏暂停 |
| `Event<T>` | 单值载荷 | HP 变化(传入新值)|
| `Event<Payload>` | 结构体载荷 | 伤害事件(多字段)|
### 2.2 按作用范围分类
| 类型 | 作用范围 | 说明 |
|------|---------|------|
| **全局事件** | 全游戏有效,跨场景 | 玩家死亡、游戏暂停、存档完成 |
| **局部事件** | 单场景/房间内有效 | 敌人死亡、机关触发、Boss 阶段变更 |
| **实体事件** | 单个实体范围 | 单个 NPC 对话开始、单个陷阱激活 |
---
## 3. 发布/订阅契约
### 3.1 发布方契约
发布方Emitter必须
- 在系统初始化时注册事件频道
- 仅在状态确实发生变化时发出事件(不重复发出相同状态)
- 在系统销毁时清理事件频道
### 3.2 订阅方契约
订阅方Listener必须
- 在系统初始化时注册订阅
- 在系统销毁时取消订阅(防止悬挂引用)
- 不在事件处理中再发出同类型事件(防止递归循环)
### 3.3 事件处理顺序
当多个系统订阅同一事件时,处理顺序**不保证**。
任何依赖特定顺序的逻辑应通过**新增中间事件**拆分为有序的因果链,而非依赖底层执行顺序。
```
❌ 错误:期望 A 先处理B 后处理同一事件
✅ 正确A 处理事件后发出新事件B 订阅新事件
```
---
## 4. 全局事件目录
### 4.1 玩家相关事件
| 事件名 | 载荷类型 | 发出方 | 典型订阅方 | 触发时机 |
|--------|---------|--------|----------|---------|
| `OnPlayerDied` | `void` | 玩家系统 | UI、音频、存档、世界 | 玩家 HP 归零 |
| `OnPlayerRevived` | `void` | 游戏管理器 | 玩家系统、UI、音频 | 复活流程完成 |
| `OnHPChanged` | `{current: Integer, max: Integer}` | 玩家系统 | UIHUD| HP 任意变化 |
| `OnSoulPowerChanged` | `{current: Integer, max: Integer}` | 玩家系统 | UIHUD| 灵力变化 |
| `OnSpiritPowerChanged` | `{current: Integer, max: Integer}` | 玩家系统 | UIHUD| 魄元变化 |
| `OnSpringChargesChanged` | `{current: Integer, max: Integer}` | 玩家系统 | UIHUD| 灵泉次数变化 |
| `OnGeoChanged` | `{current: Integer, delta: Integer}` | 经济系统 | UIHUD| Geo 增减 |
| `OnAbilityUnlocked` | `{abilityId: ID}` | 进程系统 | 玩家系统、UI | 新能力获得 |
| `OnFormChanged` | `{newFormId: ID}` | 形态系统 | UI、音频、武器系统 | 玩家切换形态 |
| `OnParrySuccess` | `{counterWindowDuration: Duration}` | 战斗系统 | 玩家系统、UI、音频 | 弹反判定成功 |
| `OnHitConfirmed` | `HitPayload` | 战斗系统 | 玩家系统(灵力积累)、反馈系统 | 攻击命中判定 |
### 4.2 敌人相关事件
| 事件名 | 载荷类型 | 发出方 | 典型订阅方 | 触发时机 |
|--------|---------|--------|----------|---------|
| `OnEnemyDied` | `{enemyId: ID, position: Vector2}` | 敌人系统 | 进程系统、经济系统、叙事系统 | 敌人 HP 归零 |
| `OnEnemyHit` | `HitPayload` | 战斗系统 | 反馈系统、音频 | 攻击命中敌人 |
| `OnBossPhaseChanged` | `{bossId: ID, phase: Integer}` | Boss 系统 | UI、音频 | Boss 进入新阶段 |
| `OnBossDefeated` | `{bossId: ID}` | 敌人系统 | 进程系统、叙事系统、存档 | Boss 战胜利 |
### 4.3 世界/进程相关事件
| 事件名 | 载荷类型 | 发出方 | 典型订阅方 | 触发时机 |
|--------|---------|--------|----------|---------|
| `OnSceneTransitionBegin` | `{targetSceneId: ID}` | 世界系统 | UI遮罩、输入系统 | 开始场景切换 |
| `OnSceneTransitionEnd` | `{sceneId: ID}` | 世界系统 | 输入系统、音频 | 场景切换完成 |
| `OnSavePointActivated` | `{savePointId: ID, position: Vector2}` | 世界系统 | 存档系统、玩家系统、UI | 玩家激活存档点 |
| `OnRoomDiscovered` | `{roomId: ID}` | 世界系统 | 地图系统 | 玩家首次进入房间 |
| `OnCollectiblePickedUp` | `{collectibleId: ID, type: CollectibleType}` | 世界系统 | 进程系统、叙事系统 | 玩家拾取收集品 |
| `OnWorldFlagChanged` | `{flagId: ID, value: Boolean}` | 叙事系统 | 叙事系统(自引用)、进程系统 | 世界状态标志变化 |
### 4.4 UI/系统相关事件
| 事件名 | 载荷类型 | 发出方 | 典型订阅方 | 触发时机 |
|--------|---------|--------|----------|---------|
| `OnGamePaused` | `void` | 游戏管理器 | 所有系统 | 暂停键触发 |
| `OnGameResumed` | `void` | 游戏管理器 | 所有系统 | 恢复键触发 |
| `OnSaveCompleted` | `{slotId: Integer}` | 存档系统 | UI提示| 存档写入完成 |
| `OnSettingsChanged` | `SettingsPayload` | 设置系统 | 音频、无障碍、UI 等 | 设置项变更 |
| `OnAchievementUnlocked` | `{achievementId: ID}` | 成就系统 | UIToast| 成就达成 |
---
## 5. 事件设计指导
### 5.1 新增事件时的决策流程
```
需要系统 A 通知系统 B 时:
B 是否需要立即同步返回值?
├─ 是 → 使用接口调用(不是事件)
└─ 否 → 使用事件
事件是否跨多个系统关心?
├─ 是(多个系统订阅)→ 定义为全局事件,加入全局目录
└─ 否(仅 A→B→ 考虑是否用接口更合适
事件是否携带可变对象引用?
├─ 是 → 改为传递值类型载荷ID + 必要字段)
└─ 否 → 可以使用
```
### 5.2 载荷设计规范
**好的载荷(值类型,信息完整)**
```
OnEnemyDied {
enemyId : ID // 唯一标识,订阅方可用于查询更多信息
position : Vector2 // 死亡位置(生成掉落物需要)
lootTableId : ID // 掉落表 ID经济系统直接使用
}
```
**不好的载荷(引用传递,导致耦合)**
```
OnEnemyDied {
enemyRef : EnemyObject // ❌ 引用具体对象,订阅方与敌人系统耦合
}
```
### 5.3 事件命名规范
| 规范 | 说明 | 示例 |
|------|------|------|
| 动词过去式On + 名词 + 动词过去式)| 表示"已发生的事" | `OnPlayerDied``OnAbilityUnlocked` |
| 变化类用 Changed | 状态数值变化 | `OnHPChanged``OnGeoChanged` |
| 开始/结束对 | 持续性事件标注阶段 | `OnSceneTransitionBegin` / `OnSceneTransitionEnd` |
| 避免"Will/Should" | 事件是通知,不是征询许可 | ❌ `OnPlayerWillDie`,✅ `OnPlayerDied` |