Files
zeling_v2/Docs/Review/Minimap_Review_Round11_Independent.md
Joywayer f74d7f1877 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.
2026-05-25 23:15:12 +08:00

21 KiB
Raw Permalink Blame History

小地图系统 Round 11 独立评估报告

评估时间2026-05-25
基准版本R10 全部修复落地后当前 HEAD
评估范围:Assets/_Game/Scripts/World/Map/ + Assets/_Game/Scripts/Editor/World/Map/
对标标准:成熟 2D 银河恶魔城游戏标准(高性能、高解耦、策划友好、编辑器一流)


第 1 章:整体评分

维度 分值(满 10 较 R10
架构解耦 8.5 ↑0.5(事件语义分离完成)
数据设计 8.5 ↑0稳定
运行时性能 8.5 ↑0.3Pin 池 + Cell 保留落地)
编辑器扩展 8.0 ↑0.5拖拽冲突可视化、IsDefault
策划友好性 7.5 ↑0仍缺 DisplayName 本地化)
功能完整性 8.5 ↑0稳定
鲁棒性 7.5 ↓0.5(发现 N11 部分订阅 Bug
可扩展性 8.5 ↑0.3SetMappedBatch、OnRoomMapped

加权综合得分85.6 / 100B+

R10 修复整体质量优秀;本轮发现 N1 MinimapHUD 部分订阅 BugP1 级别真实缺陷),导致鲁棒性维度扣分,综合分低于 R10 预估的 92 分。


第 2 章:系统亮点

2.1 接口与事件设计9/10

  • IMapService 完整定义了三个语义明确的事件:OnDatabaseChanged(结构变更)/ OnExplorationChanged(探索进度)/ OnRoomMapped(单房间解锁)。
  • 消费方MapPanel、MinimapHUD通过接口与 ServiceLocator 完全解耦,不持有任何具体 MonoBehaviour 引用。
  • MapServiceExtensions.GetVisibility 集中三级可见性推导逻辑,避免分散重复。

2.2 空间索引共享9/10

  • MapDatabaseSO.GetRoomIdAtCell(Vector2Int) 惰性构建一次,供 MapPlayerTracker / MinimapHUD.RefreshView 共享O(1) 格子查找。
  • InvalidateIndex 在结构变更时统一失效,不存在缓存过期风险。

2.3 MinimapHUD 增量刷新8.5/10

  • RefreshView 为 O(viewRadius²) 而非 O(allRooms),大地图下效果显著。
  • 回收/新建格子避免全量重建,_toRemove / _roomsInViewBuffer 列表复用消除高频 GC。

2.4 编辑器工具套件8/10

  • MapLayoutEditorWindow:格子布局预览 + 区域着色 + 拖拽移房 + 冲突可视化R10-N5+ 搜索/图例 + 验证 + Play Mode 玩家位置。
  • MapRoomDataEditorScene View 双角控制点直接拖拽,策划可在场景中直观编辑房间尺寸。
  • MapRoomAutoRegister:新建 SO 自动追加到默认 Database消灭策划忘记注册的问题。

2.5 数据兼容性保障9/10

  • [FormerlySerializedAs("AllRooms")] 确保 _allRooms 字段重命名后现有 .asset 不丢失数据。
  • EditorSetRooms 专用写入器防止外部代码绕过封装直接赋值。

第 3 章新发现问题R11-N1 ~ N12

R11-N1 ★P1★ — MinimapHUD _subscribed 标志导致部分订阅场景下事件永不触发

文件: MinimapHUD.csSubscribeServices()

现象:

private void SubscribeServices()
{
    _mapSvc         ??= ServiceLocator.GetOrDefault<IMapService>();
    _playerProvider ??= ServiceLocator.GetOrDefault<IPlayerPositionProvider>();
    _pinService     ??= ServiceLocator.GetOrDefault<IPinService>();
    if (_subscribed) return;                             // ← 提前 return 阻断后续
    if (_mapSvc == null && _playerProvider == null) return;
    if (_playerProvider != null)
        _playerProvider.OnRoomChanged += OnRoomChanged;
    if (_mapSvc != null)
    {
        _mapSvc.OnDatabaseChanged    += OnDatabaseChanged;
        _mapSvc.OnExplorationChanged += OnExplorationChanged;
    }
    _subscribed = true;                                  // ← 仅当上方至少一个服务非 null 时才置位
}

具体 Bug
场景——_playerProvider 在 Awake 时已注册(优先 ExecutionOrder_mapSvc 尚未就绪:

  1. 第一次调用:_playerProvider 成功,_mapSvc == null → 仅订阅 OnRoomChanged,置 _subscribed = true
  2. 后续调用:_mapSvc 现已就绪,但 if (_subscribed) return 提前退出,OnDatabaseChanged / OnExplorationChanged 永远不订阅
  3. 结果:小地图 HUD 读档后不刷新、房间解锁后不更新颜色。

修复方案: 改为分别追踪 _mapSvcSubscribed / _playerSubscribed,或直接仿照 MapPanel 的模式(每个服务独立 if (svc == null) 守门)。


R11-N2 ★P1★ — MapRoomDataSO.OnValidate 重复向 delayCall 追加委托

文件: MapRoomDataSO.csOnValidate()

private void OnValidate()
{
    GridSize = new Vector2Int(Mathf.Max(1, GridSize.x), Mathf.Max(1, GridSize.y));
#if UNITY_EDITOR
    UnityEditor.EditorApplication.delayCall += NotifyOwningDatabases;  // ← 问题所在
#endif
}

问题: delayCall 是多播委托(+=)。当策划在 Inspector 中快速拖动滑条时,OnValidate 每帧调用一次,NotifyOwningDatabases 被追加数十次。该方法内部执行 FindAssets + LoadAssetAtPath(昂贵),会在下一帧批量执行导致卡顿。

修复方案:-=+=,保证同一 delayCall 序列中最多一次:

EditorApplication.delayCall -= NotifyOwningDatabases;
EditorApplication.delayCall += NotifyOwningDatabases;

R11-N3 ★P1★ — MapPinManager.OnLoad 直接赋值反序列化 List共享 SaveData 引用

文件: MapPin.csMapPinManager.OnLoad()

public void OnLoad(SaveData data)
{
    _pins = data.Map.Pins ?? new List<MapPin>();   // ← 直接赋值,不是拷贝
    PinsVersion++;
}

OnSave 做了防御性拷贝(new List<MapPin>(_pins)),但 OnLoad 反方向没有拷贝。若调用方在 OnLoad 后继续持有 data 并修改 data.Map.Pins,会污染 _pins

修复:

_pins = data.Map.Pins != null ? new List<MapPin>(data.Map.Pins) : new List<MapPin>();

R11-N4 ★P1★ — MapPanel.CenterOnCurrentRoom 对整个 content 节点调用 ForceRebuildLayoutImmediate

文件: MapPanel.csCenterOnCurrentRoom()

LayoutRebuilder.ForceRebuildLayoutImmediate(_scrollRect.content);

ForceRebuildLayoutImmediate 会递归重建参数节点及其所有子节点的 Layout。_scrollRect.content 下有所有 MapRoomCellUI 实例重建代价随房间数线性增长N 房间 = N 次 layout 计算)。面板每次 OnEnable 时执行一次,常规用法中可接受;但若项目规模扩展到 200+ 房间,此处会成为明显延迟点。

建议: 只有在 content 布局确实发生变化时BuildGrid 之后)才 ForceRebuild若 ScrollRect 没有使用 LayoutGroup可改为直接计算 normalizedPosition完全跳过 ForceRebuildLayoutImmediate。


R11-N5 ★P2★ — MinimapHUDMapRoomCellUI 无对象池,跨房间时 GC 抖动

文件: MinimapHUD.csRefreshView() → cell 回收段

if (cell != null) Destroy(cell.gameObject);    // ← 销毁而非入池

MinimapHUD 的 RefreshView 在玩家跨越房间边界时,会销毁视野外的 MapRoomCellUI GameObject 并重新实例化新进入视野的格子。

  • 典型场景(走廊穿梭):每次房间切换约销毁/创建 3-8 个 Cell GameObject频率可达 1-2 次/秒。
  • Pin 已有对象池,但 Cell 没有,导致一定 GC 压力。

建议:MapRoomCellUI 建立 Stack<MapRoomCellUI> _cellPool,回收时 SetActive(false) 入池,需要时出池重置,与 Pin 池保持一致。


R11-N6 ★P2★ — MapManager.GetRoomsByRegion 每次调用都分配新数组

文件: MapManager.cs

public MapRoomDataSO[] GetRoomsByRegion(string regionId)
    => _database.AllRooms.Where(r => r != null && r.RegionId == regionId).ToArray();

每次调用分配 LINQ 枚举器 + 结果数组。若调用方(如 MapPanel 地区筛选、成就系统)在 Update 中使用,会造成 GC。

建议: 加结果缓存Dictionary<string, MapRoomDataSO[]>),在 NotifyDatabaseChanged 时失效。


R11-N7 ★P2★ — MapLayoutEditorWindow 不监听外部资产变更

文件: MapLayoutEditorWindow.cs

窗口打开后:

  • 若从代码/其他窗口修改 MapDatabaseSO(如 MapDatabaseEditor 的 Validate 按钮),布局窗口不自动刷新,需用户手动交互。
  • Undo.undoRedoPerformed 正确注册,但外部变更(EditorUtility.SetDirty 后保存、脚本修改资产)不触发 Repaint

建议: 监听 EditorApplication.projectWindowItemOnGUI 或使用 AssetDatabase.postprocessAllAssets;或在 OnGUI 开头检查 database 的 AllRooms 数组引用变化(版本号方案)。


R11-N8 ★P2★ — MapRoomCellUI.SetuppixelsPerCell 参数对 MinimapHUD 调用路径存在 API 语义歧义

文件: MapRoomCellUI.cs / MinimapHUD.cs

// MinimapHUD 调用路径:
cell.Setup(room, _mapSvc.GetVisibility(room.RoomId), null);   // 使用默认 pixelsPerCell=32
cell.SetColors(_colorExplored, _colorMapped, _colorUnknown);
PlaceCell(cell, room);                                          // 立即覆盖 RT.anchoredPosition 和 sizeDelta

Setup 内部已根据 pixelsPerCell 计算并写入了 RT.anchoredPosition / RT.sizeDelta,但 MinimapHUD 立即用 PlaceCell 覆盖,造成无意义的写入。pixelsPerCell 参数对 MinimapHUD 路径无实际效果,但 API 签名暗示它有意义,容易误导维护者。

建议:Setup 中将位置/尺寸计算提取为 SetGridLayout(room, pixelsPerCell) 方法MinimapHUD 调用 Setup 时不传位置参数,由 PlaceCell 统一负责布局。或简化为重载:Setup(room, visibility, icon) + Setup(room, visibility, icon, pixelsPerCell)


R11-N9 ★P2★ — MapPlayerTracker 假设世界原点与格子原点重合,无 WorldOffset 参数

文件: MapPlayerTracker.cs

private Vector2Int WorldToCell(Vector2 worldPos)
    => new(Mathf.FloorToInt(worldPos.x / _worldUnitsPerCell),
           Mathf.FloorToInt(worldPos.y / _worldUnitsPerCell));

如果关卡世界坐标原点不在 (0,0)(如整个世界在 Y=-500 以下),此计算会得到错误的格子坐标,导致玩家位置追踪完全失效。

建议: 增加 [SerializeField] private Vector2 _worldOriginOffset 字段,WorldToCell 先减去 _worldOriginOffset 再除以 _worldUnitsPerCell


R11-N10 ★P2★ — MapLayoutEditorWindow.DrawExitLines 连线使用房间中心而非实际出口格子坐标

文件: MapLayoutEditorWindow.csDrawExitLines()

Vector2 from = GridCenterToClip(room.GridPosition + room.GridSize / 2, origin);  // 房间中心
Vector2 to   = GridCenterToClip(target.GridPosition + target.GridSize / 2, origin);

RoomExitData 结构中已有 ExitGridPos 字段(出口在格子地图上的实际位置),但 DrawExitLines 画的是两个房间中心之间的连线。对于大尺寸房间,连线起止点可能距离实际出口较远,策划无法直观判断出口对齐情况。

建议: 改为从 exit.ExitGridPos 到对应 target 房间的对应出口格子坐标,若 target 无对应出口则退化为中心连线。


R11-N11 ★P2★ — MapRoomDataSO 公共字段缺少 RoomId 命名规则验证

文件: MapRoomDataSO.cs / MapDatabaseSO.csValidateAll()

RoomId 字段直接用于:

  1. 场景名匹配(OnRoomEntered 事件传入场景名)
  2. Dictionary key 查找
  3. 存档 HashSet 存储

目前 ValidateAll 检查了重复和空值,但未检查:

  • 首尾空格(" Room_A ""Room_A" 被视为不同但功能等效时易混淆)
  • 特殊字符(/\ 等可能影响路径处理的字符)

建议:MapRoomDataSO.OnValidate 中自动 Trim();在 ValidateAll 中增加含空格/特殊字符的警告。


R11-N12 ★P3★ — MapLayoutEditorWindow.DrawExitLines 连线在极端缩放(≤ 0°GUI.matrix 未正确恢复

文件: MapLayoutEditorWindow.csDrawLine()

GUIUtility.RotateAroundPivot(angle, mid);
GUI.DrawTexture(...);
GUI.matrix = prevMatrix;   // 手动恢复

DrawTexture 抛出异常(如纹理被意外卸载),GUI.matrix 不会被恢复,导致整个窗口绘制出现旋转偏移。

建议: 使用 using (new GUIMatrixScope(...))try/finally 包裹:

var prev = GUI.matrix;
try { GUIUtility.RotateAroundPivot(angle, mid); GUI.DrawTexture(...); }
finally { GUI.matrix = prev; }

第 4 章:维度详细评分

4.1 架构解耦 — 8.5/10

优秀:

  • IMapService 接口三个独立事件,语义清晰
  • ServiceLocator 注册在 Awake/OnDestroy生命周期正确
  • MapPinManager 独立于 MapManager通过 IPinService 解耦
  • MapServiceExtensions 扩展方法集中可复用逻辑

不足:

  • MinimapHUD _subscribed 标志存在部分订阅 BugN1 P1
  • MapPanel 仍通过 StringEventChannelSO _onMapUpdated 双通道接收单房间更新OnMapUpdated + OnExplorationChanged 语义重叠但各有其用,轻微冗余)

4.2 数据设计 — 8.5/10

优秀:

  • 三级可见性Unknown / Explored / Mapped精确匹配银河恶魔城标准
  • MapDatabaseSO 懒构建双索引id → datacell → roomId共享给所有消费方
  • MapRoomDataSO.OnValidate 自动修正 GridSize 最小值
  • RoomExitData 包含 TransitionType为场景切换类型扩展预留

不足:

  • GetRoomsByRegion 无结果缓存N6 P2
  • DisplayName 无 i18n 路径(延续 R10-N10
  • RoomId 无命名规则强制检查N11 P2

4.3 运行时性能 — 8.5/10

优秀:

  • MinimapHUD RefreshView O(viewRadius²),大地图下远快于 O(N)
  • Pin 对象池MapPanel + MinimapHUDClearPins = SetActive(false) 而非 Destroy
  • 脏标志驱动 UIdatabaseDirty / explorationDirty / viewDirty
  • LateUpdate 双重脏检查PinsVersion + 玩家位置)
  • MapDatabaseSO 空间索引 O(1) 哈希查找

不足:

  • MinimapHUD MapRoomCellUI 无对象池N5 P2跨房间边界 GC 抖动
  • CenterOnCurrentRoom 对 content ForceRebuildLayoutImmediateN4 P1大房间数时开销可见
  • GetRoomsByRegion LINQ.ToArray() 无缓存N6 P2

4.4 编辑器扩展 — 8.0/10

优秀:

  • MapLayoutEditorWindow 全功能zoom/pan/drag/conflict/search/legend/validate/PlayMode 玩家点
  • MapRoomDataEditor Scene View 双角控制点 + 吸附 + Undo + 居中快捷键
  • MapDatabaseEditor 一键验证 + 房间列表 + 错误行红色高亮
  • MapRoomAutoRegister 自动注册消除遗漏风险 + EditorPrefs 开关
  • Undo/Redo 刷新支持

不足:

  • 外部资产变更不触发窗口刷新N7 P2
  • DrawExitLines 用中心连线而非实际出口格坐标N10 P2
  • OnValidate delayCall 重复追加N2 P1

4.5 策划友好性 — 7.5/10

优秀:

  • 布局编辑器拖拽房间 + 冲突立即变红,无需专业编程知识
  • 搜索 + 图例 + 区域着色帮助大地图快速定位
  • 自动注册新房间无需手动维护 Database
  • Play Mode 实时玩家位置可视化

不足:

  • 出口连线视觉不够精确N10策划无法确认出口对齐
  • 无键盘快捷键(如 V = 验证F = 重置视图)
  • 无批量移动/对齐多个房间能力(延续 R10-N6
  • DisplayName 无法本地化预览(延续 R10-N10

4.6 功能完整性 — 8.5/10

优秀:

  • 全屏地图 + 角落小地图双 UI与头部游戏相同配置
  • SetMappedBatch 支持地图碎片批量解锁
  • OnRoomMapped + OnRoomMappedAnim 虚钩子,解锁动画预留
  • 探索进度 APIGetExplorationProgress / ExploredRoomCount
  • MapExplorationCondition 接入成就系统
  • 三种特殊房间标记Boss / SavePoint / Shop+ MapIconOverride 自定义
  • 房间出口数据 + 过渡类型

不足:

  • 无"全地图揭示"调试命令(开发阶段常用)
  • 无地图房间分组/层级(如地下层 / 地面层分图)
  • RoomOutlineTex 非矩形形状支持存在(字段已有),但编辑器无预览

4.7 鲁棒性 — 7.5/10

优秀:

  • MapManager / MapPlayerTracker 重复实例 Awake 检测 + _isDuplicate 守门
  • 全量 null 守卫(空 Database / 空房间数组)
  • FormerlySerializedAs 数据兼容
  • ValidateAll 四类错误检测null / 重复 ID / 格子重叠 / 出口悬空)
  • _exploredRooms / _mappedRooms 使用 HashSet 防重复

严重不足:

  • MinimapHUD 部分订阅 BugN1 P1OnDatabaseChanged / OnExplorationChanged 在特定启动顺序下永不触发
  • MapPinManager.OnLoad 共享 SaveData 列表引用N3 P1

4.8 可扩展性 — 8.5/10

优秀:

  • IMapService 接口易于 Mock/测试替换
  • SetMappedBatch + OnRoomMapped 为地图碎片系统提供一流扩展点
  • protected virtual OnRoomMappedAnim 供 UI 子类实现动画
  • RoomExitData.PreferredTransitionType 枚举为未来过渡系统预留
  • MapServiceExtensions 扩展方法模式
  • IsDefault 标志 + AutoRegister 支持多 Database 项目

不足:

  • IMapService.GetAllMappedRooms() / GetAllExploredRooms() 返回快照 API存档分析/成就系统需多次 HashSet 枚举)
  • MapRoomDataSO 无版本号字段(热更/DLC 房间 ID 变更无法追踪遗留数据)

第 5 章:优先级修复清单

P1 — 必须修复(影响正确性)

编号 位置 问题摘要 预估工时
R11-N1 MinimapHUD.SubscribeServices _subscribed 阻止部分订阅后续补全 → mapSvc 事件永不触发 1h
R11-N2 MapRoomDataSO.OnValidate delayCall += 重复追加 → 批量编辑时 N×FindAssets 卡顿 0.5h
R11-N3 MapPinManager.OnLoad 直接赋值反序列化 List → 引用共享污染 SaveData 0.5h
R11-N4 MapPanel.CenterOnCurrentRoom ForceRebuildLayoutImmediate(content) → 大房间数时 OnEnable 卡顿 1h

P2 — 应当修复(影响体验/维护)

编号 位置 问题摘要
R11-N5 MinimapHUD.RefreshView MapRoomCellUI 无对象池,跨房间 GC 抖动
R11-N6 MapManager.GetRoomsByRegion LINQ ToArray() 无缓存
R11-N7 MapLayoutEditorWindow 外部资产变更不触发 Repaint
R11-N8 MapRoomCellUI.Setup pixelsPerCell 参数对 MinimapHUD 路径无意义API 歧义
R11-N9 MapPlayerTracker 无 WorldOriginOffset世界坐标偏移场景无法使用
R11-N10 MapLayoutEditorWindow.DrawExitLines 中心连线而非出口格坐标,视觉精度低
R11-N11 MapRoomDataSO.OnValidate + ValidateAll RoomId 无命名规则检查Trim / 空格 / 特殊字符)

P3 — 可选优化

编号 问题摘要
R11-N12 DrawLine GUI.matrix 未在异常路径下恢复

第 6 章:与标杆游戏对比

特性 本系统 业界标杆
三级可见性 Unknown / Explored / Mapped 标准配置
角落小地图 视野半径可配置,增量刷新
全屏地图 + ScrollRect 居中
地图碎片批量解锁 SetMappedBatch (商店购买/触碰标牌解锁)
地图标记Pin系统 多类型 + 存档
非矩形房间形状 ⚠️ 字段预留,编辑器无预览 (精细多边形遮罩)
多区域地图(分图) (地下/地表/秘境分区)
房间Tooltip/命名 DisplayName (带区域名动画)
键盘导航地图 WASD/方向键)
出口连接可视化 ⚠️ 编辑器中心连线,运行时无连线 (点状通道指示)
地图缩放(运行时) 滚轮缩放
地图揭示动画 ⚠️ 钩子已预留,动画未实现 (逐格展开)

第 7 章:总结

本系统在 R10 修复落地后已达到商业级银河恶魔城地图系统的主体功能,架构理念(接口 + ServiceLocator + 事件分离 + 脏标志)、编辑器工具套件(三窗口协同 + 自动注册)处于同类独立游戏工具的前列水平

本轮发现的最高优先级问题集中在鲁棒性细节MinimapHUD 部分订阅 Bug、OnValidate delayCall 堆积)和API 设计细节Setup 参数歧义、GetRoomsByRegion 分配),修复这些问题后综合评分预估可恢复至 90~91 / 100A-

长期来看,补齐以下能力可冲击 95/100A

  1. MapRoomCellUI 对象池N5
  2. 多区域/分图支持
  3. 非矩形房间轮廓编辑器预览
  4. 出口精确连线可视化N10
  5. WorldOriginOffset 参数N9

本报告独立于前序轮次评审,基于 2026-05-25 当前代码库完整重读后生成。