Files
gold_dolphin/unity/Docs/解耦重构Review清单.md
2026-07-07 03:34:56 +08:00

201 lines
15 KiB
Markdown
Raw Permalink 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.
# 解耦架构重构 — Review 清单
> 范围:P0 地基 + P1 事件去静态化 + P2a/P2b 去反射/Find + P5Shader.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 集合 | ✅ |
| 退出再进血量=5VariableRegistry 重置) | ✅ |
### 根因(两处失败均为 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]` 黄色警告,再测受击与摇铃。