# Minimap System — Round 19 Independent Review ## 评审范围 全部 23 个源文件(4 接口 + 15 运行时 + 4 编辑器),在 R18 全部修复已落地的基础上进行全面重新审查。 --- ## R18 修复确认 | 编号 | 描述 | 验证状态 | |------|------|----------| | R18-N1a | `MapInputHandler.Update` `rangeX/rangeY` 乘以 `_zoom` | ✅ 第 79–80 行 | | R18-N1b | `MapPanel.CenterOnCurrentRoom` 从 `_roomContainer.localScale.x` 读 zoom,修正 rangeX/rangeY 及 cellX/cellY | ✅ 第 377–384 行 | | R18-N2 | `_noResultStyle` 缓存为字段,在 `EnsureLabelStyles()` 首次调用时初始化 | ✅ 第 435–445 行 | | R18-N3 | `MinimapHUD._onMapUpdated` 改 `[HideInInspector]`,移除订阅和 `OnMapUpdated` 方法 | ✅ 第 36–39 行 | --- ## R19 全方面评分 | 维度 | 权重 | 分数 | 评分依据 | |------|------|------|----------| | **架构解耦** | 20% | **95** | 4 接口全 ServiceLocator;C# 事件单向流;无循环依赖;Awake/OnDestroy 长期订阅模式;MapServiceExtensions 集中无状态查询逻辑 | | **功能完整性** | 20% | **93** | 三级可见性、地图碎片解锁动画、Pin/传送/区域名本地化、探索进度显示、雾效覆盖层、存档全覆盖 | | **性能** | 15% | **93** | 五对象池(cell×2 / pin×2 / exit×1);O(viewRadius²) 剔除;PinsVersion 脏检查;_servicesReady 短路;MapDatabaseSO 双重 O(1) 索引;R18-N1 键盘平移已修正;N1c 潜在风险(见下方) | | **编辑器工具** | 15% | **94** | 布局编辑器可视化+拖拽+搜索+图例+Undo;MapDatabaseEditor 自动验证;MapRoomDataEditor SceneGUI 双角控制点;AssetPostprocessor 自动注册+null 清理;R18-N2 GUIStyle 缓存到位 | | **可扩展性** | 10% | **95** | RoomType[Flags] 枚举可任意叠加新类型;SO 驱动(MapDatabaseSO / MapPinConfigSO);接口易替换;MapGridConstants 统一常量管理 | | **代码质量** | 10% | **95** | 全面注释;命名规范;3 处 ISaveable 防御性拷贝对称;try/finally 保护 GUI.matrix;[FormerlySerializedAs] 迁移;[HideInInspector] 废弃兼容;_noResultStyle 已缓存 | | **玩家体验设计** | 10% | **92** | 三级可见性;传送点快速旅行;循环缩放视野;Play Mode 编辑器叠加;区域名淡入动画;R18-N1 平移手感已修正 | **综合评分:93.8 / 100**(与 R18 修复后预测值完全吻合) --- ## 架构层深度审查 ### 接口与服务完整矩阵 | 接口 | 实现类 | ServiceLocator 注册 | ISaveable | 备注 | |------|--------|---------------------|-----------|------| | `IMapService` | `MapManager` | Awake(重复实例保护) | ✅ | 防御拷贝 + OnLoad 广播 OnExplorationChanged | | `IPinService` | `MapPinManager` | Awake | ✅ | PinsVersion 版本号驱动脏检查 | | `IPlayerPositionProvider` | `MapPlayerTracker` | Awake(重复实例保护) | ❌(不需要) | LateUpdate O(1) 空间索引 + NormalizedPositionInRoom 每帧插值 | | `ITeleportService` | `TeleportService` | Awake | ✅ | 防御拷贝;事件通道驱动场景过渡 | ### 数据流方向(单向) ``` MapManager ──── OnRoomEntered ────────▶ OnExplorationChanged (C# event) ▶ MapPanel.OnExplorationChanged ▶ MinimapHUD.OnExplorationChanged ▶ MapProgressDisplay.Refresh MapManager ──── SetMapped / SetMappedBatch ──▶ OnRoomMapped (C# event) ▶ MapPanel.OnRoomMappedAnim (发现动画) MapManager ──── _onRegionChanged.Raise ──────▶ RegionNameDisplay ▶ MapProgressDisplay.OnRegionChanged MapPlayerTracker ── OnRoomChanged (C# event) ▶ MinimapHUD.OnRoomChanged → RefreshView ─ NormalizedPositionInRoom ▶ MapPanel.LateUpdate(脏检查) ▶ MinimapHUD.LateUpdate.UpdatePlayerDot ``` > `_onMapUpdated` StringEventChannel 仍在 MapManager 的 3 处发射,但地图 UI 已全部移除订阅(MapPanel R12-N8、MinimapHUD R18-N3)。该通道保留以供非 Map UI 的外部系统订阅,属于已知设计状态。 ### 对象池完整性 | 池 | 类型 | 宿主 | 入池时机 | 出池时机 | |----|------|------|----------|----------| | `_cellPool` | MapRoomCellUI | MapPanel | RebuildAll / OnDestroy | BuildGrid | | `_pinPool` | Image (Pin) | MapPanel | ClearPins / OnDestroy | RenderPins | | `_exitPool` | Image (Exit) | MapPanel | ClearExits / OnDestroy | DrawExits | | `_cellPool` | MapRoomCellUI | MinimapHUD | RefreshView 裁剪过期 / OnDestroy | RefreshView 新增 | | `_pinPool` | Image (Pin) | MinimapHUD | ClearPins / OnDestroy | RebuildPins | 所有池均正确实现:入池时 `SetActive(false)` + `Push`,出池时 `Pop` + `SetActive(true)` 或 `Instantiate` 兜底。 ### ISaveable 对称性审查 | 类 | `OnSave` 防御拷贝 | `OnLoad` 防御拷贝 | 读档后事件广播 | |----|--------------------|---------------------|----------------| | `MapManager` | `new HashSet<>(x2)` | `new HashSet<>(x2)` | `OnExplorationChanged?.Invoke()` | | `MapPinManager` | `new List<>(_pins)` | `new List<>(data.Pins)` | `PinsVersion++` | | `TeleportService` | `new HashSet<>(_unlockedRoomIds)` | Clear + foreach Add | — | 三处均对称,无共享引用风险。 ### 性能关键路径 | 路径 | 复杂度 | 机制 | |------|--------|------| | `MinimapHUD.RefreshView` 新格子查询 | O(viewRadius²) | `MapDatabaseSO.GetRoomIdAtCell` 共享空间索引 | | `MinimapHUD.LateUpdate.UpdatePlayerDot` | O(1) | roomId + NormPos 双字段脏检查 | | `MapPanel.LateUpdate` 服务查询 | O(1) 短路 | `_servicesReady` bool 门控 | | `MapPanel.LateUpdate.RenderPins` | O(1) 检查 | PinsVersion 脏检查 | | `MapManager.GetRoomsByRegion` | O(1) | `_regionCache` 懒加载字典 | | `MapDatabaseSO.GetRoom` | O(1) | `_index` 字典哈希 | | `MapDatabaseSO.GetRoomIdAtCell` | O(1) | `_cellToRoom` 空间哈希 | | `MapPinConfigSO.GetSprite` | O(1) | `_cache` 惰性字典 | | `RegionNameDisplay / MapProgressDisplay.ResolveDisplayName` | O(1) | 预建 `_regionDict` | | `MapLayoutEditorWindow.EnsureLabelStyles` | O(1) | `_cachedZoomForStyle` 脏检查 | | `MapLayoutEditorWindow._noResultStyle` | O(1) | 首次初始化后不重建 | --- ## R19 新发现问题 ### N1(低,配置健壮性):`_roomContainer` 与 `_zoomTarget` 为两个独立引用,配置不一致时静默偏差 **涉及文件**:`MapInputHandler.cs`(第 21 行)、`MapPanel.cs`(第 23 行) **现状**: - `MapInputHandler._zoomTarget`:`OnScroll` 写入 `_zoomTarget.localScale`,`Update` 使用 `_zoom` 字段 - `MapPanel._roomContainer`:`CenterOnCurrentRoom` 读取 `_roomContainer.localScale.x` 两者仅在 `_zoomTarget == _roomContainer`(即 Prefab 中两个引用指向同一 GameObject)时保持同步。若开发者在 Prefab 中错误配置,`CenterOnCurrentRoom` 将读到错误的缩放系数。当前无运行时断言或警告。 **影响**:误配置后打开地图并缩放,再按"居中"快捷键(MapCenterEvent),定位会偏移但无错误提示。 **修复建议**: ```csharp // MapInputHandler.cs — OnEnable / OnScroll 中向 MapPanel 暴露当前缩放值 // 方案A:MapPanel 增加 public 属性 public float CurrentZoom => _roomContainer != null ? _roomContainer.localScale.x : 1f; // MapInputHandler.OnScroll 改从 _panel.CurrentZoom 读,而非独立维护 _zoom(消除两份状态) // 方案B(最小改动):Awake 中断言 _zoomTarget == _scrollRect.content ``` --- ### N2(低,正确性边缘):`MapPanel.OnRoomMappedAnim` 协程在目标格子被回收后继续运行 **文件**:`MapPanel.cs` 第 198–202 行 **现状**: ```csharp protected virtual void OnRoomMappedAnim(string roomId) { if (_cells.TryGetValue(roomId, out var cell) && cell != null) StartCoroutine(cell.PlayRevealAnim(_revealFlashColor, _revealDuration)); } ``` `StartCoroutine` 挂在 `MapPanel`(this)上,协程生命周期由 MapPanel 管理。若在动画播放期间触发 `RebuildAll`(`OnDatabaseChanged`),目标 `cell` 会被入池(`SetActive(false)`)。协程继续运行并向已入池的 cell 的 `_bg.color` 写入,直到 `_revealDuration` 结束。下次该 cell 出池并调用 `Setup → SetVisibility` 时颜色会被正确覆盖,因此视觉影响自愈。 **严重程度**:极低(数据库热更极少发生,动画持续 0.4 s)。但在频繁使用编辑器热更的开发流程中可能产生轻微闪烁。 **修复建议**: ```csharp // MapPanel 添加字段 private readonly Dictionary _revealCoroutines = new(); // OnRoomMappedAnim 中: if (_revealCoroutines.TryGetValue(roomId, out var old) && old != null) StopCoroutine(old); _revealCoroutines[roomId] = StartCoroutine(cell.PlayRevealAnim(...)); // RebuildAll 中清理: foreach (var c in _revealCoroutines.Values) if (c != null) StopCoroutine(c); _revealCoroutines.Clear(); ``` --- ### N3(信息,架构说明):`MapManager._onMapUpdated` 通道现在在地图 UI 内无订阅者 **文件**:`MapManager.cs` 第 85、107、122 行 经 R12-N8(MapPanel)和 R18-N3(MinimapHUD)后,地图 UI 不再订阅 `_onMapUpdated` StringEventChannelSO。MapManager 仍在 `OnRoomEntered`、`SetMapped`、`SetMappedBatch` 三处调用 `Raise()`。 **这不是 Bug**:SO 事件通道天然支持外部系统订阅(如 Achievement 系统、音效触发器);保留 Raise() 符合开放设计原则。 **仅建议添加文档注释**: ```csharp [Header("Event Channels")] [Tooltip("房间被标记时广播(Explored/Mapped),供地图外部系统订阅(地图 UI 已改用 C# 事件)。")] [SerializeField] private StringEventChannelSO _onMapUpdated; ``` --- ## 编辑器工具覆盖面(策划/开发视角) | 工具 | 目标用户 | 核心能力 | |------|----------|----------| | **MapLayoutEditorWindow** | 策划 + 开发 | 可视化房间布局;滚轮缩放;中键/Alt 平移;左键选中;拖拽编辑房间坐标(Undo);实时重叠检测红色警示;区域配色;搜索高亮(无结果提示);图例;Play Mode 玩家点叠加;出口连线(>12px 时) | | **MapDatabaseEditor** | 开发 | 数据库统计(房间数/出口数);一键验证;Inspector 内嵌"打开布局编辑器";可折叠房间列表+Ping;错误行红色高亮 | | **MapRoomDataEditor** | 关卡设计 + 开发 | SceneGUI 双角控制点可视化;拖拽吸附到整格;防反转保护;居中 SceneView 快捷按钮;HelpBox 坐标系说明 | | **MapRoomAutoRegister** | 开发 | AssetPostprocessor 自动注册新 SO 到默认 Database;null 清理;`IsDefault` 优先级;可通过 EditorPrefs 禁用 | --- ## 总结 经过 19 轮迭代,小地图系统已达到成熟的生产级 2D 探索类游戏标准: ### 强项 - **接口完整**:4 接口全 ServiceLocator,UI 层无具体实现依赖 - **存档正确**:3 处 ISaveable 防御拷贝对称,读档后事件广播完整 - **性能到位**:5 对象池 + O(viewRadius²) + 多级脏检查 + 双重 O(1) 索引 - **编辑器成熟**:拖拽布局 + 搜索 + Undo + 验证 + SceneGUI 一体化,策划友好 - **事件方向清晰**:所有 UI 更新均由 C# 事件(`OnExplorationChanged`)单向驱动,无双重刷新 ### R19 新发现(3 项,全为低优先级) - **N1**:`_zoomTarget` vs `_roomContainer` 双引用无配置一致性断言(配置层风险) - **N2**:`OnRoomMappedAnim` 协程可能写入已回收的格子(视觉自愈,影响微小) - **N3**:`_onMapUpdated` 通道在地图 UI 内零订阅(信息提示,非缺陷) ### 评分历史 | 轮次 | 评分 | 主要改进 | |------|------|----------| | R14 | 92.3 | ISaveable 签名修复,TeleportStation 枚举值 | | R15 | 91.3 | DrawRoomBadge 补 TeleportStation,MapProgressDisplay 区域名 | | R16 | 92.5 | DrawRoomBadge 优先级,MinimapHUD 4 Sprite + ChooseIcon | | R17 | 93.0 | 死代码清理,搜索 UX 改善(alpha + 无结果提示) | | R18 | 93.8 | 键盘平移范围修正,GUIStyle 缓存,MinimapHUD 双重刷新消除 | | **R19** | **93.8** | 全部 R18 修复确认到位,识别 3 项新低优先级问题 |