# 小地图系统独立评审 Round 23 **评审时间**:R23(基于 R22 全部修复已落地的代码基线) **评审范围**:`Assets/_Game/Scripts/World/Map/` 全部 19 个运行时文件 + 4 个编辑器扩展文件 **评分基准**:专业 2D Metroidvania 编辑器扩展标准(架构解耦 / 高性能 / 可扩展 / 开发者友好) --- ## R22 修复确认 | 编号 | 内容 | 状态 | |------|------|------| | R22-N1 | `BuildRegionDict/ResolveRegionDisplayName` 迁移至 `MapServiceExtensions`;两个组件单行委托 | ✅ 确认(`MapServiceExtensions.cs:17,32`;两组件第 104-108 行) | | R22-N2 | `DrawRoomBadge` 注释更新为 `MapRoomDataSO.ChooseDisplayIcon` | ✅ 确认(`MapLayoutEditorWindow.cs:480,488`) | | R22-N3 | `MapPinManager [DefaultExecutionOrder(-500)]` | ✅ 确认(`MapPin.cs:20`) | --- ## 各维度评分 ### 1. 架构设计(Architecture)19.5 / 20 **亮点(全面审查确认)** - 4 接口(IMapService / IPinService / IPlayerPositionProvider / ITeleportService)+ ServiceLocator,零硬依赖 - 事件驱动(Action,无帧轮询):OnDatabaseChanged / OnExplorationChanged / OnRoomMapped 语义独立 - ISaveable 防御性拷贝三处对称(MapManager:`new HashSet<>` ×2;MapPinManager:`new List<>` 双向;TeleportService:`readonly` + Clear+Add) - `ChooseDisplayIcon` 单一入口(MapRoomDataSO),MapPanel / MinimapHUD / Editor 三处均委托 - `MapServiceExtensions`:3 个无状态方法(GetVisibility / CreatePinAtWorldPos / BuildRegionDict+Resolve),消费方零重复逻辑 - `MapRoomCellUI` 双视图(MapPanel 全屏 + MinimapHUD 角落)复用同一 Prefab - `RoomType [Flags]` 枚举支持多类型组合,`OnValidate` 自动迁移旧 bool 字段 **信息级观察**:`MapPanel.UnsubscribeServices()` 不 null 服务引用,而 `MinimapHUD.UnsubscribeServices()` 会 null 三个字段并重置 `_servicesReady`。这是有意的架构差异——MapPanel 仅在 OnDestroy 调用 UnsubscribeServices(生命周期末尾,引用已无意义),而 MinimapHUD 作为持久 HUD 需支持跨场景重连。在目标持久服务架构下,两者均不会出现悬空引用。**无需修复,但值得注释说明设计意图。** **扣分:−0.5**(UnsubscribeServices 不对称缺少注释,可读性略低) --- ### 2. 性能(Performance)19.5 / 20 **亮点** - `_servicesReady` 短路(MapPanel R12-N7;MinimapHUD R21-N1),消除每帧 ServiceLocator 查询 - MapPanel 对象池 × 3(`readonly` 集合:`_cellPool` / `_pinPool` / `_exitPool`);MinimapHUD × 2 - O(viewRadius²) 空间索引(`MapDatabaseSO.GetRoomIdAtCell`),MinimapHUD 无需扫描全量房间 - 4 个复用缓冲区(`_toRemove` / `_roomsInViewBuffer` / `_newlyAddedBuffer` + `_cells` 增量更新) - `PinsVersion` 脏检查:两 UI 均跳过无变化重绘 - `_totalRoomCount` 懒加载缓存(MapManager),`_regionCache` 懒加载(GetRoomsByRegion 零 LINQ) - 玩家图标双脏标记(roomId + normPos),消除无效 RectTransform 写入 **信息级**:`MapProgressDisplay.Refresh()` 区域进度遍历 O(N/region),仅在 `OnExplorationChanged` 触发(非每帧),当前规模无性能风险。 **扣分:−0.5**(`MapProgressDisplay` 区域遍历未缓存已探索计数;极大地图时有轻微冗余) --- ### 3. 代码质量(Code Quality)19.5 / 20 **亮点** - 全部集合字段补齐 `readonly`(MapPanel × 6;MinimapHUD × 4;`_revealCoroutines` 亦为 `readonly`) - `CurrentZoom` 属性消除双份缩放状态(MapPanel L379) - `RunRevealAnim` 自清理协程:完成后自动 `_revealCoroutines.Remove(roomId)`(R20-N1) - `HasCustomExitPos` 语义布尔替代 `== Vector2Int.zero` 哨兵(R13-N1) - `[Obsolete]` + `[HideInInspector]` 废弃字段注解完整 - `RoomId.Trim()` 自动修剪防空格污染(OnValidate) - `DrawLine` try/finally 保证 `GUI.matrix` 恢复(R11-N12) - `MapPin.cs` 文件头注释明确历史遗留原因 **信息级**:`MapPanel.UnsubscribeServices()` 相比 `MinimapHUD.UnsubscribeServices()` 缺少注释说明为何不 null 服务引用。新加入代码者可能误认为是疏漏。 **扣分:−0.5**(UnsubscribeServices 不对称未有注释解释意图) --- ### 4. 编辑器扩展(Editor Tools)14.5 / 15 **亮点** - `MapLayoutEditorWindow`:缩放 / 平移 / 房间拖拽 / 搜索 / 区域着色 / 验证 / Undo / 热改全功能 - `_cachedZoomForStyle` 脏检查 + `_noResultStyle` 首次初始化缓存(R18-N2) - `DrawExitLines` 字段级 `_drawnExitPairs` 去重,OnGUI 零分配 - `OnProjectChange` + `OnUndoRedo` 自动失效验证缓存并触发重绘 - `SetDatabase` 公共 API(避免 MapDatabaseEditor 反射访问私有字段) - `MapRoomAutoRegister` AssetPostprocessor 自动注册工作流 - `MapDatabaseSO.ValidateAll()` 四类校验(null / 空 RoomId / 重复 / 格子重叠 / 出口悬空) - `DrawRoomBadge` 注释已更新为 `MapRoomDataSO.ChooseDisplayIcon`(R22-N2) **扣分:−0.5**(`MapLayoutEditorWindow` 搜索框匹配范围仅 RoomId/RegionId 子串,无法按 RoomType 过滤;策划批量检查某类型房间时需逐个点击) --- ### 5. 功能完整性(Feature Completeness)15.0 / 15 **所有特性完整实现:** - 三级可见性(Unknown / Explored / Mapped)+ 雾效覆盖层(R12-FD) - 图标优先级唯一入口(Override > SavePoint > Boss > Shop > Teleport) - Pin 系统(持久化 / 类型化 / 视野内渲染 / 64 字符 note 限制) - 出口连接线 + Fallback 位置(HasCustomExitPos 语义布尔) - 传送系统(TeleportService:解锁 / 验证 / 请求 / 完成回调) - 区域检测 + 区域名本地化显示(RegionNameDisplay + MapProgressDisplay) - 存档/读档(ISaveable 三处,防御性拷贝对称) - 房间发现动画(PlayRevealAnim + RunRevealAnim 自清理) - 全屏地图 + 角落 HUD 双视图;小地图视野档位循环切换(CycleZoom) - `RoomType [Flags]` 枚举 + `OnValidate` 向后兼容迁移 **扣分:0** --- ### 6. 输入系统(Input System)9.5 / 10 **亮点** - 全部使用 InputReaderSO(InputSystem),无硬编码按键 - `MapInputHandler`:Navigate / MapCenter / OnScroll 完整 - `MinimapInputHandler`:CycleMinimapZoom 路由 MinimapHUD.CycleZoom() - OnEnable/OnDisable 对称订阅/取消 **信息级**:`MapInputHandler._zoom` 作为本地累加器,与 `_panel.CurrentZoom` 读取路径不同但结果一致(OnScroll 写 `_roomContainer.localScale` → `CurrentZoom` 从 `_roomContainer.localScale.x` 读取,无环,无状态漂移)。 **扣分:−0.5**(`MapInputHandler._zoom` 轻微职责重叠,正确但不够直观) --- ## 综合评分 | 维度 | 满分 | 得分 | 较 R22 | |------|------|------|--------| | 架构设计 | 20 | 19.5 | +0.5 | | 性能 | 20 | 19.5 | ±0 | | 代码质量 | 20 | 19.5 | +0.5 | | 编辑器扩展 | 15 | 14.5 | ±0 | | 功能完整性 | 15 | 15.0 | ±0 | | 输入系统 | 10 | 9.5 | ±0 | | **合计** | **100** | **97.5** | **+1.0** | --- ## 问题清单 ### N1 — MapPanel.UnsubscribeServices 缺少意图注释(低优先级) **现状**:`MinimapHUD.UnsubscribeServices()` 会 null 三个服务引用并重置 `_servicesReady`;`MapPanel.UnsubscribeServices()` 不做此操作(也不需要,因为仅在 OnDestroy 调用)。两者不对称,可读性略差。 **修复**:在 `MapPanel.UnsubscribeServices()` 首行补注释: ```csharp // OnDestroy 时调用,生命周期末尾不需要 null 服务引用(与 MinimapHUD.UnsubscribeServices 的设计差异: // MinimapHUD 需支持 OnDestroy 后跨场景重连,MapPanel 不需要)。 ``` --- ### N2 — MapLayoutEditorWindow 搜索框不支持 RoomType 过滤(低优先级) **场景**:策划需要批量确认所有 SavePoint / BossRoom 房间的位置时,当前搜索只能匹配 RoomId/RegionId 子串,无法用 `SavePoint` 等类型关键字过滤。 **建议方案**:在搜索逻辑中加入 RoomType 枚举名匹配: ```csharp // 在 DrawMapArea 的搜索匹配部分补充: bool matchesType = !string.IsNullOrEmpty(_searchText) && System.Enum.GetNames(typeof(RoomType)) .Any(name => room.RoomFlags.HasFlag((RoomType)System.Enum.Parse(typeof(RoomType), name)) && name.IndexOf(_searchText, System.StringComparison.OrdinalIgnoreCase) >= 0); bool matches = matchesId || matchesRegion || matchesType; ``` --- ### N3 — TeleportService 缺少 [DefaultExecutionOrder](信息级) `TeleportService.Awake()` 注册 `ITeleportService`。当前 UI 组件(MinimapHUD / MapPanel)的 `SubscribeServices` 不查询 `ITeleportService`,因此无执行顺序风险。但与其他三个服务(MapManager -700 / MapPlayerTracker -600 / MapPinManager -500)相比,未标注顺序,一致性略差。 ```csharp [DefaultExecutionOrder(-400)] // 晚于 MapPinManager(-500),早于默认 0;ITeleportService 在 UI 初始化前可用 public class TeleportService : MonoBehaviour, ITeleportService, ISaveable ``` --- ## 评分历史 | 轮次 | 评分 | 关键改动 | |------|------|----------| | R18 | 93.8 | MinimapHUD 废弃 _onMapUpdated 订阅 | | R19 | 93.8 | CurrentZoom 属性;_revealCoroutines 防泄漏 | | R20 | 94.2 | RunRevealAnim 自清理;ChooseDisplayIcon 集中 | | R21 | 95.0 | MinimapHUD _servicesReady;MapPlayerTracker 执行顺序;readonly 补全 | | R22 | 96.5 | RegionNameEntry DRY 消除;注释更新;MapPinManager 执行顺序 | | **R23** | **97.5** | R22 修复确认;识别 N1(注释不对称)N2(搜索缺 RoomType 过滤)N3(TeleportService 执行顺序) | --- ## 总评 经过 23 轮迭代,小地图系统已达到**专业商业 2D Metroidvania 发布标准**: - **架构**:接口 + ServiceLocator + 事件驱动,各层职责清晰,扩展无需改动已有代码 - **性能**:全部热路径均有脏检查 / 对象池 / 短路机制保护,大地图下无性能风险 - **开发体验**:编辑器工具完整,策划可视化配置房间、验证错误、调试布局 - **代码健康**:DRY 违反已全部消除,readonly / 防御性拷贝 / 对象池三要素一致应用 剩余 2.5 分差距为信息级的可读性优化与编辑器小功能增强,不影响运行时正确性。