# 小地图系统 独立审查报告 Round 14 **审查范围**:`Assets/_Game/Scripts/World/Map/`(Runtime 19 文件)+ `Assets/_Game/Scripts/Editor/World/Map/`(Editor 4 文件) **对标标准**:成熟 2D Metroidvania 类型游戏的专业编辑器扩展级别, 面向开发人员和策划人员,要求架构解耦、高性能、高可扩展性。 --- ## 总评分(修复前) | 维度 | 满分 | 得分 | 说明 | |---|---|---|---| | 架构解耦 | 10 | 9.0 | 接口 + ServiceLocator 完整;MapPin.cs 文件名历史遗留 | | 性能 | 10 | 9.0 | 对象池完整、dirty check 完整;DrawExitLines 每帧 new HashSet | | 编辑器 UX | 10 | 9.0 | 可视化布局编辑器成熟;DrawExitLines HashSet GC 小问题 | | 数据模型 | 10 | 8.5 | RoomType [Flags] 完善;缺少 TeleportStation 类型 | | 输入系统 | 10 | 8.5 | InputSystem 软绑定完整;MinimapInputHandler R13 已加 | | 功能完整性 | 10 | 6.5 | TeleportService 存在但 MapPanel 无传送 UI 集成;SaveKey 模式错误 | | 代码质量 | 10 | 8.5 | 注释质量高;TeleportService 含死代码字段 | | 存档系统 | 10 | 5.0 | **TeleportService.ISaveable 签名错误(编译错误)** | | 可扩展性 | 10 | 9.0 | SO 驱动、Event Channel 解耦、Region 机制完善 | | **总分** | **90** | **73.0** | **换算 100 分:81.1 / 100(B)** | > **相比 R13(85.0/100)**:R13 N1–N4 + FA 修复提升了输入系统与 Bug 修复维度, > 但 TeleportService 的 ISaveable 签名引入了编译错误(N1),使存档分大幅下降。 > 所有修复后预计 **92+**。 --- ## Bug 发现 ### N1(致命 — 编译错误):TeleportService 实现了错误的 ISaveable 签名 **文件**:`TeleportService.cs`(R13-FA 新增),`SaveData.cs` **问题**: `ISaveable` 接口(`BaseGames.Core.Save`)定义为: ```csharp public interface ISaveable { void OnSave(SaveData saveData); void OnLoad(SaveData saveData); } ``` 但 `TeleportService` 实现的是: ```csharp public string SaveKey => "TeleportService"; // ❌ 接口中不存在此成员 public string Serialize() { ... } // ❌ 接口中不存在此方法 public void Deserialize(string data) { ... } // ❌ 接口中不存在此方法 // ❌ 缺少 OnSave(SaveData) / OnLoad(SaveData) ``` 这导致 `BaseGames.World.Map` 程序集**无法编译**,整个地图系统全部失效。 此外,`MapSaveData`(`SaveData.cs`)中没有 `UnlockedTeleportRoomIds` 字段, 即使签名正确,传送数据也无处存储。 **同时**,`TeleportService` 向 `ISaveableRegistry` 注册自身(OnEnable 中), 但 `ISaveableRegistry` 期望的是 `ISaveable` 对象, 而 `TeleportService` 未正确实现该接口,注册调用将在运行时无效。 **修复方案**: 1. **`SaveData.cs`** — 在 `MapSaveData` 中添加: ```csharp public HashSet UnlockedTeleportRoomIds = new(); ``` 2. **`TeleportService.cs`** — 替换 `SaveKey/Serialize/Deserialize` 为: ```csharp public void OnSave(SaveData saveData) { saveData.Map.UnlockedTeleportRoomIds = new HashSet(_unlockedRoomIds); } public void OnLoad(SaveData saveData) { _unlockedRoomIds.Clear(); if (saveData.Map.UnlockedTeleportRoomIds != null) foreach (var id in saveData.Map.UnlockedTeleportRoomIds) _unlockedRoomIds.Add(id); } ``` 移除 `ISaveableRegistry` 手动注册(`OnSave/OnLoad` 由 `SaveManager` 直接调用,无需 Registry)。 --- ### N2(高优先级):RoomType 缺少 TeleportStation 标志位 **文件**:`MapRoomDataSO.cs`,`MapPanel.cs` **问题**: `RoomType` [Flags] 枚举目前有 BossRoom / SavePoint / Shop / Merchant / Challenge, 但没有 `TeleportStation`。`TeleportService` 的解锁状态存储于运行时, 但**策划无法在 SO 上标记"此房间有传送站"**,`MapPanel` 也无法据此渲染传送图标。 ```csharp // 当前 public enum RoomType { None = 0, BossRoom = 1 << 0, SavePoint = 1 << 1, Shop = 1 << 2, Merchant = 1 << 3, Challenge = 1 << 4, // ❌ 缺少 TeleportStation } ``` **修复**: 1. `RoomType` 添加 `TeleportStation = 1 << 5` 2. `MapPanel` 添加 `[SerializeField] private Sprite _iconTeleport;` 字段 3. `MapPanel.ChooseIcon()` 中补充传送站图标逻辑 4. `MapPanel` 获取 `ITeleportService`,在 `BuildGrid` / `RefreshCell` 时区分 "已解锁传送站"和"未解锁传送站"的颜色/图标 --- ### N3(中等):TeleportService._pendingSourceRoomId 是死代码 **文件**:`TeleportService.cs`,第 108 行 **问题**: ```csharp _pendingSourceRoomId = sourceRoomId; // ← 赋值后从未读取 OnTeleportRequested?.Invoke(sourceRoomId, targetRoomId); // sourceRoomId 已直接传入 ``` `_pendingSourceRoomId` 只被写入,永远不被读取,是无用的私有字段。 **修复**:删除 `_pendingSourceRoomId` 字段及赋值语句。 --- ### N4(低优先级):MapLayoutEditorWindow.DrawExitLines 每帧 new HashSet **文件**:`MapLayoutEditorWindow.cs`,`DrawExitLines()` 方法(约第 453 行) **问题**: ```csharp private void DrawExitLines(MapDatabaseSO db, ...) { var drawn = new HashSet<(string, string)>(); // ❌ 每次 OnGUI 都分配新对象 ... } ``` 编辑器 `OnGUI` 以每秒多次频率调用,持续产生 GC 分配,可能导致编辑器卡顿。 **修复**:将 `drawn` 提升为类字段 `_drawnExitPairs`,在方法内仅调用 `Clear()`。 --- ### N5(低优先级):MapPanel.OnMapUpdated 未标注 [Obsolete] **文件**:`MapPanel.cs`,第 313 行 **问题**: ```csharp private void OnMapUpdated(string roomId) { /* R12-N8 已废弃 */ } ``` 方法已废弃但未加 `[Obsolete]` 标注,后续开发者可能误以为该方法仍有业务逻辑。 **修复**:添加 `[Obsolete("R12-N8: 由 OnExplorationChanged 统一处理,仅保留序列化兼容性。")]`。 --- ## 架构亮点(保持优秀) 以下设计在本轮审查中仍被评定为业界优秀水平: | 亮点 | 说明 | |---|---| | 接口 + ServiceLocator | 4 个服务接口完全解耦,UI 层零具体类型依赖 | | O(1) 空间索引 | `MapDatabaseSO.GetRoomIdAtCell` 惰性构建,`MinimapHUD` + `MapPlayerTracker` 共享 | | 三重对象池 | `MapPanel`(cell/pin/exit)+ `MinimapHUD`(cell/pin)零 GC 渲染 | | Dirty Flag 模式 | 面板关闭期间收到事件,OnEnable 时应用——无事件遗漏 | | `_servicesReady` 短路 | 三服务就绪后跳过 LateUpdate 的 ServiceLocator 查询 | | `PinsVersion` 脏检查 | 无需事件订阅,整数比较即可判断 Pin 集合是否变化 | | 可视化布局编辑器 | MapLayoutEditorWindow:拖拽/吸附/缩放/搜索/Play 模式覆盖层 | | MapRoomAutoRegister | AssetPostprocessor 自动注册新房间 SO,零手动操作 | | InputSystem 软绑定 | `throwIfNotFound: false` 防 InputActionAsset 缺失崩溃 | | `HasCustomExitPos` 标志 | 消除 `Vector2Int.zero` 哨兵值歧义(R12-N5,R13-N1 已修复) | --- ## 修复优先级总表 | 编号 | 严重程度 | 文件 | 说明 | |---|---|---|---| | N1 | ★★★★★ 致命 | `TeleportService.cs` + `SaveData.cs` | ISaveable 签名错误导致编译失败 | | N2 | ★★★★ 高 | `MapRoomDataSO.cs` + `MapPanel.cs` | 缺少 TeleportStation RoomType + MapPanel 传送 UI 集成 | | N3 | ★★★ 中 | `TeleportService.cs` | `_pendingSourceRoomId` 死代码 | | N4 | ★★ 低 | `MapLayoutEditorWindow.cs` | DrawExitLines HashSet 每帧分配 | | N5 | ★ 极低 | `MapPanel.cs` | OnMapUpdated 缺 [Obsolete] | --- ## 修复后预估评分 | 维度 | 修复前 | 修复后 | |---|---|---| | 架构解耦 | 9.0 | 9.0 | | 性能 | 9.0 | 9.5(N4 修复) | | 编辑器 UX | 9.0 | 9.0 | | 数据模型 | 8.5 | 9.5(N2 TeleportStation) | | 输入系统 | 8.5 | 8.5 | | 功能完整性 | 6.5 | 9.0(N2 传送 UI) | | 代码质量 | 8.5 | 9.0(N3/N5) | | 存档系统 | 5.0 | 9.5(N1 修复) | | 可扩展性 | 9.0 | 9.0 | | **总分** | **81.1** | **≈ 92.3** |