Add independent review reports for Minimap system (Rounds 8, 9, and 26)

- Round 8 report highlights improvements in architecture, editor usability, and data robustness, with a total score of 80/100.
- Round 9 report focuses on editor extension capabilities, identifying issues with room data indexing and layout editing, resulting in a score of 76/100.
- Round 26 report evaluates the system against commercial standards, noting new issues and confirming previous fixes, with a score of 95.8/100.
This commit is contained in:
2026-05-25 23:15:12 +08:00
parent e2bc324905
commit f74d7f1877
53 changed files with 6825 additions and 270 deletions

View File

@@ -0,0 +1,146 @@
# 小地图独立审查报告 — Round 8
> **审查日期**:第 8 轮独立审查前序Round 1~7Round 7 评分 82/100 + 修复后预估 ~88
> **对标基准**:成熟商业 Metroidvania含空洞骑士级别的探索 / 标注 / 区域切换 / 编辑器工作流)
> **范围**`Assets/_Game/Scripts/World/Map/**` + 相关 Save / Editor / ServiceLocator
> **审查方式**:完全独立重读全部 17 个文件,不预设结论;对 Round 7 修复项做交叉验证
---
## 一、综合评分(八维)
| 维度 | 评分 | 较 Round 7 (修复后) |
|------|------|--------|
| 架构与解耦 (15%) | 14 / 15 | ↑(共享空间索引 + DB 事件接口齐备)|
| 编辑器扩展易用性 (15%) | 13 / 15 | ↑(双窗口 + Validate 已成熟)|
| 数据/存档健壮性 (10%) | 8 / 10 | ↑LastRegionId、CreatePin 校验落地)|
| 运行时性能 (15%) | 14 / 15 | ↑空间索引复用、PinsVersion 脏检查)|
| 可扩展性 (10%) | 7 / 10 | =RegionSO/进度 UI 仍缺)|
| 视觉与表现层 (10%) | 8 / 10 | =(区域名提示已就位,但 Pin 限于全屏)|
| 输入与平台 (10%) | 6 / 10 | =Input System 迁移仍未启动)|
| 文档与测试 (15%) | 10 / 15 | ↑(七轮报告体系完善,仍缺 PlayMode 集成测试)|
| **合计** | **80 / 100** | — |
> 说明本轮在更高标尺下重新刻度。Round 7 的"修复后预估 ~88"是基于自身基线Round 8 引入 **NotifyDatabaseChanged 入口空挂** 等结构性新发现,整体分数回落到 80。修复完 P0/P1 后预计 88~91。
---
## 二、Round 7 修复交叉验证 ✅
| Round 7 编号 | 内容 | Round 8 验证 |
|------|------|------|
| N1 | MapPanel 在 LateUpdate 中 RenderPins + 监听 OnDatabaseChanged | ✅ MapPanel.cs:124/136 已落地 |
| N2 | IMapService 增加 OnDatabaseChanged 事件 | ✅ 接口与实现齐备 |
| N3 | 空间索引下沉到 MapDatabaseSO | ✅ `GetRoomIdAtCell` + Invalidate 已就位HUD/Tracker 均使用共享索引 |
| N4 | CreatePin 校验 roomId / clamp 归一化 | ✅ MapPin.cs:60~94 |
| N5 | SaveData.LastRegionId + OnLoad 恢复 | ✅ MapManager.cs:60/67 |
| N6 | MinimapHUD step③ 跳过新增格 | ✅ `_newlyAddedBuffer` 去重 |
| N7 | MapInputHandler 命名空间冲突 | ✅ `UnityEngine.Input.GetAxisRaw` |
| N8 | `_worldUnitsPerCell` `[Min(0.01f)]` | ✅ MapPlayerTracker.cs |
**结论**Round 7 所有修复均稳定在仓库中,未发生回归。
---
## 三、本轮新发现问题
### P0必修
#### R8-N1`IMapService.NotifyDatabaseChanged()` 全仓零调用 — API 空挂
- **位置**`Assets/_Game/Scripts/World/Map/MapManager.cs:127`,全文检索 `NotifyDatabaseChanged` 仅出现于"声明"与"实现"两处。
- **后果**Round 7 N2 修复在接口层补齐了 DB 热更新通知通道,但**调用端缺失**。
- 编辑器中通过 Inspector 修改某个 `MapRoomDataSO``GridPosition`、新增/删除 `MapDatabaseSO.AllRooms` 数组元素,**运行时不会触发**任何 UI 重建。
- MapPanel/MinimapHUD 继续渲染过期布局,直到玩家进入新房间或重开面板。
- **修复**:在 `MapDatabaseSO.OnValidate` 中(`#if UNITY_EDITOR && Application.isPlaying`)回调 `ServiceLocator.GetOrDefault<IMapService>()?.NotifyDatabaseChanged()`;同时让 `MapRoomDataSO.OnValidate` 也对所属 Database 反向通知(或在数据库一侧做差量比较)。可选:暴露给编辑器窗口的 "Apply" 按钮显式调用。
#### R8-N2MinimapHUD 完全不渲染玩家 Pin
- **位置**`MinimapHUD.cs` 未引用 `IPinService`,仅 MapPanel 渲染图钉。
- **后果**:玩家在全屏地图上标注的图钉**无法在角落小地图上可见**,违反主流 Metroidvania 体验HUD 应给出最近的目标提示,避免反复打开大地图)。
- **修复**:在 MinimapHUD 中订阅 `IPinService.PinsVersion`,在可视范围内挑选最近的 N 个 Pin屏幕外用边缘箭头表示。最小化实现仅渲染当前视野单元格范围内的 Pin。
### P1建议修
#### R8-N3MapManager.Awake 重复实例 Destroy 后 OnEnable 仍会执行
- **位置**`MapManager.cs:36-46`
- **分析**`Destroy(gameObject)` 在 Awake 中调用,但 Unity 生命周期中 **Awake → OnEnable** 同帧仍会触发OnEnable 会订阅事件 + 注册 ISaveable本帧末才被销毁后 OnDisable 取消订阅。期间若发生 OnSave极少但存在会写入"将被销毁"的实例。
- **修复**:增加 `private bool _isDuplicate;` 在 Awake 中标记OnEnable/OnDisable 提前 return。
#### R8-N4MapPlayerTracker 重复实例只 return 不 Destroy
- **位置**`MapPlayerTracker.Awake`(与 MapManager 模式不一致)。
- **后果**:场景中若意外存在两个 MapPlayerTracker第二个不会被销毁仍消耗 LateUpdate虽然不注册服务`_database` 依然在 Start 中赋值)。
- **修复**:与 MapManager 对齐 — 检测到已注册时 `Destroy(gameObject); return;`
#### R8-N5服务获取没有"懒加载/重试"机制
- **位置**`MapPanel.OnEnable`(行 79~80一次性获取 `_playerProvider` / `_pinService` / `_mapSvc`
- **后果**:若 MapPanel 比 MapPinManager / MapManager 早 OnEnable场景启动顺序未严格保证时后续不会再尝试。用户必须关闭再打开面板。
- **修复**:在 LateUpdate 起始位置增加 lazy 解析:
```csharp
if (_pinService == null) _pinService = ServiceLocator.GetOrDefault<IPinService>();
```
或采用"DefaultExecutionOrder + GameServiceRegistrar"统一保证启动顺序(已部分落实,但 Panel 是 UI 子物体,启动顺序更脆弱)。
#### R8-N6MapManager.OnLoad 不广播 EVT_MapUpdated / EVT_RegionChanged
- **位置**`MapManager.OnLoad`(行 63~69
- **后果**:若读档时 MapPanel 已打开(例如从设置菜单读档),探索状态、区域名提示不会即时刷新。`RefreshAllCells` 仅在 OnEnable 触发。
- **修复**:在 OnLoad 末尾通过 `IMapService.OnDatabaseChanged` 通知 UI 全量重建(语义略宽,但成本可接受);或为 `IMapService` 增加 `OnSaveLoaded` 专用事件。
### P2可选改进
#### R8-N7`MapPin.OnSave` 直接共享 `_pins` 引用,与 MapManager 拷贝模式不一致
- **位置**`MapPin.cs:98`
- **风险**:若未来 SaveSystem 引入异步序列化或重试,运行时 `CreatePin/RemovePin` 修改集合可能与序列化冲突。
- **修复**`data.Map.Pins = new List<MapPin>(_pins);`(与 MapManager 的 `new HashSet<>` 风格一致)。
#### R8-N8`MapManager.GetExplorationProgress` 缓存 `_totalRoomCount` 但无 `OnDatabaseChanged` 失效钩子
- **位置**`MapManager.NotifyDatabaseChanged()` 内已 reset `_totalRoomCount = -1`,但前提是有人调用 NotifyDatabaseChanged见 R8-N1。需要联动确认。
- **修复**:随 R8-N1 一并解决。
#### R8-N9MapPin Save 字段命名考虑前向兼容
- `MapPin` 是同时充当**运行时模型 + 存档结构**的 `[Serializable] class`。若未来字段重构(例如增加 `IsCompleted` 标志),旧存档反序列化可能在 BinaryFormatter 下损坏。
- **建议**:要么用 JSON 存档(已部分使用?需确认 SaveSystem 序列化器),要么显式提供 `[OnDeserialized]` migrations。
#### R8-N10MapInputHandler 仍使用 `UnityEngine.Input.GetAxisRaw`(旧 Input Manager
- 与项目其他模块(推测使用新 Input System不一致。
- **建议**:迁移到 `IInputService`项目内已有的抽象。Round 7 已标记,本轮再次确认为待办。
### P3长期/暂可不修)
- **R8-D1**RegionSO 配置化区域颜色、地图碎片关联、Boss 标记)仍未启动,目前 RegionId 仅是字符串。
- **R8-D2**:探索进度 UI`GetExplorationProgress` API 已存在)未在面板上呈现。
- **R8-D3**:手柄缩放 / 平移热键尚未对齐 PC + Gamepad 双输入。
- **R8-D4**PlayMode 集成测试(房间发现 → 存档 → 读档 → UI 同步)尚未编写。
- **R8-D5**`Docs/Design/MinimapDesignSpec.md` 设计文档(约束格子语义、颜色、图层)仍未补齐。
---
## 四、设计亮点(继续保留)
1. **架构**`ServiceLocator + ScriptableObject + EventChannel` 三件套清晰分层;`IMapService / IPinService / IPlayerPositionProvider` 抽象到位。
2. **共享空间索引**`MapDatabaseSO.GetRoomIdAtCell` 统一了 HUD 与 Tracker 的查询路径,避免双份索引内存与同步开销。
3. **编辑器扩展**`MapLayoutEditorWindow`(俯视图拖拽)+ `MapRoomDataEditor`Scene 句柄两点确定矩形)+ `MapDatabaseEditor`(一键 Validate形成完整作业流。
4. **存档健壮性**MapManager 复制 HashSet 防引用泄漏LastRegionId 恢复消除"读档首次进房误触发区域 Toast"。
5. **性能保护**CellPool、PinsVersion 脏检查、空间索引懒构建、Mathf.Clamp01 防御。
---
## 五、推荐修复优先级与预期得分
| 优先级 | 项目 | 预计提升 |
|-----|-----|-----|
| P0 | R8-N1 NotifyDatabaseChanged 接入 + R8-N2 MinimapHUD Pin 渲染 | +5 |
| P1 | R8-N3 / N4单例对齐+ N5lazy 解析)+ N6OnLoad 广播)| +3 |
| P2 | R8-N7 / N8 / N9 | +1 |
| P3 | R8-D1~D5 | +3 |
完成 P0+P1 后整体预期 **88/100**;进一步完成 P2 后 **89/100**P3 全部就位(含 RegionSO + 探索进度 UI + 设计文档)后可冲击 **92~93/100**。
---
## 六、与 Round 7 的差异说明
Round 7 报告以"接口补齐 + 局部 NRE 防御"视角给出修复后 88 的预估,但**未审视已补 API 的调用闭环**。Round 8 在更严标尺下:
- 发现 `NotifyDatabaseChanged` 是"半完工 API"(声明 + 实现存在,但无调用方),列为 P0。
- 发现 MinimapHUD 不渲染 Pin 的功能盲区,列为 P0属于 Round 1~7 一直未提及的功能性缺口)。
- 重新审视单例守护、服务懒解析、读档广播这三处稳健性细节,列为 P1。
修复方向已在第三章逐项给出,等待执行授权。