diff --git a/unity/Docs/quantum-pulse-turing.md b/unity/Docs/quantum-pulse-turing.md new file mode 100644 index 0000000..473bc3e --- /dev/null +++ b/unity/Docs/quantum-pulse-turing.md @@ -0,0 +1,83 @@ +# Unity 项目解耦架构重构 — 整体计划(v1.1) + +> 配套详细诊断与改造映射见 `Docs/解耦架构重构方案.md`(已据同行评审修正至 v1.1) +> 当前状态:**P0 地基 + P1 事件去静态化 + P2 大部分(去 Find/反射/Shader.Find)已落地**,P2d 运行时集合接入、P3/P4/P5/P6 待推进 + +## 1. 目标 + +以 **ScriptableObject 为通信总线**,消除当前耦合腐烂信号: +- 7 个跨场景单例(`GameManager`/`AudioManager`/`ScoreManager`/`SceneLoader`/`TimeController`/`EnemyManager`/`LightMaskSystem`) +- `GameObject.Find/FindWithTag/FindObjectsByType` 散落运行时(含 `GameHUD` 条件性 `FindWithTag` 补救、`EnemyAI` Start 一次 `FindWithTag`、`MainMenuUIController` 对象名耦合) +- UI 反射读私有字段(`GameHUD`、`EnemyHealthBar`) +- God Class(`GameManager`/`HealthSystem`);`PlayerController` 仅轻度耦合 +- `static Action` 事件伪总线(`onGameOver/onGameWin/onPlayerDamaged/OnEchoReleased/onScoreChanged`) +- `Resources.Load` / `Shader.Find` 字符串硬编码资源路径 + +**结果形态**:所有共享状态进 Variable 资产、跨系统消息走 Event 通道、实体集合走 RuntimeSet。MonoBehaviour 只认 SO 资产引用,彼此不再直连。 + +## 2. 目标架构分层 + +``` +L0 SO 通信总线(项目级资产,跨场景存活,无 MonoBehaviour) + · Event 通道:GameEvent / IntEvent / Vector3Event / StringEvent / GameObjectEvent + · Variable 资产:FloatVariable / IntVariable / BoolVariable / Vector3Variable + · RuntimeSet:玩家集 / 敌人集 / 灵灯集 + ↓ 监听/触发(Inspector 引用) ↓ 注册/读取 +L1 系统服务(Audio/Scene/Time/Spawn) L2 实体(Player/Enemy/Lantern) + ↓ 触发事件 + L3 表现层(HUD/Overlay,只读 Variable + 监听 Event) +``` +层间**零直接类引用**,谁触发/监听全在 Inspector 可见可配。 + +## 3. 四条铁律 + +1. **调用方不碰单例** → 改 `XxxEvent.Raise()`,服务类自己订阅 +2. **生产代码零 `GameObject.Find`** → 找实体走 `RuntimeSet` +3. **共享状态进 Variable** → UI 订阅 `OnValueChanged`,不再反射/逐帧 Find +4. **一个 MonoBehaviour 一件事** → 拆分 God Class + +## 4. 已落地地基(P0,增量零侵入) + +`Assets/Architecture/`(**未改动任何现有文件**,可整体回滚): +- `Core/GameEvent.cs` — 无参事件通道 + `GameEventListener` +- `Core/TypedGameEvents.cs` — `GameEvent` + Int/Float/Vector3/String/GameObject 通道及监听器(含补全的 `StringEventListener`) +- `Variables/` — Float/Int/Bool/Vector3 Variable,带 `OnValueChanged` + `ContextMenu` 重置 +- `RuntimeSets/RuntimeSet.cs` — `RuntimeSet` + `TransformRuntimeSet` + `RuntimeSetRegistrar` + 泛型 `RuntimeSetRegistrar` +- `Editor/VariableDrawer.cs` — Inspector 实时显示变量值 +- `Variables/VariableRegistry.cs` — **运行时状态泄漏防护**:`IVariable` 接口 + `VariableRegistry`,在每次进入 Play Mode 时(`[RuntimeInitializeOnLoadMethod(BeforeSceneLoad)]`)统一 `ResetToDefault()`,防止 SO 变量跨局持久化(如 PlayerHealth 残留 0 直接触发死亡) +- `Editor/AssetBootstrap.cs` — 一键生成核心 SO 资产(菜单 `Architecture > Bootstrap Core Assets`),已存在则跳过;变量同时写入 `_value` 与 `_defaultValue`,从源头杜绝跨 Play Mode 泄漏 + +## 5. 分阶段路线图 + +| 阶段 | 目标 | 关键动作 | 风险 | +|---|---|---|---| +| **P0 地基**(已完成) | 引入通信总线 | 落地 `Assets/Architecture/` + 创建首批资产 | 极低 | +| **P1 事件去静态化** | 消灭 `static Action` 事件 | `onGameOver/onGameWin/onPlayerDamaged/onScoreChanged/onScoreSettled/OnEchoReleased` → SO 事件资产,1:1 替换订阅 | 低 | +| **P2 去掉 Find** | 消灭 Find / 反射 | 建 `PlayerRuntimeSet`/`EnemyRuntimeSet`/`LanternRuntimeSet`,预制体挂 Registrar;改写 `GameHUD`/`EnemyAI`/`GameManager`/`MainMenuUIController`/`GameLostOverlay`/`EnemyHealthBar` | 中 | +| **P3 去单例调用方** | 调用方不再 `XxxManager.Instance` | `Audio/Scene/Time/Score` 改事件订阅者;`ScorePickup`/`SoulDrop`/`ScoringUIController` 改走 Variable/Event | 中 | +| **P4 拆分 God Class** | 单一职责 | `GameManager`→状态机+过场+敌人控制;`HealthSystem`→健康+死亡表现;`PlayerController` 轻量剥离 `RequireComponent` | 中高 | +| **P5 资源与配置** | 去 `Resources.Load`/`Shader.Find`/魔法串 | 音频/视频→**SerializeField 直接引用(AudioClip/VideoClip 拖到 SO/组件)**,**不引入 Addressables**(此项目体量下 Addressables 工程复杂度过高,需装包/建分组/异步加载);`Shader.Find`→资产引用;场景名/标签→SO 配置 | 中 | +| **P6 工具与守门** | 防回归 | `VariableDrawer`(已完成)+ 构建期校验脚本(扫描生产代码 `GameObject.Find`)+ 设计师文档 | 低 | + +建议节奏:**P0→P1→P2 一个迭代内完成**(收益最大、风险最低);P3/P4 按系统逐个推进;P5/P6 并行。 + +## 6. 关键待改文件清单(按阶段) + +- **P1**:`GameManager.cs`(去 `onGameOver`/`onGameWin` static)、`HealthSystem.cs`、`EchoSystem.cs`、`ScoreManager.cs`(去 `onScoreChanged` **+ `onScoreSettled` 两个 static**,二者需一并迁移) +- **P2**:`GameHUD.cs`(`:104` 条件 `FindWithTag` + `:150-199` 反射)、`EnemyHealthBar.cs`(`:50-53,73-75` 反射)、`EnemyAI.cs`(`:112` FindWithTag + `:232` Damage)、`GameManager.cs`(`FindObjectsOfType`)、`MainMenuUIController.cs`(`:55,61,66,143` 对象名耦合)、`GameLostOverlay.cs`(`:85` 自查找);并创建 `PlayerRuntimeSet`/`EnemyRuntimeSet`/`LanternRuntimeSet` 资产 +- **P3**:`AudioManager`/`SceneLoader`/`TimeController`/`ScoreManager` 改订阅者;`ScorePickup.cs:39`/`SoulDrop.cs:110`/`ScoringUIController.cs`/`MainMenuUIController.cs:206,216`/`GameLostOverlay.cs:246` 改 `Raise` +- **P4**:`GameManager.cs` 拆分;`HealthSystem.cs` 拆分;`Player.cs` 轻量剥离 `RequireComponent` +- **P5**:`AudioManager.cs:121`/`StoryPVPlayer.cs:141` 的 `Resources.Load` → SO 资产引用;`Shader.Find` **共 5 处** → 序列化 `Shader` 字段引用:`GameLostOverlay.cs:358`(WaterRippleFade)、`EchoSystem.cs:140`(EchoRing,已有 `ringShader` 字段)、`GroundBuilder.cs:180`(AbyssEdgeGlow)、`CliffWallBuilder.cs:108`(AbyssEdgeGlow)、`GroundClipTool.cs:19`(SpriteWithGroundClip,**Editor 脚本,运行时不进包,可保留 Shader.Find,但建议同样改字段引用**));场景名魔法串 → `SceneList` SO +- **P6**:构建期校验脚本(扫描 `GameObject.Find`)+ 设计师 SO 配置文档 + +## 7. 本迭代建议起点(P1+P2 验证链路) + +1. 运行菜单 `Architecture > Bootstrap Core Assets` 一键生成首批资产(`PlayerHealth`/`Score` IntVariable、`OnGameOver`/`OnGameWin`/`OnPlayerDied`/`OnPlayerDamaged` GameEvent、`EchoReleased`/`ScoreChanged`/`ScoreSettled` 带载荷事件、`Players`/`Enemies` RuntimeSet);已存在则跳过 +2. 从最小系统切入:把 `GameHUD` 接到 `PlayerHealth` 变量(消灭逐帧条件 Find 回退)+ 消除 `EnemyHealthBar` 反射(改用 `maxHealth`/`health` 的 IntVariable 订阅) +3. 跑通后按路线图逐阶段推进;具体系统改造指名文件即可 + +## 8. 风险与回滚 + +- 架构核心位于 `Assets/Architecture/`,可整体删除回滚;P1/P2 对既有文件的改造均为"加 SO 字段 + 改订阅方式",保留原逻辑,可逐文件回退 +- 迁移保持"旧接口可用、新接口并行",每阶段独立验证,避免大爆炸重写 +- 事件通道为引用语义:误删资产会在 Inspector 显示缺失引用(编译期可查),不会静默失效——比反射/单例安全