15 KiB
15 KiB
解耦架构重构 — 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。
- ⚠️ 接线约束:敌人 Prefab 上的
- 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. 编译 / 运行时验证
- 编译全绿:已修
GameManagerCS0120、GameHUDCS0123、VariableDrawerCS0246、UIBuilderCS0246;全仓排雷确认其余引用架构类型的文件已带齐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.OnEnableRegister(OnBell)均正确(Vector3Event 签名匹配),断链在 EchoSystem 或/且 敌人预制体 EnemyAI 的Echo Released Event字段为null(最可能是玩家预制体的 EchoSystem 未接)。
诊断加固(已落地,无需再改代码)
HealthSystem/EchoSystem/EnemyAI/DamageFlashOverlay的事件字段加OnValidate():编辑器内即刻在 Inspector 显示黄色警告三角 + 控制台告警,精确锁定漏接组件。- Raise 处加一次性空引用
Debug.LogWarning(运行时不刷屏)。 GameHUD不再自动 new 一个 event=null 的 DamageFlashOverlay,改为仅告警,避免掩盖「未接线」。
用户需做的接线修复(看 OnValidate 警告最准)
- 玩家预制体
HealthSystem:On Player Damaged Event→OnPlayerDamaged。 - 场景中的
DamageFlashOverlay:On Player Damaged Event→OnPlayerDamaged(若场景没有该物体,先放一个)。 - 玩家预制体
EchoSystem:Echo Released Event→EchoReleased。 - 敌人预制体(Project 里的 prefab,不是场景临时实例):
EnemyAI.Echo Released Event→EchoReleased。 - 重新编译 + 进 Play Mode,先确认控制台无上述
[HealthSystem]/[EchoSystem]/[EnemyAI]/[DamageFlashOverlay]黄色警告,再测受击与摇铃。