# Minimap System — Round 18 Independent Review ## 评审范围 全部 23 个源文件(4 接口 + 15 运行时 + 4 编辑器),在 R17 全部修复已落地的基础上进行全面重新审查。 --- ## R17 修复确认 | 编号 | 描述 | 验证状态 | |------|------|----------| | R17-N1 | 删除 `MapLayoutEditorWindow._cachedErrorRoomIds` 死代码字段 | ✅ 已确认,字段已不存在 | | R17-N2 | 搜索活跃时非匹配房间 alpha 0.28 → 0.08 | ✅ 已确认,第 335 行 | | R17-N3 | 搜索无结果时居中显示提示文本 | ✅ 已确认,第 363–373 行 | --- ## R18 全方面评分(修复前) | 维度 | 权重 | 分数 | 说明 | |------|------|------|------| | 架构解耦 | 20% | 95 | 四接口全 ServiceLocator;事件单向流;无循环依赖;服务订阅 Awake 长期持有不随 OnEnable/Disable 失效 | | 功能完整性 | 20% | 92 | 三级可见性、Pin、传送、本地化区域名、地图碎片解锁动画、存档一致;全部覆盖 | | 性能 | 15% | 91 | 三池(cell/pin/exit)+ O(viewRadius²) 剔除 + PinsVersion 脏检查 + _servicesReady 短路;N1 键盘平移未乘缩放;N3 MinimapHUD 双重刷新 | | 编辑器工具 | 15% | 93 | 布局编辑器:拖拽/搜索/图例/Play Mode 叠加/Undo-Redo/AssetPostprocessor 自动注册;R17 UX 修复有效;N2 每帧重建 noResultStyle | | 可扩展性 | 10% | 95 | RoomType[Flags] 可任意叠加新类型;SO 驱动;接口易替换;MapPinConfigSO 集中 Pin 配置 | | 代码质量 | 10% | 93 | 命名规范、注释充实、防御拷贝三处对称;N2 GUIStyle 每 OnGUI 帧重建 | | 玩家体验设计 | 10% | 88 | 键盘平移范围未乘 `_zoom`,缩放后平移速度感知偏差 | **综合(修复前):92.4 / 100** --- ## R18 新发现问题 ### N1(中,正确性):`MapInputHandler` 键盘平移范围未乘缩放系数 **文件**:`Assets/_Game/Scripts/World/Map/MapInputHandler.cs` 第 76–87 行 **现状**: ```csharp Vector2 contentSize = content.rect.size; Vector2 viewportSize = viewport.rect.size; float rangeX = contentSize.x - viewportSize.x; float rangeY = contentSize.y - viewportSize.y; ``` `RectTransform.rect.size` 是本地空间(未缩放)的尺寸。当 `_zoomTarget.localScale = (_zoom, _zoom, 1)` 改变后,内容在屏幕上的视觉尺寸为 `contentSize × _zoom`,ScrollRect 的实际可滚动范围也随之放大。 当前代码中 `rangeX = contentSize.x - viewportSize.x` 不含缩放因子,导致: - 放大时(`_zoom > 1`):每帧移动距离 `delta.x / rangeX` 被高估,平移速度感知快于预期; - 缩小时(`_zoom < 1`):平移速度感知慢于预期; - 同样问题影响 `MapPanel.CenterOnCurrentRoom` 中的 `rangeX / rangeY` 计算。 **修复**: ```csharp // MapInputHandler.Update float rangeX = contentSize.x * _zoom - viewportSize.x; float rangeY = contentSize.y * _zoom - viewportSize.y; // MapPanel.CenterOnCurrentRoom — 同步修复 rangeX/rangeY float rangeX = contentSize.x * /* zoom */ - viewSize.x; // 需从 MapInputHandler 传入或独立持有 ``` > **注意**:`MapPanel.CenterOnCurrentRoom` 中无法直接读取 `MapInputHandler._zoom`;最简方案是从 `_roomContainer.localScale.x` 读取当前缩放值。 --- ### N2(低,编辑器性能):`noResultStyle` 每次 `OnGUI` 重新分配 **文件**:`Assets/_Game/Scripts/Editor/World/Map/MapLayoutEditorWindow.cs` 第 365–371 行 **现状**: ```csharp var noResultStyle = new GUIStyle(EditorStyles.boldLabel) { alignment = TextAnchor.MiddleCenter, normal = { textColor = new Color(1f, 0.8f, 0.2f, 0.8f) }, fontSize = 13, }; ``` 此段仅在"搜索有内容 && 无匹配"时执行,但每次 `OnGUI`(编辑器 60 fps 或更高频率)均分配一个新 `GUIStyle` 对象。与已有的 `_roomLabelStyle`、`_badgeBossStyle`、`_badgeNormalStyle` 的缓存模式不一致。 **修复**:添加 `_noResultStyle` 字段,在 `EnsureLabelStyles()` 中一并初始化(`fontSize = 13` 固定,无需随 `_zoom` 变化)。 --- ### N3(低,架构一致性):`MinimapHUD` 保留 `_onMapUpdated` 订阅导致双重刷新 **文件**:`Assets/_Game/Scripts/World/Map/MinimapHUD.cs` 第 90 行 + 第 205–209 行 **现状**: ```csharp // OnEnable: _onMapUpdated?.Subscribe(OnMapUpdated).AddTo(_subs); // OnMapUpdated: private void OnMapUpdated(string roomId) { if (_cells.TryGetValue(roomId, out var cell)) cell.SetVisibility(_mapSvc.GetVisibility(roomId)); } ``` `MapPanel` 已在 R12-N8 移除 `_onMapUpdated` 订阅,改由 `OnExplorationChanged` 统一处理。但 `MinimapHUD` 未跟进: 每次房间被探索或标记时,`MapManager` 先后触发: 1. `_onMapUpdated?.Raise(roomId)` → `MinimapHUD.OnMapUpdated(roomId)` — 更新单个格子 2. `OnExplorationChanged?.Invoke()` → `MinimapHUD.OnExplorationChanged()` — 刷新全部活跃格子 步骤 1 的工作完全被步骤 2 覆盖,造成重复写入。对于小视野(4–9 格)影响可忽略,但在视野半径较大时每次探索会多做一次无效的单格更新。 **修复**:从 `MinimapHUD` 移除 `_onMapUpdated` SerializeField 及其订阅,保留 `[HideInInspector, SerializeField]` 字段并标注废弃说明(与 MapPanel 的处理方式对齐,保留 Prefab 序列化兼容性),删除 `OnMapUpdated` 私有方法。 --- ## 架构深度审查 ### 接口层(4 接口) | 接口 | 实现 | 注册方式 | 状态 | |------|------|----------|------| | `IMapService` | `MapManager` | `ServiceLocator.Register` in Awake | ✅ 完整 | | `IPinService` | `MapPinManager` | ServiceLocator in Awake | ✅ 完整 | | `IPlayerPositionProvider` | `MapPlayerTracker` | ServiceLocator in Awake | ✅ 完整 | | `ITeleportService` | `TeleportService` | ServiceLocator in Awake | ✅ 完整 | ### 存档一致性(3 处 ISaveable) | 实现类 | 防御拷贝 | 覆盖加载 | 广播更新 | |--------|----------|----------|----------| | `MapManager.OnSave/OnLoad` | ✅ `new HashSet<>(_x)` | ✅ `new HashSet<>` 后赋值 | ✅ `OnExplorationChanged?.Invoke()` | | `MapPinManager.OnSave/OnLoad` | ✅ `new List<>(_pins)` | ✅ `new List<>` 后赋值 | ✅ `PinsVersion++` | | `TeleportService.OnSave/OnLoad` | ✅ `new HashSet<>(_unlockedRoomIds)` | ✅ Clear + foreach Add | ✅(通过传送服务事件) | ### 对象池完整性 | 池 | 容纳类型 | 所在组件 | 入池时机 | |----|----------|----------|----------| | `_cellPool` | MapRoomCellUI | MapPanel | RebuildAll | | `_pinPool` | Image (Pin) | MapPanel | ClearPins | | `_exitPool` | Image (Exit) | MapPanel | ClearExits | | `_cellPool` | MapRoomCellUI | MinimapHUD | RefreshView 裁剪过期格子 | | `_pinPool` | Image (Pin) | MinimapHUD | ClearPins | ### 性能关键路径 | 路径 | 复杂度 | 机制 | |------|--------|------| | `MinimapHUD.RefreshView` 新格子查询 | O(viewRadius²) | `MapDatabaseSO.GetRoomIdAtCell` 共享空间索引 | | `MapPanel.LateUpdate` 服务查询 | O(1) 短路 | `_servicesReady` bool 门控 | | `MapPanel.LateUpdate` Pin 渲染 | O(1) 检查 | `PinsVersion` 脏检查 | | `MapManager.GetRoomsByRegion` | O(1) | `_regionCache` 懒加载字典 | | `MapDatabaseSO.GetRoom` | O(1) | `_index` 字符串→SO 哈希索引 | | `MapDatabaseSO.GetRoomIdAtCell` | O(1) | `_cellToRoom` 格子坐标→ID 空间索引 | | `MinimapHUD.UpdatePlayerDot` | O(1) | roomId + NormPos 双字段脏检查 | | `MapLayoutEditorWindow.EnsureLabelStyles` | O(1) | `_cachedZoomForStyle` 脏检查 | ### 编辑器工具覆盖面 | 工具 | 能力 | |------|------| | `MapLayoutEditorWindow` | 可视化布局预览;滚轮缩放;中键/Alt 平移;房间拖拽编辑(Undo);区域配色;验证 + 错误高亮;搜索高亮(R17 UX 改善);图例;Play Mode 玩家点叠加 | | `MapDatabaseEditor` | Inspector 内嵌"在布局编辑器中打开"按钮 | | `MapRoomDataEditor` | SceneGUI 双角控制点可视化 GridPosition/GridSize | | `MapRoomAutoRegister` | `AssetPostprocessor` 自动注册新 SO 到默认 Database;删除 null 清理;可禁用 | --- ## R18 修复预期评分 | 维度 | 权重 | 当前 | 修复后 | 变化 | |------|------|------|--------|------| | 架构解耦 | 20% | 95 | 95 | — | | 功能完整性 | 20% | 92 | 92 | — | | 性能 | 15% | 91 | 93 | +2(N1+N3)| | 编辑器工具 | 15% | 93 | 94 | +1(N2)| | 可扩展性 | 10% | 95 | 95 | — | | 代码质量 | 10% | 93 | 95 | +2(N2+N3 架构一致)| | 玩家体验设计 | 10% | 88 | 92 | +4(N1 修复后平移手感正确)| **综合(修复前):92.4 / 100 → 修复后:93.8 / 100** --- ## 总结 经过 18 轮迭代,小地图系统整体架构已达到生产级 2D 探索类游戏标准: - **接口完整**:四接口零具体类依赖,ServiceLocator 完全解耦 - **存档对称**:三处 ISaveable 均实现防御拷贝,读档后广播正确事件 - **性能到位**:五池 + O(viewRadius²) + 多级脏检查 + _servicesReady 短路 - **编辑器成熟**:拖拽、搜索、Undo、Play Mode 叠加、自动注册一体化 R18 新发现 3 项问题均为低/中优先级,不影响功能正确性(N1 影响手感,N2/N3 为轻微性能与架构一致性问题)。