- 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.
147 lines
9.9 KiB
Markdown
147 lines
9.9 KiB
Markdown
# 小地图独立审查报告 — Round 8
|
||
|
||
> **审查日期**:第 8 轮独立审查(前序:Round 1~7,Round 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-N2:MinimapHUD 完全不渲染玩家 Pin
|
||
- **位置**:`MinimapHUD.cs` 未引用 `IPinService`,仅 MapPanel 渲染图钉。
|
||
- **后果**:玩家在全屏地图上标注的图钉**无法在角落小地图上可见**,违反主流 Metroidvania 体验(HUD 应给出最近的目标提示,避免反复打开大地图)。
|
||
- **修复**:在 MinimapHUD 中订阅 `IPinService.PinsVersion`,在可视范围内挑选最近的 N 个 Pin(屏幕外用边缘箭头表示)。最小化实现:仅渲染当前视野单元格范围内的 Pin。
|
||
|
||
### P1(建议修)
|
||
|
||
#### R8-N3:MapManager.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-N4:MapPlayerTracker 重复实例只 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-N6:MapManager.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-N9:MapPin Save 字段命名考虑前向兼容
|
||
- `MapPin` 是同时充当**运行时模型 + 存档结构**的 `[Serializable] class`。若未来字段重构(例如增加 `IsCompleted` 标志),旧存档反序列化可能在 BinaryFormatter 下损坏。
|
||
- **建议**:要么用 JSON 存档(已部分使用?需确认 SaveSystem 序列化器),要么显式提供 `[OnDeserialized]` migrations。
|
||
|
||
#### R8-N10:MapInputHandler 仍使用 `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(单例对齐)+ N5(lazy 解析)+ N6(OnLoad 广播)| +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。
|
||
|
||
修复方向已在第三章逐项给出,等待执行授权。
|