Files
2026-07-07 03:34:56 +08:00

81 lines
12 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.
# 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 要接**预制体资产**而非场景临时实例)。