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

15 KiB
Raw Permalink Blame History

解耦架构重构 — 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.csTypedGameEvents.csRuntimeSets/RuntimeSet.csVariables/VariableRegistry.cs + FloatVariable/IntVariable/BoolVariable/Vector3Variable.csEditor/VariableDrawer.csEditor/AssetBootstrap.cs

  • GameEvent 双通道Raise() 先触发代码监听器(_codeListeners),再触发 Inspector 监听器(_listeners)。顺序是否符合预期?(当前:代码监听优先)
  • 代码监听器泄漏Register(Action) / Unregister(Action) 未在 OnDisable 对称注销时,组件销毁后事件仍持有引用 → 悬空调用。已要求订阅方在 OnDisable 注销,请核对第 3 节。
  • VariableRegistry 重置时机[RuntimeInitializeOnLoadMethod(BeforeSceneLoad)] 在每次进入 Play Mode 触发 ResetAll()。确认该回调在 Editor「停止→再播放」时确实重新执行(域重置后)。
  • _defaultValue 机制ResetToDefault()_value 恢复为 _defaultValueAssetBootstrap 生成时已同时写 _value=_defaultValue;但策划手工 CreateAssetMenu 新建变量时若只改 _value 没改 _defaultValueReset 会回到 0/初始值——需在约定里提醒。
  • Reset 触发刷新ResetToDefault()OnValueChanged?.Invoke(_value),可能令 UI 在启动瞬间刷新一次——确认无副作用。
  • RuntimeSetRegistrar 空集合:若 Prefab 未挂 RuntimeSetRegistrar 或未拖 Enemies 集合,GameManager.DisableAllEnemyAIFindObjectsOfType 兜底分支(见第 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)——敌人血条据此刷新。
    • isPlayerhealthVar/maxHealthVar + 触发 onPlayerDamagedEvent:65-69, 79, 83)。⚠️ 敌人路径不写全局变量(关键正确性)。
  • ScoreManager:47-48):onScoreChanged / onScoreSettled 两个静态事件均已迁移scoreChangedEvent / scoreSettledEventSetScore/ResetScore/ScoreCountUp 全部改 Raise(...)
  • EchoSystem:25):OnEchoReleasedechoReleasedEvent;新增公共只读属性 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 上的 HealthSystemhealthVar/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 未清完。建议给 SpiritLanternSystempublic float Cooldown => cooldown; 后删此反射。
    • 残留 FindStart 中仍有一次性 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 waterRippleShaderInspector 需拖入 WaterRippleFade.shader
  • GroundBuilder:edgeGlowMaterial):原 Shader.Find("IndianOcean/AbyssEdgeGlow") 改为序列化 Shader 字段,需拖 AbyssEdgeGlow.shader
  • CliffWallBuilder:edgeGlowMaterial):同上,需拖 AbyssEdgeGlow.shader
  • EchoSystemringShader 字段已存在(确认 Inspector 已拖 EchoRing.shader)。
  • GroundClipTool.cs:19Editor 脚本Shader.Find 运行时不进包,暂保留;建议后续也改为序列化字段以统一。

6. 编辑器工具

  • UIBuilder.cs:305-307):BuildResultScreenLoadAssetAtPath<GameEvent>(".../OnGameOver.asset") 自动连线。⚠️ 前提是先运行 Architecture > Bootstrap Core Assets 生成资产,否则两格为 null,需手动拖。
  • AssetBootstrap.cs:菜单项 Architecture/Bootstrap Core Assets,生成 11 个 SO 资产到 Assets/Architecture/Assets/,已存在则跳过。✓ 已修 CS0246(补 using Architecture.Core;)。
  • 自动创建的 DamageFlashOverlayGameHUD.Start 兜底,:87-91):该自动实例的 onPlayerDamagedEventnull → 受击不闪红。建议场景预置一个手动接好线的 DamageFlashOverlayHUD 检测到就不自动建。

7. Unity 编辑器内必做(手动 · 代码改完 ≠ 能跑)

  • 运行菜单 Architecture > Bootstrap Core Assets 生成全部 SO 资产。
  • GameManagerOn Game Over Event/On Game Win Event → 对应资产;Enemies SetEnemiesEnemy Manager Ref → 场景 EnemyManager(可空)。
  • 玩家 HealthSystemOn Player Damaged EventOnPlayerDamagedHealth Var/Max Health VarPlayerHealth
  • 敌人 HealthSystem:三个 SO 字段留空
  • EchoSystemEcho Released EventEchoReleasedRing ShaderEchoRing.shader
  • ScoreManagerScore Changed EventScoreChangedScore Settled EventScoreSettled最易漏)。
  • GameHUDScore Changed EventScoreChangedPlayer Health VarPlayerHealth
  • DamageFlashOverlayOn Player Damaged EventOnPlayerDamaged
  • ScoreDisplay / GameOverScreen / GameResultScreenScore Changed Event / On Game Over Event / On Game Win Event 对应资产(若用 UIBuilder 搭建则 GameResultScreen 已自动连)。
  • EnemyAIEcho Released EventEchoReleased
  • 敌人 Prefab 挂 RuntimeSetRegistrarSet→Enemies);Player Prefab 挂 RuntimeSetRegistrarSet→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() 先触发 _listenersInspector/UnityEvent),再 _codeListeners Architecture/Core/GameEvent.cs
6 灵灯反射残留 SpiritLanternSystempublic float CooldownGameHUD 灵灯 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 EventOnPlayerDied 资产。
  • GameManager:新增 On Player Died EventOnPlayerDied 资产。
  • GameLostOverlayGameplay 场景物体):新增 On Game Over EventOnGameOver 资产。
  • DamageFlashOverlay场景预置已接线实例(拖 On Player Damaged EventOnPlayerDamaged),否则受击不闪红(已加空接线警告)。
  • GameResultScreen:仅 On Game Win EventOnGameWin(不再有 On Game Over Event 字段)。
  • GameOverScreen 已从项目删除:若旧场景/预制体仍挂该组件,请在 Unity 中移除该组件(避免 Missing Script)。

12. Play Mode 回归(用户实测)与诊断加固

回归结果(03:15

链路 结果
魂灵数 ← ScoreChanged
生命图标 ← PlayerHealth
受击泛红 ← OnPlayerDamaged + DamageFlashOverlay 接线缺口
摇铃引敌 ← EchoReleased 接线缺口
玩家死亡过场 ← OnPlayerDied→OnGameOver + Enemies 集合
退出再进血量=5VariableRegistry 重置)

根因(两处失败均为 SO 事件资产未接,非代码 bug)

  • OnPlayerDamagedHealthSystem.Damage 第87行 onPlayerDamagedEvent?.Raise() 逻辑正确(同机制 OnPlayerDied 已通),断链在 HealthSystem 或/且 DamageFlashOverlay 的 On Player Damaged Event 字段为 null
  • EchoReleasedEchoSystem.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. 玩家预制体 HealthSystemOn Player Damaged EventOnPlayerDamaged
  2. 场景中的 DamageFlashOverlayOn Player Damaged EventOnPlayerDamaged(若场景没有该物体,先放一个)。
  3. 玩家预制体 EchoSystemEcho Released EventEchoReleased
  4. 敌人预制体Project 里的 prefab,不是场景临时实例):EnemyAI.Echo Released EventEchoReleased
  5. 重新编译 + 进 Play Mode,先确认控制台无上述 [HealthSystem]/[EchoSystem]/[EnemyAI]/[DamageFlashOverlay] 黄色警告,再测受击与摇铃。