Add independent review reports for Minimap system (Rounds 8, 9, and 26)

- Round 8 report highlights improvements in architecture, editor usability, and data robustness, with a total score of 80/100.
- Round 9 report focuses on editor extension capabilities, identifying issues with room data indexing and layout editing, resulting in a score of 76/100.
- Round 26 report evaluates the system against commercial standards, noting new issues and confirming previous fixes, with a score of 95.8/100.
This commit is contained in:
2026-05-25 23:15:12 +08:00
parent e2bc324905
commit f74d7f1877
53 changed files with 6825 additions and 270 deletions

View File

@@ -0,0 +1,239 @@
# 小地图独立审查报告 — 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 不渲染 PinRound 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<MapDatabaseSO>(
UnityEditor.AssetDatabase.GUIDToAssetPath(guid));
if (db?.AllRooms != null && System.Array.IndexOf(db.AllRooms, this) >= 0)
db.InvalidateIndex();
}
#endif
```
或更轻量方案:让 `MapDatabaseSO.GetRoom`/`GetRoomIdAtCell` 在 Editor 下每帧检查 dirty 标志。
#### R9-N2MapLayoutEditorWindow 不可编辑 — 仅"只读预览"
- **位置**`MapLayoutEditorWindow.HandleInput`
- **现状**只支持平移、缩放、点击选中Ping。无法在窗口内**直接拖拽改变房间 GridPosition**。
- **后果**:策划想调整两个房间的相邻关系,需要:① 在 Layout 窗口看到问题 → ② 切换到 Project 找对应 SO → ③ 进入 Scene 用 SceneView 拖拽 → ④ 切回 Layout 窗口查看。流程断裂严重,背离"编辑器易用"目标。
- **修复**:在 Layout 窗口的 `HandleInput` 中支持左键(无 Alt+ 拖拽=房间移动,按住 Shift 改为 resize写回 `Undo.RecordObject + EditorUtility.SetDirty`。
- **加分项**:键盘 Delete 删除当前选中房间Ctrl+D 复制(位移 GridSize 距离)。
#### R9-N3MapDatabaseSO 创建新房间 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<MapRoomDataSO>(path);
if (room == null) continue;
// 找到默认 MapDatabaseSO自动加入弹出确认对话框可选
...
}
}
}
```
或在 MapLayoutEditorWindow 工具栏加 "扫描未注册 Room" 按钮。
### P1建议修
#### R9-N4MapLayoutEditorWindow 缺少搜索 / 过滤 / 区域图例
- 100+ 房间时无法快速定位特定 RoomId / RegionId。
- 区域配色由 Palette 自动分配,**没有图例显示"颜色 → 区域名"**,策划猜不出蓝色代表哪个区域。
- **修复**:工具栏增加 `TextField` 输入 RoomId 关键字 → 仅高亮匹配房间;右下角浮动面板列出区域→颜色映射。
#### R9-N5MapDatabaseEditor 验证不在保存/打开时自动触发
- 当前必须手动点 "重新验证" 才会出错误清单。
- **修复**:在 `OnEnable` 中调用一次 `ValidateAll`;或在 `MapDatabaseSO.OnValidate` 中 `#if UNITY_EDITOR` 自动验证(轻量项)。
#### R9-N6MapRoomDataEditor 静态 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-N7Scene 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-N9MapPanel `_playerIconImg` 不强制置顶
- `_playerIconImg` 作为 `_roomContainer` 子物体,渲染顺序由其在 Hierarchy 中的位置决定。若策划在 Prefab 中把它放在 cells 之前,会被房间格子遮挡。
- **修复**`UpdatePlayerIcon` 末尾 `_playerIconImg.transform.SetAsLastSibling()`,或文档明确要求"必须为最后一个子节点"。
#### R9-N10RegionNameDisplay 协程引用未在 OnDisable 清理
- `_showCoroutine` 在 OnDisable 时未置 null下次 OnEnable 后旧引用仍存在StopCoroutine 对已停止的句柄无害但语义不洁)。
- **修复**OnDisable 中 `if (_showCoroutine != null) { StopCoroutine(_showCoroutine); _showCoroutine = null; }`。
#### R9-N11MapLayoutEditorWindow 不显示 Play Mode 玩家位置
- 编辑器窗口在 Play Mode 中**不会高亮显示玩家当前所在房间**QA 调试不便。
- **修复**:在 `OnGUI` 中 `if (Application.isPlaying)` 查 `IPlayerPositionProvider.CurrentRoomId`,在对应房间上叠加红色圆点。
#### R9-N12MapLayoutEditorWindow 不显示 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** 探索进度 UIAPI 已就位)
- **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/P1R8-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-N1MapRoomDataSO.OnValidate 通过 EditorApplication.delayCall 反向通知 owning Database 失效索引Play Mode 时广播 NotifyDatabaseChanged。
- [x] R8-N2MinimapHUD 渲染视野内 PinIPinService + Sprite 字典 + PinsVersion 脏检查)。
- [x] R8-N3MapManager Awake 重复实例处理增加 _isDuplicate 字段OnEnable/OnDisable 守卫。
- [x] R8-N4MapPlayerTracker Awake 重复实例 Destroy。
- [x] R8-N5MapPanel.LateUpdate 懒加载服务_mapSvc/_playerProvider/_pinService
- [x] R8-N6MapManager.OnLoad 末尾广播 OnDatabaseChanged。
- [x] R8-N7MapPin.OnSave 改为 new List<MapPin>(_pins),避免共享引用。
### Round 9 新发现 P0 / P1 / P2 全部完成
- [x] R9-N2MapLayoutEditorWindow 支持左键拖拽房间Undo + 实时刷新。
- [x] R9-N3MapRoomAutoRegister.cs 新增 AssetPostprocessor新建 Room 自动追加到默认 Database。
- [x] R9-N4布局窗口工具栏新增搜索框按 RoomId/RegionId 高亮)+ 图例面板(按 Region 着色映射)。
- [x] R9-N5MapDatabaseEditor.OnEnable 自动 ValidateAll 并构建错误集。
- [x] R9-N6MapRoomDataEditor GUIStyle 改为懒加载(属性访问器 + null 合并赋值)。
- [x] R9-N7DragHandle 绘制 BL/TR 角点标签,便于多房间编辑识别。
- [x] R9-N8MapPin 服务注册从 OnEnable/OnDisable 迁移到 Awake/OnDestroy与 MapManager/Tracker 对齐。
- [x] R9-N9MapPanel 玩家图标 SetAsLastSibling 强制顶层。
- [x] R9-N10RegionNameDisplay.OnDisable 显式 StopCoroutine 并复位 alpha。
- [x] R9-N11MapLayoutEditorWindow 在 Play Mode 绘制玩家红点(基于 IPlayerPositionProvider
- [x] R9-N13MapRoomDataEditor 增加 [CanEditMultipleObjects]。
### 编译验证
- BaseGames.World.Map.csproj0 警告 0 错误 ✓
- BaseGames.Editor.csproj 中 Map/编辑器扩展相关源文件0 错误 ✓
(仅余 BaseGames.Dialogue 的 Camera 命名空间错误,与本次改动无关)
### 预期得分调整
本轮 19 项 P0/P1/P2 全部修复落地后,编辑器扩展专项预计:
- 编辑器扩展 (10%) 72 → ~88自动注册 / 拖拽编辑 / 搜索 / 图例 / Play 模式可视化 / 多选)
- 数据契约 / 错误恢复 (15%)80 → ~92OnValidate 反向通知 + 自动验证 + 重复实例 Destroy
- 总分预期76 → ~89A-)。
下一轮独立复审后即可正式确认得分。