重构方案

This commit is contained in:
2026-07-07 03:40:27 +08:00
parent ecb20cf370
commit dc084d5c1e
+83
View File
@@ -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<T>:玩家集 / 敌人集 / 灵灯集
↓ 监听/触发(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<T>` + Int/Float/Vector3/String/GameObject 通道及监听器(含补全的 `StringEventListener`
- `Variables/` — Float/Int/Bool/Vector3 Variable,带 `OnValueChanged` + `ContextMenu` 重置
- `RuntimeSets/RuntimeSet.cs``RuntimeSet<T>` + `TransformRuntimeSet` + `RuntimeSetRegistrar` + 泛型 `RuntimeSetRegistrar<T>`
- `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<EnemyAI>`)、`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 显示缺失引用(编译期可查),不会静默失效——比反射/单例安全