# 小地图独立审查报告 — 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()?.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(); ``` 或采用"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(_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。 修复方向已在第三章逐项给出,等待执行授权。