201 lines
15 KiB
Markdown
201 lines
15 KiB
Markdown
# 解耦架构重构 — Review 清单
|
||
|
||
> 范围:P0 地基 + P1 事件去静态化 + P2a/P2b 去反射/Find + P5(Shader.Find 部分)
|
||
> 状态:代码已全部落地(静态校验通过),**尚未在 Unity 内编译/接线/回归**。
|
||
> 用法:逐项勾选。每条都标注了「文件 / 风险等级 / 需核对的点」。
|
||
|
||
---
|
||
|
||
## 0. 改动总览
|
||
|
||
| 类别 | 文件数 | 说明 |
|
||
|------|--------|------|
|
||
| 新增架构框架 | 7 | `Assets/Architecture/` 下:Core / RuntimeSets / Variables / Editor |
|
||
| 修改发布方 | 4 | GameManager / HealthSystem / ScoreManager / EchoSystem |
|
||
| 修改订阅方 | 5 | GameResultScreen / GameOverScreen / DamageFlashOverlay / ScoreDisplay / EnemyAI |
|
||
| 去反射/Find 重写 | 3 | GameHUD / EnemyHealthBar / Player(属性) |
|
||
| Shader.Find 修复 | 3 | GameLostOverlay / GroundBuilder / CliffWallBuilder |
|
||
| 编辑器工具 | 1 | UIBuilder(自动连线事件资产) |
|
||
| 文档 | 2 | `plans/quantum-pulse-turing.md` + `Docs/解耦架构重构方案.md` |
|
||
|
||
---
|
||
|
||
## 1. 新增架构框架(设计正确性 · 需重点 review)
|
||
|
||
**文件**:`Architecture/Core/GameEvent.cs`、`TypedGameEvents.cs`、`RuntimeSets/RuntimeSet.cs`、`Variables/VariableRegistry.cs` + `FloatVariable/IntVariable/BoolVariable/Vector3Variable.cs`、`Editor/VariableDrawer.cs`、`Editor/AssetBootstrap.cs`
|
||
|
||
- [ ] **GameEvent 双通道**:`Raise()` 先触发代码监听器(`_codeListeners`),再触发 Inspector 监听器(`_listeners`)。顺序是否符合预期?(当前:代码监听优先)
|
||
- [ ] **代码监听器泄漏**:`Register(Action)` / `Unregister(Action)` 未在 `OnDisable` 对称注销时,组件销毁后事件仍持有引用 → 悬空调用。已要求订阅方在 `OnDisable` 注销,请核对第 3 节。
|
||
- [ ] **VariableRegistry 重置时机**:`[RuntimeInitializeOnLoadMethod(BeforeSceneLoad)]` 在每次进入 Play Mode 触发 `ResetAll()`。确认该回调在 Editor「停止→再播放」时确实重新执行(域重置后)。
|
||
- [ ] **`_defaultValue` 机制**:`ResetToDefault()` 把 `_value` 恢复为 `_defaultValue`。`AssetBootstrap` 生成时已同时写 `_value=_defaultValue`;但**策划手工 `CreateAssetMenu` 新建变量时若只改 `_value` 没改 `_defaultValue`,Reset 会回到 0/初始值**——需在约定里提醒。
|
||
- [ ] **Reset 触发刷新**:`ResetToDefault()` 会 `OnValueChanged?.Invoke(_value)`,可能令 UI 在启动瞬间刷新一次——确认无副作用。
|
||
- [ ] **RuntimeSetRegistrar 空集合**:若 Prefab 未挂 `RuntimeSetRegistrar` 或未拖 `Enemies` 集合,`GameManager.DisableAllEnemyAI` 走 `FindObjectsOfType` 兜底分支(见第 6 节)。
|
||
|
||
---
|
||
|
||
## 2. 发布方改造(static 事件 → SO 字段)
|
||
|
||
- [ ] **GameManager**(`:51-66`):`GameOver()`/`Win()` 仍是 `static`,已改 `Instance.onGameOverEvent?.Raise()`,且方法开头 `if (Instance == null) return;` 保护。✓ 已修 CS0120。
|
||
- [ ] **HealthSystem**(`:62-84`):
|
||
- `OnHealthChanged?.Invoke(health, maxHealth)` 在**每次 `Damage`** 触发(`:80`)——敌人血条据此刷新。
|
||
- **仅 `isPlayer` 时**写 `healthVar`/`maxHealthVar` + 触发 `onPlayerDamagedEvent`(`:65-69, 79, 83`)。⚠️ 敌人路径不写全局变量(关键正确性)。
|
||
- [ ] **ScoreManager**(`:47-48`):`onScoreChanged` / `onScoreSettled` 两个静态事件**均已迁移**到 `scoreChangedEvent` / `scoreSettledEvent`;`SetScore`/`ResetScore`/`ScoreCountUp` 全部改 `Raise(...)`。
|
||
- [ ] **EchoSystem**(`:25`):`OnEchoReleased` → `echoReleasedEvent`;新增公共只读属性 `BellCooldown`(供 HUD),`ringShader` 已为序列化字段。
|
||
|
||
---
|
||
|
||
## 3. 订阅方改造(Register 替代 `+=` static)
|
||
|
||
- [ ] **GameResultScreen**(`:43-44, OnEnable/OnDisable`):`onGameOverEvent.Register(...)` / `onGameWinEvent.Register(...)`,OnDisable 反注册。
|
||
- [ ] **GameOverScreen**(`:27`):`onGameOverEvent.Register(ShowGameOver)`,OnDisable 反注册。
|
||
- [ ] **DamageFlashOverlay**(`:4 using, :32`):`onPlayerDamagedEvent.Register(OnPlayerDamaged)`,OnDestroy 反注册。
|
||
- [ ] **ScoreDisplay**(`:18, OnEnable/OnDisable`):`scoreChangedEvent.Register(UpdateText)`。
|
||
- [ ] **EnemyAI**(`:51, OnEnable/OnDisable`):`echoReleasedEvent.Register(OnBell)`,OnDisable 反注册。
|
||
- [ ] **生命周期对称**:所有订阅在 `OnEnable`/`Start` 注册、`OnDisable`/`OnDestroy` 注销,避免重复订阅或组件销毁后悬空调用。
|
||
|
||
---
|
||
|
||
## 4. 去反射 / 去 Find(关键正确性 · 重点 review)
|
||
|
||
- [ ] **EnemyHealthBar 重写(最重要)**:改为订阅**同物体** `HealthSystem.OnHealthChanged(health, maxHealth)`,**不再读全局 `PlayerHealth`**。修复了原方案「所有敌人共享同一份血量变量」的严重 bug。
|
||
- ⚠️ **接线约束**:敌人 Prefab 上的 `HealthSystem` 其 `healthVar`/`maxHealthVar`/`onPlayerDamagedEvent` 三个 SO 字段**必须留空**,否则敌人受伤会改写 `PlayerHealth`、污染玩家血条与 HUD。
|
||
- [ ] **GameHUD 去反射**:
|
||
- 生命图标:`playerHealthVar.OnValueChanged += UpdateLifeIcons(int)`(已修 CS0123 签名)。✓
|
||
- 技能 CD:读 `PlayerController.RollCooldown` / `EchoSystem.BellCooldown` 公共属性。✓
|
||
- ⚠️ **残留 1 处反射**(`:181-183`):灵灯最大 CD 仍 `typeof(SpiritLanternSystem).GetField("cooldown", NonPublic|Instance)`,属 P2b 未清完。建议给 `SpiritLanternSystem` 加 `public float Cooldown => cooldown;` 后删此反射。
|
||
- **残留 Find**:`Start` 中仍有一次性 `FindWithTag("Player")`(`:55`)和 `FindObjectOfType<DamageFlashOverlay>()`(`:87`)——一次性可接受,非逐帧;若想彻底解耦可改场景引用(留待 P2e)。
|
||
- [ ] **Player.cs**:新增 `public (float remaining, float total) RollCooldown` 只读属性,供 HUD 替代反射。✓
|
||
|
||
---
|
||
|
||
## 5. Shader.Find → 序列化字段(P5 反馈项)
|
||
|
||
- [ ] **GameLostOverlay**(`:waterRippleShader`):原 `Shader.Find("GameFramework/UI/WaterRippleFade")` 改为 `[SerializeField] Shader waterRippleShader`,Inspector 需拖入 `WaterRippleFade.shader`。
|
||
- [ ] **GroundBuilder**(`:edgeGlowMaterial`):原 `Shader.Find("IndianOcean/AbyssEdgeGlow")` 改为序列化 `Shader` 字段,需拖 `AbyssEdgeGlow.shader`。
|
||
- [ ] **CliffWallBuilder**(`:edgeGlowMaterial`):同上,需拖 `AbyssEdgeGlow.shader`。
|
||
- [ ] **EchoSystem**:`ringShader` 字段已存在(确认 Inspector 已拖 `EchoRing.shader`)。
|
||
- [ ] **GroundClipTool.cs:19**:**Editor 脚本**,`Shader.Find` 运行时不进包,暂保留;建议后续也改为序列化字段以统一。
|
||
|
||
---
|
||
|
||
## 6. 编辑器工具
|
||
|
||
- [ ] **UIBuilder.cs**(`:305-307`):`BuildResultScreen` 时 `LoadAssetAtPath<GameEvent>(".../OnGameOver.asset")` 自动连线。⚠️ **前提是先运行 `Architecture > Bootstrap Core Assets` 生成资产**,否则两格为 `null`,需手动拖。
|
||
- [ ] **AssetBootstrap.cs**:菜单项 `Architecture/Bootstrap Core Assets`,生成 11 个 SO 资产到 `Assets/Architecture/Assets/`,已存在则跳过。✓ 已修 CS0246(补 `using Architecture.Core;`)。
|
||
- [ ] **自动创建的 DamageFlashOverlay**(GameHUD.Start 兜底,`:87-91`):该自动实例的 `onPlayerDamagedEvent` 为 `null` → 受击不闪红。建议场景**预置**一个手动接好线的 `DamageFlashOverlay`,HUD 检测到就不自动建。
|
||
|
||
---
|
||
|
||
## 7. Unity 编辑器内必做(手动 · 代码改完 ≠ 能跑)
|
||
|
||
- [ ] 运行菜单 `Architecture > Bootstrap Core Assets` 生成全部 SO 资产。
|
||
- [ ] GameManager:`On Game Over Event`/`On Game Win Event` → 对应资产;`Enemies Set` → `Enemies`;`Enemy Manager Ref` → 场景 EnemyManager(可空)。
|
||
- [ ] **玩家** HealthSystem:`On Player Damaged Event`→`OnPlayerDamaged`、`Health Var`/`Max Health Var`→`PlayerHealth`。
|
||
- [ ] **敌人** HealthSystem:三个 SO 字段**留空**。
|
||
- [ ] EchoSystem:`Echo Released Event`→`EchoReleased`、`Ring Shader`→`EchoRing.shader`。
|
||
- [ ] ScoreManager:`Score Changed Event`→`ScoreChanged`、`Score Settled Event`→`ScoreSettled`(**最易漏**)。
|
||
- [ ] GameHUD:`Score Changed Event`→`ScoreChanged`、`Player Health Var`→`PlayerHealth`。
|
||
- [ ] DamageFlashOverlay:`On Player Damaged Event`→`OnPlayerDamaged`。
|
||
- [ ] ScoreDisplay / GameOverScreen / GameResultScreen:`Score Changed Event` / `On Game Over Event` / `On Game Win Event` 对应资产(若用 UIBuilder 搭建则 GameResultScreen 已自动连)。
|
||
- [ ] EnemyAI:`Echo Released Event`→`EchoReleased`。
|
||
- [ ] 敌人 Prefab 挂 `RuntimeSetRegistrar`(Set→`Enemies`);Player Prefab 挂 `RuntimeSetRegistrar`(Set→`Players`,当前无代码消费,可延后)。
|
||
|
||
---
|
||
|
||
## 8. 编译 / 运行时验证
|
||
|
||
- [ ] **编译全绿**:已修 `GameManager` CS0120、`GameHUD` CS0123、`VariableDrawer` CS0246、`UIBuilder` CS0246;全仓排雷确认其余引用架构类型的文件已带齐 `using`。
|
||
- [ ] **Play Mode 回归**:
|
||
- [ ] 左上角魂灵数随吃魂变化 → `ScoreChanged` 链路通。
|
||
- [ ] 左下角生命图标随受伤减少 → `PlayerHealth` 链路通。
|
||
- [ ] 受伤时屏幕红边闪 → `OnPlayerDamaged` + DamageFlashOverlay。
|
||
- [ ] 按 E 摇铃,附近敌人朝铃铛移动 → `EchoReleased` 链路通。
|
||
- [ ] 玩家死亡 → 失败过场 + 敌人 AI 被禁用 → `OnGameOver` + `Enemies` 集合。
|
||
- [ ] **状态泄漏验证**:退出 Play Mode 再进一次,血量应从 5 开始(`VariableRegistry` 重置生效,无跨局泄漏)。
|
||
|
||
---
|
||
|
||
## 9. 已知风险 & 遗留 TODO
|
||
|
||
| 项 | 位置 | 说明 | 阶段 |
|
||
|----|------|------|------|
|
||
| MainMenuUIController 对象名耦合 | `FindButton`/`GameObject.Find` by name | 改 Inspector 引用或 SO 配置 | P2e |
|
||
| EnemyAI.cs:112 一次性 FindWithTag | Start 中 | 待 Players 集合接入后移除 | P2e |
|
||
| 单例调用方 | ScorePickup / SoulDrop 等 | 改走 SO 事件/变量 | P3 |
|
||
| God Class 拆分 | PlayerController 等 | 按 SRP 拆组件 | P4 |
|
||
| 构建期守门脚本 | 新 Editor 校验 | 静态扫描 GameObject.Find / 静态单例引用 | P6 |
|
||
|
||
> 已本轮解决:灵灯 CD 反射(改为 `SpiritLanternSystem.Cooldown` 属性)、GameLostOverlay 直调(改订阅 OnGameOver)、GameOverScreen 空响应(已删除)、DamageFlashOverlay 空接线(OnEnable 警告 + 场景预置)。
|
||
|
||
---
|
||
|
||
## 10. 回滚说明
|
||
|
||
- 所有新增代码位于 `Assets/Architecture/`,与现有游戏代码零耦合,可整体删除回滚。
|
||
- 对现有文件的修改(发布/订阅方、去反射、Shader.Find)为就地改写;如需回滚,请用 Git 版本对比 `Assets/` 下被改文件。
|
||
- 生成的 SO 资产位于 `Assets/Architecture/Assets/`,可安全删改、重新 `Bootstrap`。
|
||
|
||
---
|
||
|
||
## 11. 第二轮 Review 意见处理记录
|
||
|
||
针对两份 review 意见(严重项 + 顺手修项),已全部落地。改动文件清单:
|
||
|
||
### 严重(需修复)
|
||
|
||
| # | 意见 | 处理 | 文件 |
|
||
|---|------|------|------|
|
||
| 1 | VariableRegistry.ResetAll 时序:BeforeSceneLoad 时 SO 尚未注册导致重置无效 | `Register()` 内立即调用 `variable.ResetToDefault()`,变量「上线」即重置;保留 `ResetAll` 作兜底 | `Architecture/Variables/VariableRegistry.cs` |
|
||
| 2 | HealthSystem 直接调 `GameManager.GameOver()` | 改为 Raise `OnPlayerDied` SO 事件;`GameManager` 订阅并触发 `GameOver()` | `HealthSystem.cs` / `GameManager.cs` |
|
||
| 3 | GameLostOverlay 被 `GameManager` 直接调用 | 改为订阅 `OnGameOver` 事件自激活;删除静态 `Show()` 与 `_instance`/`FindObjectOfType` | `GameLostOverlay.cs` / `GameManager.cs` |
|
||
| 4 | GameResultScreen / GameOverScreen 空响应 | `GameResultScreen` 删除空 `OnGameOver`(只负责胜利);`GameOverScreen` 无引用、已删除 | `GameResultScreen.cs`(删字段/订阅/空方法)、`GameOverScreen.cs`(删除)、`UIBuilder.cs`(移除 onGameOver 接线防 NRE) |
|
||
|
||
### 顺手修(建议项)
|
||
|
||
| # | 意见 | 处理 | 文件 |
|
||
|---|------|------|------|
|
||
| 5 | GameEvent.Raise 顺序:Inspector 优先于代码 | `Raise()` 先触发 `_listeners`(Inspector/UnityEvent),再 `_codeListeners` | `Architecture/Core/GameEvent.cs` |
|
||
| 6 | 灵灯反射残留 | `SpiritLanternSystem` 加 `public float Cooldown`;GameHUD 灵灯 CD 改读该属性,删最后一处反射 | `SpiritLanternSystem.cs` / `GameHUD.cs` |
|
||
| 7 | GameHUD 延迟订阅逻辑 | 移除 `Update` 中逐帧 `ScoreManager.Instance` 延迟订阅,改为 `Start` 无条件注册事件 | `GameHUD.cs` |
|
||
| 8 | ScoreDisplay 初始值直接访问 `ScoreManager.Instance` | 初始值改走事件(显示 0,首个 `ScoreChanged` 刷新),不再访问单例 | `ScoreDisplay.cs` |
|
||
|
||
### 新增接线要求(Unity 编辑器内)
|
||
|
||
- `HealthSystem`(玩家):新增 `On Player Died Event` → `OnPlayerDied` 资产。
|
||
- `GameManager`:新增 `On Player Died Event` → `OnPlayerDied` 资产。
|
||
- `GameLostOverlay`(Gameplay 场景物体):新增 `On Game Over Event` → `OnGameOver` 资产。
|
||
- `DamageFlashOverlay`:**场景预置已接线实例**(拖 `On Player Damaged Event` → `OnPlayerDamaged`),否则受击不闪红(已加空接线警告)。
|
||
- `GameResultScreen`:仅 `On Game Win Event` → `OnGameWin`(不再有 `On Game Over Event` 字段)。
|
||
- `GameOverScreen` 已从项目删除:若旧场景/预制体仍挂该组件,请在 Unity 中移除该组件(避免 Missing Script)。
|
||
|
||
---
|
||
|
||
## 12. Play Mode 回归(用户实测)与诊断加固
|
||
|
||
### 回归结果(03:15)
|
||
| 链路 | 结果 |
|
||
|------|------|
|
||
| 魂灵数 ← ScoreChanged | ✅ |
|
||
| 生命图标 ← PlayerHealth | ✅ |
|
||
| 受击泛红 ← OnPlayerDamaged + DamageFlashOverlay | ❌ **接线缺口** |
|
||
| 摇铃引敌 ← EchoReleased | ❌ **接线缺口** |
|
||
| 玩家死亡过场 ← OnPlayerDied→OnGameOver + Enemies 集合 | ✅ |
|
||
| 退出再进血量=5(VariableRegistry 重置) | ✅ |
|
||
|
||
### 根因(两处失败均为 SO 事件资产未接,非代码 bug)
|
||
- **OnPlayerDamaged**:`HealthSystem.Damage` 第87行 `onPlayerDamagedEvent?.Raise()` 逻辑正确(同机制 OnPlayerDied 已通),断链在 HealthSystem 或/且 DamageFlashOverlay 的 `On Player Damaged Event` 字段为 `null`。
|
||
- **EchoReleased**:`EchoSystem.StartEcho` 第220行 `echoReleasedEvent?.Raise(p)` 与 `EnemyAI.OnEnable` `Register(OnBell)` 均正确(Vector3Event 签名匹配),断链在 EchoSystem 或/且 **敌人预制体** EnemyAI 的 `Echo Released Event` 字段为 `null`(最可能是玩家预制体的 EchoSystem 未接)。
|
||
|
||
### 诊断加固(已落地,无需再改代码)
|
||
- `HealthSystem` / `EchoSystem` / `EnemyAI` / `DamageFlashOverlay` 的事件字段加 `OnValidate()`:**编辑器内即刻在 Inspector 显示黄色警告三角 + 控制台告警**,精确锁定漏接组件。
|
||
- Raise 处加一次性空引用 `Debug.LogWarning`(运行时不刷屏)。
|
||
- `GameHUD` 不再自动 new 一个 event=null 的 DamageFlashOverlay,改为仅告警,避免掩盖「未接线」。
|
||
|
||
### 用户需做的接线修复(看 OnValidate 警告最准)
|
||
1. 玩家预制体 `HealthSystem`:`On Player Damaged Event` → `OnPlayerDamaged`。
|
||
2. 场景中的 `DamageFlashOverlay`:`On Player Damaged Event` → `OnPlayerDamaged`(若场景没有该物体,先放一个)。
|
||
3. 玩家预制体 `EchoSystem`:`Echo Released Event` → `EchoReleased`。
|
||
4. **敌人预制体**(Project 里的 prefab,不是场景临时实例):`EnemyAI.Echo Released Event` → `EchoReleased`。
|
||
5. 重新编译 + 进 Play Mode,先确认控制台无上述 `[HealthSystem]/[EchoSystem]/[EnemyAI]/[DamageFlashOverlay]` 黄色警告,再测受击与摇铃。
|
||
|