Files
zeling_v2/Docs/Review/Minimap_Review_Round8_Independent.md
Joywayer f74d7f1877 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.
2026-05-25 23:15:12 +08:00

147 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 小地图独立审查报告 — 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。
修复方向已在第三章逐项给出,等待执行授权。