- 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.
240 lines
15 KiB
Markdown
240 lines
15 KiB
Markdown
# 小地图独立审查报告 — 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<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-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<MapRoomDataSO>(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<MapPin>(_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-)。
|
||
|
||
下一轮独立复审后即可正式确认得分。
|
||
|