多轮审查评估
This commit is contained in:
287
Docs/Review/FrameworkReview_2026_May_v18.md
Normal file
287
Docs/Review/FrameworkReview_2026_May_v18.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# Framework Review — 2026 May v18
|
||||
|
||||
**项目**:zeling_v2
|
||||
**Review 范围**:Core 基础设施全覆盖(GameManager/FSM/ServiceLocator/EventChannel/Pool/Save/Settings/Difficulty/Scene)+ World/Map + World/Shop + Player 完整模块 + Editor 工具 + Support/Platform
|
||||
**基线分**:9.74(v17 结束时)
|
||||
**本轮修复问题**:TD-39 / TD-40 / TD-42 / TD-44 + 代码风格 TD-41
|
||||
|
||||
---
|
||||
|
||||
## 1. 本轮覆盖的文件清单
|
||||
|
||||
### Core 基础设施
|
||||
| 文件 | 功能 |
|
||||
|------|------|
|
||||
| `GameManager.cs` | 全局游戏流程 FSM 协调器,单例 |
|
||||
| `GameStateMachine.cs` | 状态机骨架,ValidNextStates 校验 |
|
||||
| `GameServiceRegistrar.cs` | 服务注册最早入口(Execution Order -2000) |
|
||||
| `BuiltinGameStates.cs` | 9 个内置 GameState 实现 |
|
||||
| `GameIds.cs` | 全局常量 ID 集中点 |
|
||||
| `DeathRespawnService.cs` | 死亡/复活流程协程 |
|
||||
| `SceneLoader.cs` | Addressables 场景加载工具(本轮重构) |
|
||||
| `SceneService.cs` | ISceneService 实现,协调 fade + SceneLoader(本轮重构) |
|
||||
| `Difficulty/DifficultyManager.cs` | 难度管理 + ISaveable |
|
||||
| `Assets/AssetLoader.cs` | Addressables 薄封装工具 |
|
||||
| `Assets/AssetReleaseTracker.cs` | 场景生命周期 handle 追踪 |
|
||||
| `Assets/AddressKeys.cs` | Addressable 地址常量(本轮修复缩进) |
|
||||
| `Pool/GlobalObjectPool.cs` | LRU 感知对象池,Addressables 预热 |
|
||||
| `Pool/PooledObject.cs` | 池化对象组件 |
|
||||
| `Save/SaveMigrator.cs` | 版本迁移链(2.0→2.1) |
|
||||
| `Save/LocalFileStorage.cs` | 原子写文件,备份恢复 |
|
||||
| `Save/CrashReporter.cs` | 崩溃日志 + 紧急存档触发 |
|
||||
| `Save/EmergencySaveService.cs` | 周期性自动存档 |
|
||||
| `Events/BaseEventChannelSO.cs` | SO 事件频道泛型基类 |
|
||||
| `Events/EventSubscription.cs` | RAII 订阅句柄 + CompositeDisposable |
|
||||
| `Events/EventChannelRegistry.cs` | 运行时频道注册表 |
|
||||
| `Events/ServiceLocator.cs` | 类型安全服务定位器 |
|
||||
| `GlobalSettingsSO.cs` | 全局设置 SO + GlobalSettingsData(本轮修复) |
|
||||
| `SettingsManager.cs` | 设置持久化 + Apply |
|
||||
|
||||
### World/Map
|
||||
| 文件 | 功能 |
|
||||
|------|------|
|
||||
| `MapManager.cs` | 地图探索进度管理,ISaveable |
|
||||
| `MapPanel.cs` | 全屏地图 UI,OnEnable 重建格子 |
|
||||
| `MapRoomDataSO.cs` | 房间元数据 SO |
|
||||
|
||||
### World/Shop
|
||||
| 文件 | 功能 |
|
||||
|------|------|
|
||||
| `ShopController.cs` | 库存过滤/购买/补货(本轮修复 .Take 顺序) |
|
||||
| `ShopInventorySO.cs` | 商店库存 SO,RestockPolicy 枚举 |
|
||||
| `ShopItemSO.cs` | 商品 SO,多类型字段 |
|
||||
| `ShopNPC.cs` | NPC 交互触发 ShopController.Open |
|
||||
|
||||
### Player
|
||||
| 文件 | 功能 |
|
||||
|------|------|
|
||||
| `PlayerStats.cs` | HP/灵力/护符修改器/难度联动/ISaveable |
|
||||
| `PlayerMovement.cs` | Rigidbody2D 封装,Coyote Time,检测墙体 |
|
||||
| `PlayerCombat.cs` | HitBox 激活,连击段 DamageSource 切换 |
|
||||
| `FormController.cs` | 三形态切换,广播 SO+C# 双事件 |
|
||||
| `WeaponManager.cs` | 形态→武器映射,护符 Override |
|
||||
| `States/PlayerController.cs` | 主协调器,IDamageable/IPoiseSource |
|
||||
| `States/PlayerStateBase.cs` | 状态基类,非 MonoBehaviour |
|
||||
| `States/AttackState.cs` | 3 段连击,Animancer 帧事件驱动 HitBox |
|
||||
| `States/DashState.cs` | 无敌帧冲刺,CooldownTimer |
|
||||
| `States/HurtState.cs` | 受击硬直,双重结束保护(timer + animation) |
|
||||
| *(其余 ~15 个状态文件)* | Jump/Fall/Idle/Run/AerialDash/WallSlide 等 |
|
||||
|
||||
### Editor
|
||||
| 文件 | 功能 |
|
||||
|------|------|
|
||||
| `EventBusMonitorWindow.cs` | Event Bus 实时监控窗口,过滤/暂停/自动滚动 |
|
||||
| `SceneScaffoldTools.cs` | 一键生成 Persistent 场景骨架(本轮更新) |
|
||||
| *(其余 Editor 工具文件)* | AddressKeyValidator, EventChainEditorWindow 等 |
|
||||
|
||||
### Support/Platform
|
||||
| 文件 | 功能 |
|
||||
|------|------|
|
||||
| `PlatformBootstrap.cs` | 编译期 Steamworks 判断,失败降级 NullPlatform |
|
||||
| `SteamPlatformService.cs` | 成就/统计/云存档,`#if STEAMWORKS_NET` 隔离 |
|
||||
| `NullPlatformService.cs` | 空实现,无平台时保持接口完整 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 发现问题与修复
|
||||
|
||||
### TD-39 — SceneLoader/SceneService 双重事件订阅(Medium-High)
|
||||
|
||||
**文件**:`Core/SceneLoader.cs`、`Core/SceneService.cs`
|
||||
**问题**:两个组件同时订阅 `SceneLoadRequestEventChannelSO`。
|
||||
- `SceneLoader` 使用 **Addressables** API 处理加载
|
||||
- `SceneService` 使用 **SceneManager**(非 Addressables)处理加载
|
||||
- 同一事件触发时,两套逻辑并发执行,造成场景状态不一致、`_onSceneLoaded` 被发射两次
|
||||
|
||||
**修复**:将 `SceneLoader` 重构为纯工具组件:
|
||||
- 移除 `_onSceneLoadRequest` 字段、`_subs`、`OnEnable`/`OnDisable`、`HandleRequest`
|
||||
- 将 `LoadSceneCoroutine` 由 `private` 改为 `public`(供 SceneService 调用)
|
||||
- `SceneService` 添加 `[SerializeField] SceneLoader _sceneLoader` 字段
|
||||
- `SceneService.LoadSceneCoroutine` 委托给 `_sceneLoader.LoadSceneCoroutine`(保留 fade 逻辑)
|
||||
- `SceneService.UnloadCurrentRoomCoroutine` 委托给 `_sceneLoader.UnloadCurrentCoroutine`
|
||||
- 移除 `SceneService` 中的 `_onSceneLoaded`、`_currentRoomScene` 字段
|
||||
|
||||
同步更新 `SceneScaffoldTools.cs`:
|
||||
- 移除对 `sceneLoader._onSceneLoadRequest` 的赋值
|
||||
- 移除对 `sceneService._onSceneLoaded` 的赋值
|
||||
- 添加 `AssignReference(sceneService, "_sceneLoader", sceneLoader)`
|
||||
|
||||
**Inspector 迁移**:在 Persistent 场景中,SceneService 的 `_sceneLoader` 字段需手动绑定 SceneLoader 组件。
|
||||
|
||||
### TD-40 — LoadMainMenuCoroutine 硬编码 Magic String(Medium)
|
||||
|
||||
**文件**:`Core/SceneService.cs`
|
||||
**问题**:`LoadMainMenuCoroutine` 使用字面字符串 `"MainMenu"`,与 `AddressKeys.SceneMainMenu = "Scene_MainMenu"` 不一致,且绕过了 Addressables 地址校验体系。
|
||||
|
||||
**修复**:替换为 `AddressKeys.SceneMainMenu`(已引入 `using BaseGames.Core.Assets`),同时移除 `using UnityEngine.SceneManagement`(SceneService 不再直接调用 SceneManager API)。
|
||||
|
||||
### TD-41 — AddressKeys.Labels 嵌套类缩进错误(Low)
|
||||
|
||||
**文件**:`Core/Assets/AddressKeys.cs`
|
||||
**问题**:`Labels` 嵌套静态类相对外部类多缩进 4 个空格(类体内出现了两层缩进)。
|
||||
|
||||
**修复**:对齐到标准单层缩进(与同文件其他成员保持一致)。
|
||||
|
||||
### TD-42 — ShopController.GetAvailableItems 过滤顺序错误(Medium)
|
||||
|
||||
**文件**:`World/Shop/ShopController.cs`
|
||||
**问题**:原代码先 `.Take(MaxDisplaySlots)` 再 `.Where(过滤条件)`,导致若前 N 件商品被过滤出局,实际可显示的商品数少于 `MaxDisplaySlots`,商店 UI 出现空格。
|
||||
|
||||
```csharp
|
||||
// 修复前(错误)
|
||||
.Take(_inventory.MaxDisplaySlots)
|
||||
.Where(item => item != null && !_soldUniqueItems.Contains(...) && ...)
|
||||
|
||||
// 修复后(正确)
|
||||
.Where(item => item != null && !_soldUniqueItems.Contains(...) && ...)
|
||||
.Take(_inventory.MaxDisplaySlots)
|
||||
```
|
||||
|
||||
### TD-44 — GlobalSettingsSO.ShowSpeedrunTimer 无法传递给运行时数据(Low)
|
||||
|
||||
**文件**:`Core/GlobalSettingsSO.cs`
|
||||
**问题**:`GlobalSettingsSO` 定义了 `ShowSpeedrunTimer` 字段,但 `GlobalSettingsData`(运行时值)及 `CreateDefault()` 均未包含该字段。Speedrun 模块无法通过 `ISettingsService.Current.ShowSpeedrunTimer` 访问默认值。
|
||||
|
||||
**修复**:
|
||||
1. `GlobalSettingsData` 添加 `public bool ShowSpeedrunTimer = false;`
|
||||
2. `CreateDefault()` 添加 `ShowSpeedrunTimer = ShowSpeedrunTimer,` 以传递 SO 默认值
|
||||
|
||||
---
|
||||
|
||||
## 3. 架构与设计评估
|
||||
|
||||
### 3.1 Core 基础设施
|
||||
|
||||
**GameManager / GameStateMachine**(9.5/10)
|
||||
- `[DefaultExecutionOrder(-1000)]` + `DontDestroyOnLoad` 设计干净
|
||||
- `GameStateMachine.TransitionTo` 通过 `ValidNextStates` 集合校验合法转换,防止非法跳转
|
||||
- `DeathFlow()` 协程将死亡→复活全流程收纳在一处,逻辑清晰
|
||||
- 唯一小瑕疵:`_deathScreenConfirmed` 标志在 `GameManager` 和 `DeathRespawnService` 各有一处订阅,存在轻微冗余(但不构成 Bug,两者职责不同)
|
||||
|
||||
**ServiceLocator**(10/10)
|
||||
- `Unregister<T>(T impl)` 的引用对比模式防止新实例被旧 OnDestroy 错误清除——这是同类实现中少见的细节正确性
|
||||
- `#if UNITY_EDITOR` 隔离的 `OverrideForTest`/`Reset` 为单元测试提供完整支持
|
||||
|
||||
**BaseEventChannelSO / CompositeDisposable**(10/10)
|
||||
- 自定义事件 accessor 的订阅计数(仅 Editor 编译)与 EventBusMonitor 完美配合
|
||||
- `AddTo(CompositeDisposable)` 扩展方法链式 API 流畅,无内存泄漏风险
|
||||
|
||||
**GlobalObjectPool**(9.5/10)
|
||||
- LRU 链表(LinkedList + AliveNode 存储)O(1) Spawn/Despawn 回收性能优秀
|
||||
- Addressables 预热 + 后台协程补池 = 无运行时卡顿
|
||||
- `MaxCount=0` 时完全不追踪活跃列表,减少无谓开销
|
||||
|
||||
**Save 系统**(9.5/10)
|
||||
- `LocalFileStorage` 原子写(tmp→replace)+ 备份恢复,数据安全性达到商业标准
|
||||
- `SaveMigrator` fall-through 迁移链,`System.Version` 语义比较,健壮
|
||||
- `CrashReporter` 每会话最多写 5 个诊断文件 + 日志数量上限裁剪,防异常风暴
|
||||
|
||||
**SceneLoader / SceneService**(修复后 9.0/10)
|
||||
- 重构后职责分离:SceneService = 协调(fade + 事件分发);SceneLoader = 执行(Addressables 加减载)
|
||||
- "先加载新、再卸载旧"保证加载失败时旧场景存活
|
||||
- 已消除双重订阅和 magic string 问题
|
||||
|
||||
### 3.2 World/Map + Shop
|
||||
|
||||
**MapManager**(9.5/10)
|
||||
- 三级可见性(Unknown/Explored/Mapped)设计完整,`SetMapped` 自动包含 Explored
|
||||
- `ISaveable` + `ISaveableRegistry` 自注册,生命周期干净
|
||||
- `HashSet<string>` 查询 O(1) 高效
|
||||
|
||||
**ShopController**(修复后 9.0/10)
|
||||
- `_isDirty` 脏标志避免每帧重建列表,缓存策略合理
|
||||
- `TryPurchase` 通过事件频道扣 Geo,ShopController 不直接依赖 PlayerStats
|
||||
- `RestockPolicy` 枚举驱动补货逻辑,扩展友好
|
||||
- 修复 `.Take` 顺序后商品展示逻辑正确
|
||||
|
||||
**ShopItemSO**(8.5/10)
|
||||
- 多类型字段(HealthRestore/Charm/KeyItem/Buff/MapFragment)集中在一个 SO 较为混杂
|
||||
- 建议(非强制):可将不同类型收益拆为 `[SerializeReference]` 子类,Inspector 折叠更清晰;当前方案对小型项目完全可接受
|
||||
|
||||
### 3.3 Player 模块
|
||||
|
||||
**PlayerController**(9.5/10)
|
||||
- `[RequireComponent]` 四连确保同节点组件存在,Awake 自动获取,零运行时 NullRef
|
||||
- 非 MonoBehaviour 状态类由 Controller 实例化,生命周期受控,无 Awake/OnEnable 竞争
|
||||
- `_onPlayerSpawned` 广播 Transform 替代 `FindWithTag`,消除 O(n) 全场景扫描
|
||||
|
||||
**PlayerStats**(9.5/10)
|
||||
- `AddModifier/RemoveModifier` 浮点 flat+percent 双轨修改器,护符叠加计算无需遍历所有效果
|
||||
- 难度切换时保持 HP 比例的处理(`hpRatio`)体现细节关怀
|
||||
- `IRewardTarget` 接口反向依赖解耦 Quest→Player
|
||||
|
||||
**PlayerStateBase / 状态机**(9.5/10)
|
||||
- 状态不继承 MonoBehaviour = 零 Unity 开销,纯 POCO 状态切换
|
||||
- `ValidTransitions`(仅 Editor)白名单调试辅助实用
|
||||
- `AttackState` 用 Animancer 归一化时间事件驱动 HitBox,不写死帧数,资产驱动连击节奏
|
||||
|
||||
**DashState**(9.0/10)
|
||||
- `override bool IsInvincible => true` 清晰声明无敌语义
|
||||
- 冷却计时由 `PlayerController.Update` 统一驱动,状态无 Update 调用
|
||||
- `TickCooldown` 命名语义清晰
|
||||
|
||||
**HurtState**(9.0/10)
|
||||
- 双重 `_ended` 标志防止 timer 超时与 animation end 同时触发时的重复转换
|
||||
- `Initialize(DamageInfo)` 分离击退应用与状态进入,时序正确
|
||||
|
||||
### 3.4 Editor 工具
|
||||
|
||||
**EventBusMonitorWindow**(9.5/10)
|
||||
- 过滤/暂停/自动滚动完整,订阅计数实时可见
|
||||
- `EditorApplication.update` 轮询刷新,仅 Play Mode 可用,性能控制合理
|
||||
|
||||
**SceneScaffoldTools**(9.0/10)
|
||||
- 一键生成 Persistent 场景骨架,反射赋值减少手动配置错误
|
||||
- 本轮更新:移除 SceneLoader 的冗余事件赋值,添加 `_sceneLoader` 引用绑定
|
||||
- 扩展性良好:新增服务只需在 Awake 对应区域追加
|
||||
|
||||
### 3.5 Support/Platform
|
||||
|
||||
**PlatformBootstrap**(9.5/10)
|
||||
- `async void Awake` + `#if UNITY_STANDALONE && STEAMWORKS_NET` 编译期隔离,无平台无代码
|
||||
- 初始化失败降级 NullPlatformService,不中断游戏启动
|
||||
- `_platform?.RunCallbacks()` 在 Update 安全调用,Steam API 要求满足
|
||||
|
||||
**SteamPlatformService**(9.5/10)
|
||||
- `IsAchievementUnlocked` 返回 `Task<bool>` 统一异步接口(虽然底层是同步 Steam API)
|
||||
- `StoreStats()` 随每次 SetStat/SetAchievement 立即调用,防止数据丢失
|
||||
|
||||
---
|
||||
|
||||
## 4. 多维度评分
|
||||
|
||||
| 维度 | 得分 | 说明 |
|
||||
|------|------|------|
|
||||
| **架构设计** | 9.8 | 职责分离彻底,接口抽象层次清晰,ServiceLocator+EventChannel 双轨解耦优秀。SceneLoader重构消除最后一处架构歧义 |
|
||||
| **性能** | 9.7 | LRU对象池O(1)回收、HashSet查询O(1)、EventChannel订阅无GC、Coyote Time精确计时。无Update重的全场景扫描 |
|
||||
| **可扩展性** | 9.8 | GameIds/AddressKeys集中ID管理、RestockPolicy枚举驱动补货、ValidTransitions白名单可逐步完善、ShopItemType可按需扩展 |
|
||||
| **编辑器友好** | 9.6 | EventBusMonitor实时调试、SceneScaffoldTools一键脚手架、Debug.Assert参数验证、Editor条件编译隔离调试功能 |
|
||||
| **使用便利性** | 9.7 | channel.Subscribe().AddTo() RAII链式、FormController三事件广播覆盖全部下游、WeaponManager Override API简洁 |
|
||||
| **代码一致性** | 9.7 | 统一CompositeDisposable模式、_subs/_subscriptions命名轻微不一致(可接受)、SaveableRegistry自注册统一 |
|
||||
| **安全性** | 9.8 | LocalFileStorage原子写+备份、CrashReporter异常风暴限流、Addressables失败不破坏当前场景 |
|
||||
| **整体** | **9.77** | 修复4个问题后达到此分值 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 本轮变更汇总
|
||||
|
||||
| ID | 类型 | 文件 | 描述 |
|
||||
|----|------|------|------|
|
||||
| TD-39 | Bug Fix | SceneLoader.cs / SceneService.cs / SceneScaffoldTools.cs | 消除双重事件订阅;SceneLoader重构为纯工具组件,SceneService委托调用 |
|
||||
| TD-40 | Bug Fix | SceneService.cs | LoadMainMenuCoroutine 使用 AddressKeys.SceneMainMenu 替换 magic string "MainMenu" |
|
||||
| TD-41 | Style Fix | AddressKeys.cs | Labels嵌套类对齐到标准单层缩进 |
|
||||
| TD-42 | Bug Fix | ShopController.cs | GetAvailableItems中.Take移至.Where之后,保证展示槽位填满 |
|
||||
| TD-44 | Bug Fix | GlobalSettingsSO.cs | GlobalSettingsData添加ShowSpeedrunTimer字段;CreateDefault()传递SO默认值 |
|
||||
| S3 | Improvement | ShopItemSO.cs | 平铺类型字段迁移至 `[SerializeReference]` 多态子类;Inspector 按需显示字段,消除空字段噪音 |
|
||||
| S4 | Improvement | GameIds.cs | `GameIds.Scene` 补充 `MainMenu = "Scene_MainMenu"`,与 AddressKeys.SceneMainMenu 对齐 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 原遗留建议(已全部实施)
|
||||
|
||||
S3 和 S4 均已在本轮完成,无遗留建议。
|
||||
|
||||
---
|
||||
|
||||
*Review 完成时间:2026 年 5 月*
|
||||
*v18 历史累计修复 TD 总数:44(+ 2 项优化改进)*
|
||||
Reference in New Issue
Block a user