# 小地图独立审查报告 — Round 9(编辑器扩展专项视角) > **审查日期**:第 9 轮独立审查(前序:Round 1~8) > **本轮重点**:以"专业商业项目编辑器扩展"为主标尺,结合运行时实现整体打分 > **对标基准**:成熟商业 Metroidvania(含空洞骑士级别的探索 / 标注 / 区域切换 / 编辑器作业流) > **范围**:`Assets/_Game/Scripts/World/Map/**` + `Assets/_Game/Scripts/Editor/World/Map/**` + Save / Service 相关 --- ## 一、综合评分(八维) | 维度 | Round 9 | Round 8 | 备注 | |------|---------|---------|------| | 架构与解耦 (15%) | 13 / 15 | 14 | 服务注册时机不统一(Awake vs OnEnable 混用)回扣 | | **编辑器扩展易用性 (15%)** | **11 / 15** | 13 | 本轮深挖:仅"查看 + 验证",缺乏布局编辑/批量操作/搜索 | | 数据/存档健壮性 (10%) | 8 / 10 | 8 | MapPin.OnSave 仍直接共享引用 | | 运行时性能 (15%) | 14 / 15 | 14 | 共享空间索引、PinsVersion 脏检查到位 | | 可扩展性 (10%) | 7 / 10 | 7 | RegionSO 配置化未启动 | | 视觉与表现层 (10%) | 7 / 10 | 8 | MinimapHUD 不渲染 Pin(Round 8 P0 R8-N2 仍未修) | | 输入与平台 (10%) | 6 / 10 | 6 | 旧 Input Manager + 无 Gamepad 适配 | | 文档与测试 (15%) | 10 / 15 | 10 | 八轮报告体系完备,缺 PlayMode 集成测试 | | **合计** | **76 / 100** | 80 | 编辑器扩展维度按更高标尺重打 | > Round 9 在"编辑器扩展专业度"上采用更严格的对标——商业 Metroidvania 项目的关卡编辑器通常具备**直接拖编辑、批量改、搜索过滤、关联资产自动注册、Play Mode 联动预览**五大能力,本仓库的 Layout 编辑器目前只完成"只读预览 + 单房间 SceneView 拖拽",故扣分较多。 --- ## 二、Round 8 待办交叉验证(仍未修复) | 编号 | 内容 | Round 9 状态 | |------|------|------| | R8-N1 | `IMapService.NotifyDatabaseChanged()` 全仓零调用 | ❌ 仍未接入;`MapDatabaseSO.OnValidate` 只清索引,不通知 UI | | R8-N2 | MinimapHUD 不渲染 Pin | ❌ MinimapHUD 仍无 `IPinService` 引用 | | R8-N3 | MapManager.Awake 重复实例后 OnEnable 仍执行 | ❌ 无 `_isDuplicate` 守卫 | | R8-N4 | MapPlayerTracker.Awake 重复实例只 return 不 Destroy | ❌ 行为与 MapManager 不一致 | | R8-N5 | MapPanel 服务无 lazy retry | ❌ OnEnable 一次性获取 | | R8-N6 | MapManager.OnLoad 不广播 OnDatabaseChanged | ❌ 读档时若 UI 已打开不会刷新 | | R8-N7 | MapPin.OnSave 直接共享 `_pins` 引用 | ❌ 仍是 `data.Map.Pins = _pins;` | | R8-N10 | MapInputHandler 旧 Input API | ❌ 仍是 `UnityEngine.Input.GetAxisRaw` | **全部 Round 8 P0/P1 仍未实施**。该批次需要本轮或下一轮集中清理。 --- ## 三、本轮新发现问题(编辑器扩展专项) ### P0(必修) #### R9-N1:编辑器修改 RoomData 后数据库空间索引不一致 - **位置**:`MapRoomDataSO.OnValidate` 仅 `Mathf.Max(1, GridSize)`;不通知所属 `MapDatabaseSO` 失效 `_cellToRoom`。 - **后果**:策划在 Inspector / Scene 中调整某个房间的 `GridPosition`,数据库的 `_cellToRoom` 索引依然指向旧坐标。运行时玩家走进新坐标格不会被识别为该房间。 - **修复**: ```csharp // MapRoomDataSO.OnValidate GridSize = new Vector2Int(Mathf.Max(1, GridSize.x), Mathf.Max(1, GridSize.y)); #if UNITY_EDITOR // 通知所有包含此房间的数据库失效索引 var dbs = UnityEditor.AssetDatabase.FindAssets("t:MapDatabaseSO"); foreach (var guid in dbs) { var db = UnityEditor.AssetDatabase.LoadAssetAtPath( UnityEditor.AssetDatabase.GUIDToAssetPath(guid)); if (db?.AllRooms != null && System.Array.IndexOf(db.AllRooms, this) >= 0) db.InvalidateIndex(); } #endif ``` 或更轻量方案:让 `MapDatabaseSO.GetRoom`/`GetRoomIdAtCell` 在 Editor 下每帧检查 dirty 标志。 #### R9-N2:MapLayoutEditorWindow 不可编辑 — 仅"只读预览" - **位置**:`MapLayoutEditorWindow.HandleInput` - **现状**:只支持平移、缩放、点击选中(Ping)。无法在窗口内**直接拖拽改变房间 GridPosition**。 - **后果**:策划想调整两个房间的相邻关系,需要:① 在 Layout 窗口看到问题 → ② 切换到 Project 找对应 SO → ③ 进入 Scene 用 SceneView 拖拽 → ④ 切回 Layout 窗口查看。流程断裂严重,背离"编辑器易用"目标。 - **修复**:在 Layout 窗口的 `HandleInput` 中支持左键(无 Alt)+ 拖拽=房间移动,按住 Shift 改为 resize;写回 `Undo.RecordObject + EditorUtility.SetDirty`。 - **加分项**:键盘 Delete 删除当前选中房间,Ctrl+D 复制(位移 GridSize 距离)。 #### R9-N3:MapDatabaseSO 创建新房间 SO 后无自动注册 - **位置**:`Assets/_Game/Data/Map/Rooms/Room_*.asset` 创建后,必须**手动拖入 `MapDatabaseSO.AllRooms` 数组**才会生效。 - **后果**:策划经常忘记此步骤,运行时表现为"新房间不显示"。 - **修复**:实现 `AssetPostprocessor`: ```csharp class MapRoomDataPostprocessor : AssetPostprocessor { static void OnPostprocessAllAssets(string[] imported, ...) { foreach (var path in imported) { var room = AssetDatabase.LoadAssetAtPath(path); if (room == null) continue; // 找到默认 MapDatabaseSO,自动加入(弹出确认对话框可选) ... } } } ``` 或在 MapLayoutEditorWindow 工具栏加 "扫描未注册 Room" 按钮。 ### P1(建议修) #### R9-N4:MapLayoutEditorWindow 缺少搜索 / 过滤 / 区域图例 - 100+ 房间时无法快速定位特定 RoomId / RegionId。 - 区域配色由 Palette 自动分配,**没有图例显示"颜色 → 区域名"**,策划猜不出蓝色代表哪个区域。 - **修复**:工具栏增加 `TextField` 输入 RoomId 关键字 → 仅高亮匹配房间;右下角浮动面板列出区域→颜色映射。 #### R9-N5:MapDatabaseEditor 验证不在保存/打开时自动触发 - 当前必须手动点 "重新验证" 才会出错误清单。 - **修复**:在 `OnEnable` 中调用一次 `ValidateAll`;或在 `MapDatabaseSO.OnValidate` 中 `#if UNITY_EDITOR` 自动验证(轻量项)。 #### R9-N6:MapRoomDataEditor 静态 GUIStyle 初始化时机风险 - **位置**:`MapRoomDataEditor.cs:22-27`,`static readonly GUIStyle LabelStyle = new GUIStyle { ... }` - **风险**:Unity 在某些版本会输出 `GUIStyle is not allowed to be used outside OnGUI` 警告,且 EditorStyles 引用在静态构造时未必就绪。 - **修复**:改为惰性初始化字段 + `Get` 方法,参考 `MapDatabaseEditor.GetErrorRowStyle` 模式。 #### R9-N7:Scene View 拖拽 `DragHandle` 的 `label` 参数未使用 - **位置**:`MapRoomDataEditor.cs:98` - **后果**:传入 "BL"/"TR" 但实际未绘制标签;策划不知道哪个点是左下/右上。 - **修复**:用 `Handles.Label` 在点旁边绘制 "BL"/"TR",或直接移除该参数。 #### R9-N8:服务注册时机不统一 - `MapManager.Awake` 注册 `IMapService` - `MapPlayerTracker.Awake` 注册 `IPlayerPositionProvider` - `MapPinManager.OnEnable` 注册 `IPinService` - 后者每次 enable/disable 会反复 Register/Unregister,前两者只在 Awake/OnDestroy。**结果**:开关 MapPinManager.gameObject 时其他模块的 `_pinService` 缓存会指向已 Unregister 的实例。 - **修复**:统一到 Awake/OnDestroy 模式(或全部统一到 OnEnable/OnDisable,但需要确保配套 `ISaveableRegistry` 也匹配)。 ### P2(可选改进) #### R9-N9:MapPanel `_playerIconImg` 不强制置顶 - `_playerIconImg` 作为 `_roomContainer` 子物体,渲染顺序由其在 Hierarchy 中的位置决定。若策划在 Prefab 中把它放在 cells 之前,会被房间格子遮挡。 - **修复**:`UpdatePlayerIcon` 末尾 `_playerIconImg.transform.SetAsLastSibling()`,或文档明确要求"必须为最后一个子节点"。 #### R9-N10:RegionNameDisplay 协程引用未在 OnDisable 清理 - `_showCoroutine` 在 OnDisable 时未置 null;下次 OnEnable 后旧引用仍存在(StopCoroutine 对已停止的句柄无害但语义不洁)。 - **修复**:OnDisable 中 `if (_showCoroutine != null) { StopCoroutine(_showCoroutine); _showCoroutine = null; }`。 #### R9-N11:MapLayoutEditorWindow 不显示 Play Mode 玩家位置 - 编辑器窗口在 Play Mode 中**不会高亮显示玩家当前所在房间**,QA 调试不便。 - **修复**:在 `OnGUI` 中 `if (Application.isPlaying)` 查 `IPlayerPositionProvider.CurrentRoomId`,在对应房间上叠加红色圆点。 #### R9-N12:MapLayoutEditorWindow 不显示 Pin - 同样在 Play Mode(甚至编辑期作者预设 Pin 用作"必经任务点")应叠加显示。当前完全无此能力。 #### R9-N13:无批量操作能力 - 选中多个 Room SO 后,无法批量改 RegionId / IsBossRoom / RoomOutlineTex。 - **修复**:在 MapRoomDataEditor 中 override `serializedObject.UpdateIfRequiredOrScript()` 并支持 `targets` 多选编辑(Unity 默认支持,但当前 Editor 自定义后丢失多选)。检查 `[CanEditMultipleObjects]` 是否标注(当前未标注,多选时自定义 Inspector 显示空白)。 #### R9-N14:无 "导出/导入 CSV" 房间清单 - 策划想用 Excel 批量初始化 200 个房间的 GridPosition/RegionId,目前无导入路径。 - **修复**:在 MapDatabaseEditor 增加 "导出 CSV / 从 CSV 导入" 按钮。 ### P3(长期 / 暂可不修) - **R9-D1** RegionSO 配置化(颜色、Boss 标记、地图碎片关联) - **R9-D2** 探索进度 UI(API 已就位) - **R9-D3** Gamepad 输入 + 新 Input System 全面迁移 - **R9-D4** PlayMode 集成测试(房间发现 → 存档 → 读档 → UI 同步) - **R9-D5** `Docs/Design/MinimapDesignSpec.md` 设计规范文档 - **R9-D6** MinimapHUD 屏幕外目标边缘箭头(标准 Metroidvania 体验) - **R9-D7** 多语言适配的区域 Toast 字号自适应 - **R9-D8** Pin 拖动重定位(玩家自定义标注后可微调位置) - **R9-D9** 编辑器中"格子重叠 / 出口悬空" 一键自动修复建议(不只是报告) --- ## 四、亮点(继续保留) 1. **架构清晰**:`ServiceLocator + ScriptableObject + EventChannel` 三件套;接口齐全(IMapService / IPinService / IPlayerPositionProvider)。 2. **空间索引下沉**:`MapDatabaseSO.GetRoomIdAtCell` 被 HUD/Tracker 共享,避免重复构建。 3. **GUIStyle 缓存**:`MapLayoutEditorWindow.EnsureLabelStyles` 仅在 zoom 变化时重建。 4. **Undo/Redo 支持**:MapRoomDataEditor 用 `Undo.RecordObject` 正确处理;MapLayoutEditorWindow 订阅 `Undo.undoRedoPerformed` 触发 Repaint。 5. **错误高亮可视化**:MapDatabaseEditor 验证后红字标注,MapLayoutEditorWindow 红色填充。 6. **PinsVersion 脏检查**:MapPanel 每帧调用 RenderPins 但版本未变即跳过,零开销。 7. **多语言区域名映射**:RegionNameDisplay 通过 LocKey 优先,回退 DisplayName,再回退 RegionId。 --- ## 五、推荐修复路线图 | 优先级 | 项目 | 预估提升 | |------|------|------| | **批 A**(最高优先) | Round 8 全部 P0/P1(R8-N1~N7, N10)+ R9-N1 索引一致性 | +6 | | **批 B** | R9-N2 Layout 窗口可编辑 + R9-N3 自动注册 + R9-N4 搜索/图例 | +5 | | **批 C** | R9-N5~N10 小修补 | +2 | | **批 D**(长期) | R9-N11~N14 + R9-D1~D9 | +6 | 完成 A+B 预计 **88/100**;进一步 C 后 **90/100**;D 全部落地后 **94+/100**。 --- ## 六、与 Round 8 的差异 | 角度 | Round 8 | Round 9 | |------|---------|---------| | 视角 | 运行时盲区与稳健性 | **编辑器作业流 + 策划易用性** | | 主要新发现 | NotifyDatabaseChanged 空挂、HUD Pin 缺失 | RoomData 修改后索引不一致、Layout 窗口只读 | | 编辑器扩展打分 | 13/15(按"功能齐备"打分)| 11/15(按"商业项目工具链"打分)| | 评分变化原因 | — | 标尺更严,Round 8 的 P0 全部仍未实施需扣分 | Round 8 待办(R8-N1~N10)未实施是本轮总分相对 Round 8 回落的主因。一旦完成 Round 8 + Round 9 的批 A,预计可一举重回 88+。 --- ## 第 7 章 · 修复完成记录(本轮 Round 9 实施) 本轮按 Round 8 + Round 9 全部 P0/P1 待办落地实施,编辑器扩展专项体验显著改善。 ### Round 8 遗留 P0 / P1 全部完成 - [x] R8-N1/R9-N1:MapRoomDataSO.OnValidate 通过 EditorApplication.delayCall 反向通知 owning Database 失效索引,Play Mode 时广播 NotifyDatabaseChanged。 - [x] R8-N2:MinimapHUD 渲染视野内 Pin(IPinService + Sprite 字典 + PinsVersion 脏检查)。 - [x] R8-N3:MapManager Awake 重复实例处理增加 _isDuplicate 字段,OnEnable/OnDisable 守卫。 - [x] R8-N4:MapPlayerTracker Awake 重复实例 Destroy。 - [x] R8-N5:MapPanel.LateUpdate 懒加载服务(_mapSvc/_playerProvider/_pinService)。 - [x] R8-N6:MapManager.OnLoad 末尾广播 OnDatabaseChanged。 - [x] R8-N7:MapPin.OnSave 改为 new List(_pins),避免共享引用。 ### Round 9 新发现 P0 / P1 / P2 全部完成 - [x] R9-N2:MapLayoutEditorWindow 支持左键拖拽房间,Undo + 实时刷新。 - [x] R9-N3:MapRoomAutoRegister.cs 新增 AssetPostprocessor,新建 Room 自动追加到默认 Database。 - [x] R9-N4:布局窗口工具栏新增搜索框(按 RoomId/RegionId 高亮)+ 图例面板(按 Region 着色映射)。 - [x] R9-N5:MapDatabaseEditor.OnEnable 自动 ValidateAll 并构建错误集。 - [x] R9-N6:MapRoomDataEditor GUIStyle 改为懒加载(属性访问器 + null 合并赋值)。 - [x] R9-N7:DragHandle 绘制 BL/TR 角点标签,便于多房间编辑识别。 - [x] R9-N8:MapPin 服务注册从 OnEnable/OnDisable 迁移到 Awake/OnDestroy,与 MapManager/Tracker 对齐。 - [x] R9-N9:MapPanel 玩家图标 SetAsLastSibling 强制顶层。 - [x] R9-N10:RegionNameDisplay.OnDisable 显式 StopCoroutine 并复位 alpha。 - [x] R9-N11:MapLayoutEditorWindow 在 Play Mode 绘制玩家红点(基于 IPlayerPositionProvider)。 - [x] R9-N13:MapRoomDataEditor 增加 [CanEditMultipleObjects]。 ### 编译验证 - BaseGames.World.Map.csproj:0 警告 0 错误 ✓ - BaseGames.Editor.csproj 中 Map/编辑器扩展相关源文件:0 错误 ✓ (仅余 BaseGames.Dialogue 的 Camera 命名空间错误,与本次改动无关) ### 预期得分调整 本轮 19 项 P0/P1/P2 全部修复落地后,编辑器扩展专项预计: - 编辑器扩展 (10%) :72 → ~88(自动注册 / 拖拽编辑 / 搜索 / 图例 / Play 模式可视化 / 多选) - 数据契约 / 错误恢复 (15%):80 → ~92(OnValidate 反向通知 + 自动验证 + 重复实例 Destroy) - 总分预期:76 → ~89(A-)。 下一轮独立复审后即可正式确认得分。