重构方案
This commit is contained in:
@@ -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 显示缺失引用(编译期可查),不会静默失效——比反射/单例安全
|
||||
Reference in New Issue
Block a user