This commit is contained in:
2026-07-07 03:34:56 +08:00
parent e7891d7e00
commit ecb20cf370
81 changed files with 2108 additions and 293 deletions
+80
View File
@@ -0,0 +1,80 @@
# 2026-07-07
## Unity 项目解耦架构重构(gold_dolphin/unity
- **架构审计结论**:定位到严重耦合信号
- 7 个跨场景单例:GameManager/AudioManager/ScoreManager/SceneLoader(均 PersistentSingleton)、TimeController(Singleton)、EnemyManager/LightMaskSystem(static Instance)
- `GameObject.Find/FindWithTag/FindObjectsByType` 散落运行时代码;`GameHUD.Update` 仅在 `_playerHealth==null` 时**条件性** `FindWithTag("Player")` 补救(非每帧,但仍是反模式);`EnemyAI` 的 FindWithTag 仅 Start 一次
- `GameHUD``typeof(X).GetField(..., NonPublic|Instance)` 反射读私有字段(health/rollCooldown/_lastEchoTime 等),字段改名会静默失效
- God ClassGameManager(约295行,6职责)、HealthSystem(约216行,健康+死亡+溶解)PlayerController 仅轻度耦合(112行,RequireComponent 3系统),非 God Class
- static 事件伪总线:onGameOver/onPlayerDamaged/OnEchoReleased/onScoreChanged
- EnemyAI 直接 `GetComponent<HealthSystem>().Damage()` + HealthSystem 反向调 `GameManager.GameOver()`
- `Resources.Load` 用于音频/视频
- **已落地解耦基础框架** `Assets/Architecture/`(增量、零侵入、未改现有文件):
- Core/GameEvent.cs(无参事件通道+监听器)、Core/TypedGameEvents.csGameEvent<T> + Int/Float/Vector3/String/GameObject 通道及监听器)
- Variables/Float/Int/Bool/Vector3 Variable,带 OnValueChanged + ContextMenu 重置)
- RuntimeSets/RuntimeSet.cs(泛型集合 + TransformRuntimeSet + RuntimeSetRegistrar 自动注册)
- Editor/VariableDrawer.csInspector 实时显示变量值)
- **产出方案文档** `Docs/解耦架构重构方案.md`:分层架构(LO SO总线 / L1系统 / L2实体 / L3表现)、逐条 Before→After 改造映射、P0-P6 分阶段路线图。
- **项目约定**:无 .asmdef,所有代码在 Assembly-CSharpGameEvent/RuntimeSet/FloatVariable 等类名无冲突,新增代码可安全编入。
## 同行评审修正(2 份意见,全部核实采纳)
- 文档 v1.1 已根据评审重写 `Docs/解耦架构重构方案.md`
- 严重度下调:`GameHUD` 的 FindWithTag 实为 `_playerHealth==null` 条件回退(非每帧);`PlayerController` 从 God Class 降级为轻度耦合(112行/RequireComponent 3系统)。
- 行号修正:`EnemyAI.Damage()``:232`(非 :230)。
- 补遗漏点:`EnemyHealthBar` 反射读私有字段(`:50-53,73-75`)、`GameHUD` Start 的 `FindObjectOfType<DamageFlashOverlay>``onGameWin` 静态事件被 `GameResultScreen` 监听、`GameLostOverlay``Shader.Find``FindObjectOfType` 自检查、`MainMenuUIController` 对象名耦合、以及 `ScorePickup/SoulDrop/ScoringUIController/MainMenuUIController/GameLostOverlay``ScoreManager/SceneLoader` 的直接单例调用线。
- **代码补强**(评审点):`TypedGameEvents.cs``StringEventListener`(初稿声称有但未实现);`RuntimeSet.cs` 补泛型 `RuntimeSetRegistrar<T>`(强类型集合如 `EnemyRuntimeSet` 用,领域文件夹写一行具体闭包子类即可)。
## 续作:P1+P2 落地改造(代码侧,本次会话)
- **P1 静态事件 → SO 事件资产(全部完成,含 onScoreSettled**
- 发布方改写:GameManager(`onGameOver`/`onGameWin`→GameEvent)、HealthSystem(`onPlayerDamaged`→GameEvent)、EchoSystem(`OnEchoReleased`→Vector3Event)、ScoreManager(`onScoreChanged`/`onScoreSettled`→IntEvent)。
- 订阅方改写:GameResultScreen/GameOverScreen(→`onGameOver`/`onGameWin` GameEvent.Register)、DamageFlashOverlay(→`onPlayerDamagedEvent`.Register)、ScoreDisplay(→`scoreChangedEvent` IntEvent.Register)、EnemyAI(→`echoReleasedEvent` Vector3Event.Register)。
- APIGameEvent 同时支持 Inspector 监听器(GameEventListener) 与代码监听器 `Register(System.Action)`GameEvent<T> 用 `Register(Action<T>)`
- **P2a GameHUD**:接 PlayerHealth IntVariableOnValueChanged 驱动 UpdateLifeIcons+ scoreChangedEvent;技能 CD 改读 PlayerController.RollCooldown / EchoSystem.BellCooldown 公共只读属性(替代反射)。
- **P2b EnemyHealthBar 重要正确性修正**:原方案让所有 HealthSystem 写同一个全局 IntVariable,会导致多敌人共享同一份血量(严重 bug)。改为 EnemyHealthBar 引用**同物体** HealthSystemRequireComponent 保证),订阅其 `OnHealthChanged` 事件刷新;HealthSystem 的全局 `healthVar`/`maxHealthVar` 写入**仅限 `isPlayer`**。HealthSystem 新增 `CurrentHealth`/`MaxHealth` 只读属性 + `OnHealthChanged` 事件。
- **P2d GameManager**DisableAllEnemyAI 改用 Enemies(TransformRuntimeSet) 遍历禁用,替代 `FindObjectsOfType<EnemyAI>()`EnemyManager 改 SerializeField 引用。保留 `FindObjectsOfType` 兜底分支(enemiesSet 未接入时),接入敌人 Prefab 的 RuntimeSetRegistrar 后可删。
- **P5 Shader.Find ×5 处理**GameLostOverlay(水波纹→`waterRippleShader` 字段)、GroundBuilder/CliffWallBuilder(AbyssEdgeGlow→`edgeGlowShader` 字段)EchoSystem 已有 `ringShader` 字段;GroundClipTool.cs:19 为 Editor 脚本(运行时不进包,保留但建议改字段)。
- **变量跨 Play Mode 状态泄漏(用户重点反馈)**VariableRegistry + `IVariable.ResetToDefault()` + `[RuntimeInitializeOnLoadMethod(BeforeSceneLoad)]` 在每次进 Play Mode 统一重置;AssetBootstrap 生成变量时同时写 `_value`/`_defaultValue`
- **UIBuilder.cs**BuildResultScreen 时自动 LoadAssetAtPath 连线 OnGameOver/OnGameWin 资产,使编辑器搭建的 UI 直接可用(需先运行 `Architecture > Bootstrap Core Assets`)。
- **仍需手动/后续步骤(Unity 编辑器内)**:
1. 运行菜单 `Architecture > Bootstrap Core Assets` 生成全部 SO 资产。
2. 各 UI/系统组件 Inspector 拖入对应事件/变量资产(GameResultScreen/GameOverScreen/DamageFlashOverlay/ScoreDisplay/EnemyAI/GameHUD 等)。
3. 敌人 Prefab 挂 RuntimeSetRegistrar(Enemies)、Player Prefab 挂 RuntimeSetRegistrar(Players),使运行时集合被填充;之后删 GameManager 的 FindObjectsOfType 兜底分支。
4. P2e 剩余:MainMenuUIController 对象名耦合 + GameLostOverlay.cs:85 FindObjectOfType 自检查;EnemyAI.cs:112 Start 一次性 FindWithTag(待 Players 集合接入后移除)。
- **验证**:全仓 grep 确认 `HealthSystem.onPlayerDamaged`/`ScoreManager.onScore*`/`EchoSystem.OnEchoReleased`/`GameManager.onGameOver/onGameWin` 已无代码引用(仅注释),新 API 表面(OnHealthChanged/CurrentHealth/RollCooldown/BellCooldown/IVariable)均已落地。
## 计划/方案文档同步(用户 4 条反馈)
- Addressables 过度:P5 改为仅 SerializeField 引用(AudioClip/VideoClip),不引入 Addressables。
- 变量重置策略:计划 P0/P1 明确 VariableRegistry + _defaultValue 方案。
- Shader.Find 5 处:P5 清单补全。
- onScoreSettledP1 一并迁移。
- 两份文档(`plans/quantum-pulse-turing.md``Docs/解耦架构重构方案.md`)均已更新至反映上述反馈。
## 编译错误修复(用户反馈 4 个 CS 错误,已全部修正)
- `GameManager.cs:55/64`CS0120 —— `GameOver()`/`Win()` 是 static 方法,却直接访问实例字段 `onGameOverEvent`/`onGameWinEvent`。改为 `Instance.onGameOverEvent?.Raise()` / `Instance.onGameWinEvent?.Raise()`(方法开头已有 `if (Instance == null) return;` 保护,安全)。
- `GameHUD.cs:82/102`CS0123 —— `IntVariable.OnValueChanged``Action<int>`,但 `UpdateLifeIcons()` 是无参方法,委托签名不匹配。改为 `UpdateLifeIcons(int value)`(内部 `currentHealth = value`),并把初始直接调用 `UpdateLifeIcons()``UpdateLifeIcons(playerHealthVar.Value)`
- **经验教训(可复用)**:① 把 static 事件总线改成 SO 事件字段后,原 `GameManager.GameOver()/Win()` 这类 static 入口方法必须改走 `Instance.字段`(或把字段也变 static,但 SO 资产不应是 static);② 订阅 `Variable.OnValueChanged``Action<T>`)时,回调方法必须带对应 `T` 参数,否则 CS0123。已提醒用户编译后按接线手册拖资产。
- `VariableDrawer.cs`CS0246 —— `Architecture.Editor` 命名空间下的 Editor 脚本引用 `FloatVariable/IntVariable/BoolVariable/Vector3Variable`(位于 `Architecture.Variables`)时漏 `using Architecture.Variables;`。**规则**Architecture 下的 Editor 脚本都需显式 `using Architecture.Variables;``using Architecture.Core;``AssetBootstrap.cs` 已带齐,无需改。
- `UIBuilder.cs`(位于 `Assets/Editor/`,非 Architecture/Editor):CS0246 —— 引用 `GameEvent``Architecture.Core`)漏 `using Architecture.Core;`,已在文件头补上。已全仓 grep 排雷:其余引用架构类型的外部文件(DamageFlashOverlay/GameHUD/GameOverScreen/GameResultScreen/EnemyAI/EchoSystem/HealthSystem/GameManager/ScoreDisplay/ScoreManager)均已在改写时带齐 using,无其它漏网。`TransformRuntimeSet``GameEvent` 同处 `Architecture.Core` 命名空间。
## 第二轮 Review 意见处理(2 份,全部落地)
- **关键时序修复**`VariableRegistry.Register()` 内立即 `variable.ResetToDefault()`,解决 `RuntimeInitializeOnLoadMethod(BeforeSceneLoad)` 在 SO 尚未 OnEnable 注册前执行 `ResetAll`_variables 为空)导致重置无效、跨局状态残留的问题。`ResetAll` 保留作兜底。
- **HealthSystem 去单例直调**:玩家死亡改为 `onPlayerDiedEvent?.Raise()`,不再 `GameManager.GameOver()``GameManager` 订阅 `OnPlayerDied``GameOver()`。新增双方 `onPlayerDiedEvent` 字段(同指 `OnPlayerDied` 资产)。
- **GameLostOverlay 解耦**:删除静态 `Show()` / `_instance` / `FindObjectOfType`,改为订阅 `OnGameOver` 事件(`OnEnable` 注册 → `Activate`)。`GameManager.DeathTransition` 不再直接调用。
- **空响应清理**`GameResultScreen` 删除空 `OnGameOver` 方法+字段+订阅(只负责胜利);`GameOverScreen` 全仓无引用,已删除 `.cs`+`.meta``UIBuilder` 同步移除 `onGameOverEvent` 接线(否则 `FindProperty` 返回 null 抛 NRE)。
- **顺手修**`GameEvent.Raise()` 改为 Inspector 监听(`_listeners`)先于代码监听(`_codeListeners`)`SpiritLanternSystem``public float Cooldown`GameHUD 灵灯 CD 删最后一处反射;GameHUD 移除 `Update` 逐帧延迟订阅 ScoreManager(改为 Start 无条件注册);ScoreDisplay 初始值走事件不再访问 `ScoreManager.Instance`
- **静态校验通过**:grep 确认无 `GameOverScreen`/`GameLostOverlay.Show`/`_instance`/`typeof(SpiritLanternSystem)` 残留;`onPlayerDiedEvent`(HealthSystem 发布/GameManager 订阅)、`onGameOverEvent`(GameManager 发布/GameLostOverlay 订阅) 一致。
- **新增接线要求**HealthSystem+GameManager 的 `On Player Died Event``OnPlayerDied`GameLostOverlay(场景物体) `On Game Over Event``OnGameOver`DamageFlashOverlay 需场景预置已接线实例;GameResultScreen 仅 `On Game Win Event`。旧场景若残留 GameOverScreen 组件需手动移除。
## Play Mode 回归(用户实测,03:15
- ✅ 通过:魂灵数(ScoreChanged)、生命图标(PlayerHealth)、玩家死亡过场(OnPlayerDied→OnGameOver+Enemies 集合)、VariableRegistry 重置(退出再进血量=5,无跨局泄漏)。
- ❌ 失败两项(已定位,均为 SO 事件资产接线缺口,非代码 bug):
1. **OnPlayerDamaged 受击泛红**HealthSystem.Damage 第87行 `onPlayerDamagedEvent?.Raise()` 逻辑正确(同机制 OnPlayerDied 可通),断链原因为 HealthSystem 或/且 DamageFlashOverlay 的 `On Player Damaged Event` 字段为 null。
2. **EchoReleased 摇铃引敌**EchoSystem.StartEcho 第220行 `echoReleasedEvent?.Raise(p)` + EnemyAI.OnEnable `Register(OnBell)` 均正确,断链原因为 EchoSystem 或/且 敌人预制体 EnemyAI 的 `Echo Released Event` 字段为 null(极可能玩家预制体 EchoSystem 未接)。
- **诊断加固(已落地)**:给 HealthSystem / EchoSystem / EnemyAI / DamageFlashOverlay 的事件字段加 `OnValidate()` 编辑器实时告警(Inspector 黄色三角+控制台),并在 Raise 处加一次性空引用 `Debug.LogWarning`;另把 GameHUD 中「自动 new 一个 event=null 的 DamageFlashOverlay」改为仅告警不创建,避免掩盖未接线问题。
- **结论**:代码链路正确,失败=事件字段未接。用户需在编辑器看 OnValidate 警告锁定漏接组件,重拖 `OnPlayerDamaged`/`EchoReleased` 资产(注意 EnemyAI 要接**预制体资产**而非场景临时实例)。