Files
zeling_v2/Docs/Review/Minimap_Review_Round7_Independent.md
Joywayer e2bc324905 Add independent review report for Minimap system Round 7
- Validate fixes from Round 6 and identify new issues
- Document findings including UX defects, editor integration flaws, and code quality concerns
- Propose solutions and prioritize issues based on severity
- Evaluate against standards of mature 2D Metroidvania games
2026-05-25 14:44:31 +08:00

14 KiB
Raw Blame History

小地图系统 Round 7 独立再审查报告

评估日期Round 7
评估方法:在 Round 6 修复完成后,再次独立通读所有 17 个地图模块文件,重点发现 Round 6 遗漏的真实问题
对标标准:成熟 2D Metroidvania 游戏小地图,专注编辑器扩展质量、架构解耦、高性能、策划友好度


一、Round 6 修复验证

Round 6 修复 ID 验证结果 备注
P1-1 MinimapHUD 空间索引 通过 _spatialIndex 构建/查询逻辑正确O(viewRadius²) 替代 O(N) 落实
P2-1 MapDatabaseEditor GUIStyle 缓存 通过 _errorRowStyle + GetErrorRowStyle() 惰性初始化,OnEnable 重置支持皮肤切换
P2-2 MapRoomDataEditor CELL_SIZE 说明 通过 Inspector HelpBox 已补充三段坐标系说明
P2-3 MapLayoutEditorWindow Undo 刷新 通过 OnEnable/OnDisable 正确注册/注销,OnUndoRedo 清除验证缓存并 Repaint
P2-4 ValidateAll 编辑器保护 通过 #if UNITY_EDITOR 包裹完整,构建包不再含 O(N²) 验证逻辑
N3 MinimapHUD Setup 冗余位置 ⚠️ 部分通过 step② 已加 PlaceCell但 step③ 仍重新遍历全部 _cells 包含刚 PlaceCell 完的新格子,新格子被 PlaceCell 两次(详见 R7-N6

Round 6 主要修复落实良好,但 N3 引入了新的小冗余。


二、Round 7 新发现的问题

🔴 R7-N1MapPanel 不响应 Pin 增删事件(真实 UX 缺陷)

位置MapPanel.cs 第 88-92 行

private void OnEnable()
{
    ...
    RenderPins();         // ← 只在打开面板时调用一次
    UpdatePlayerIcon();
    CenterOnCurrentRoom();
    _onMapUpdated?.Subscribe(OnMapUpdated).AddTo(_subs);
}

问题

  • RenderPins() 仅在 OnEnable 调用一次
  • LateUpdate 只刷新玩家图标,从不重新检查 PinsVersion
  • 玩家在地图打开状态下添加/删除 Pin典型操作UI 不会同步刷新

影响:地图面板期间用户的所有 Pin 操作CreatePin/RemovePin在面板关闭并重新打开前视觉上无任何反馈。这是策划/玩家可见的 UX 错误。

修复方案

  • 方案 A轻量、推荐LateUpdate 末尾调用 RenderPins(),已有 _lastPinVersion 脏检查,无版本变化时立即 return无开销
  • 方案 BIPinService 中新增 event Action OnPinsChangedMapPinManagerAddPin/RemovePin 时触发

🔴 R7-N2MapPanel/MinimapHUD 不响应数据库热更(编辑器/工具集成缺陷)

位置MapPanel.cs 第 83-86 行,MinimapHUD.cs 第 56-70 行

// MapPanel
if (_cells.Count == 0)
    BuildGrid();
else
    RefreshAllCells();    // ← 只刷新已存在 _cells 的可见性
// MinimapHUD
BuildSpatialIndex(_mapSvc?.Database);   // ← 仅在 OnEnable 时构建

问题

  • 策划在编辑器中通过 MapLayoutEditorWindow 编辑房间布局或新增房间后,运行中的 MapPanel/MinimapHUD 不会反映变化
  • RefreshAllCells 只更新已有 _cells 集合,新增房间不会被实例化、被删除房间的格子不会被回收
  • 空间索引同样不会重建

影响

  • 策划开发时的迭代体验差:每次编辑数据库都需重启游戏
  • 运行时若支持 Addressable 数据库热更或动态地图生成DLC地图会失效

修复方案

  1. IMapService 新增 event Action OnDatabaseChanged
  2. MapDatabaseSO.OnValidate 中触发该事件(编辑器侧)
  3. MapPanel/MinimapHUD 订阅并执行完整重建

🟡 R7-N3BuildSpatialIndex 在 MinimapHUD 和 MapPlayerTracker 中重复实现

位置MinimapHUD.cs 第 97-108 行 vs MapPlayerTracker.cs 第 67-81 行

两处构建几乎相同的 Dictionary<Vector2Int, string> 索引(玩家追踪和小地图查询)。每个组件都独立构建一次相同数据,浪费内存 + 数据不一致风险(房间数据变化时一方更新另一方未更新)。

修复方案:将索引下沉为 MapDatabaseSO 的内置查询:

// MapDatabaseSO 中
private Dictionary<Vector2Int, string> _cellToRoom;
public string GetRoomIdAtCell(Vector2Int cell) { ... }

或封装为 MapServiceExtensions.GetRoomIdAtCell(this IMapService svc, Vector2Int cell) 集中实现。


🟡 R7-N4MapPinManager.CreatePin 缺少输入校验

位置MapPin.cs 第 61-74 行

public MapPin CreatePin(string roomId, float normX, float normY,
                         PinType type = PinType.Marker, string note = "")
{
    var pin = new MapPin {
        RoomId         = roomId,        // 不验证非空、不验证 roomId 是否存在
        NormalizedPosX = normX,         // 不 Clamp01
        NormalizedPosY = normY,
        ...
    };
    AddPin(pin);
    return pin;
}

问题

  • roomId 可空字符串/无效 ID导致 MapPanel.RenderPinsTryGetValue 失败但 Pin 仍存在于存档
  • normX/normY 可为负数或大于 1Pin 显示在房间格子外
  • 错误数据持久化到存档,后续清理困难

修复方案:参数校验 + Mathf.Clamp01 + RoomId 存在性检查(可选警告)。


🟡 R7-N5MapManager.OnRoomEntered 区域变化日志缺失

位置MapManager.cs 第 70-82 行

private void OnRoomEntered(string roomId)
{
    bool changed = _exploredRooms.Add(roomId);
    if (changed) _onMapUpdated?.Raise(roomId);

    var regionId = _database?.GetRoom(roomId)?.RegionId;
    if (!string.IsNullOrEmpty(regionId) && regionId != _currentRegionId)
    {
        _currentRegionId = regionId;
        _onRegionChanged?.Raise(regionId);
    }
}

问题

  • 首次启动游戏(无存档加载)时,玩家首次进入某区域,_currentRegionId 由 null 变成 regionId,会触发一次 EVT_RegionChanged
  • 加载存档后OnLoad 只恢复 _exploredRooms/_mappedRooms不恢复 _currentRegionId
  • 玩家加载存档进入游戏时,第一次进入房间会再次触发 EVT_RegionChanged导致「读档后第一次进房显示区域名渐显」UX 异常

修复方案OnLoad 同步恢复 _currentRegionId(可基于玩家当前位置或最近进入的房间推导)。


🟡 R7-N6MinimapHUD step③ 对新格子的二次 PlaceCell

位置MinimapHUD.cs 第 175-194 行

foreach (var roomId in _roomsInViewBuffer)
{
    ...
    PlaceCell(cell, room);  // ← step② 中刚调用
    _cells[roomId] = cell;
}

// ③ 重定位所有格子
foreach (var (id, cell) in _cells)
{
    ...
    PlaceCell(cell, r);     // ← 新格子在此被再次 PlaceCell
}

问题:刚在 step② 中通过 PlaceCell 设置过位置的新格子,在 step③ 中又被 PlaceCell 一遍。虽然结果幂等,但浪费写入。

修复方案step② 不调用 PlaceCell信任 step③ 统一处理),或 step③ 跳过本帧新增的格子。后者更清晰:

var newlyAdded = new HashSet<string>();
// step②
foreach (var roomId in _roomsInViewBuffer) {
    if (_cells.ContainsKey(roomId)) continue;
    ...
    PlaceCell(cell, room);
    newlyAdded.Add(roomId);
    _cells[roomId] = cell;
}
// step③ 重定位剩余存量格子
foreach (var (id, cell) in _cells) {
    if (newlyAdded.Contains(id)) continue;
    ...
}

🟢 R7-N7MapInputHandler 仍使用旧版 Input SystemRound 6 N4 未修复)

位置MapInputHandler.cs 第 42-43 行

float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");

新 Input System 项目此代码失效,已在 Round 6 标注但未修复,本轮再次确认。


🟢 R7-N8MapPlayerTracker._worldUnitsPerCell 字段无范围保护

位置MapPlayerTracker.cs 第 25 行

[SerializeField] private float _worldUnitsPerCell = 18f;

若策划误填 0 或负值,会导致:

  • WorldToCell 除以 0 产生 Infinity,进而 Mathf.FloorToInt 异常
  • 归一化位置计算 worldSize.x 为 0Mathf.Max(1f, ...) 兜底但语义错误

修复方案[Min(0.01f)]OnValidate 保护。


🟢 R7-N9MapPin.cs 文件名与类名不一致(历史遗留)

MapPin.cs 内部包含 MapPinManager(主类)+ PinSpriteEntry。VS Code/Rider 全局搜索 MapPinManager 文件名找不到,新人查找代码体验差。

修复方案:因 Unity .meta GUID 绑定限制,安全做法是新建 MapPinManager.cs 仅含一个 // see MapPin.cs 注释引导(不安全的做法是改 .meta 但风险高);或保持现状并在 README 注明。


🟢 R7-N10MapDatabaseSO._index OnDisable 清理的潜在 NRE

位置MapRoomDataSO.cs 第 92 行

private void OnDisable() => _index = null;

ScriptableObject 在域重载Domain Reload/编辑器停止播放时执行 OnDisable。若同时有运行中的协程或异步任务持有 SO 引用并即将调用 GetRoom,会触发索引重建(线性 LINQ首帧停止时性能尖峰 + 短暂 NRE 风险。

修复方案:清理改为 lock 包裹的重建逻辑,或保留缓存让 GC 自然回收。当前实现风险低但非完全无害。


三、本轮独立评分

维度 Round 6 Round 7 独立 差值 说明
架构解耦 9.0 8.5 -0.5 R7-N3索引重复实现扣 0.5
性能 8.0→8.5 8.5 维持 P1-1 已修复,新发现的 N6 冗余写入影响微小
编辑器扩展 8.5→9.0 9.0 维持 P2 修复落实
数据设计 8.0 7.5 -0.5 R7-N4无输入校验+ R7-N5OnLoad 不恢复 region扣分
功能完整性 8.0 7.5 -0.5 R7-N1Pin 不响应增删)是真实功能缺陷
代码质量 8.5 8.5 0 整体仍达标
可扩展性 8.0 7.5 -0.5 R7-N2无热更事件限制工具化扩展
策划友好度 8.0 7.5 -0.5 R7-N2 影响策划迭代体验

加权总分

架构解耦     ████████░░  8.5/10
性能         ████████░░  8.5/10
编辑器扩展   █████████░  9.0/10
数据设计     ███████░░░  7.5/10
功能完整性   ███████░░░  7.5/10  ← R7-N1 Pin 响应缺陷
代码质量     ████████░░  8.5/10
可扩展性     ███████░░░  7.5/10  ← R7-N2 无热更
策划友好度   ███████░░░  7.5/10
─────────────────────────────
加权总分      82/100

Round 6 报告预估修复后 87 分,本轮独立审查实际 82 分——差距源于 Round 6 评估视野未覆盖 N1/N2/N3 这三类"运行时-编辑器"协同问题。


四、严重程度排序与修复优先级

P0立即修复影响真实 UX/功能)

ID 问题 工时估计
R7-N1 MapPanel 不响应 Pin 增删 LateUpdate 加 RenderPins 调用)
R7-N5 OnLoad 不恢复 _currentRegionId OnLoad 推导一行)

P1影响工具化与扩展

ID 问题 工时估计
R7-N2 MapPanel/MinimapHUD 不响应数据库热更 IMapService 新增事件 + 订阅)
R7-N3 BuildSpatialIndex 重复实现 中(下沉到 MapDatabaseSO 或扩展方法)
R7-N4 CreatePin 缺输入校验 小(参数 Clamp + 警告)

P2代码质量与健壮性

ID 问题 工时估计
R7-N6 step③ 对新格子二次 PlaceCell newlyAdded 集合跳过)
R7-N7 MapInputHandler 旧版 Input 中(替换为 IInputService
R7-N8 _worldUnitsPerCell 无范围保护 极小([Min]

P3历史遗留 / 边缘场景)

ID 问题
R7-N9 MapPin.cs 文件名与类名不一致
R7-N10 _index OnDisable 清理潜在 NRE

五、对标空洞骑士 / 丝之歌的差距分析

能力 对标标准 当前状态 差距
三级可见性Unknown/Explored/Mapped ✓ 完整
MapFragment 购买揭示 ✓ 接口已就位
自定义 Pin 标记 ✓ 多种类型 ✓ 但地图打开时不响应增删 N1
区域名渐显 ✓ 但读档后会异常触发 N5
存档点显示完整地图 ✓ via SetMapped API
房间非矩形轮廓 ✓ RoomOutlineTex 字段
小地图视野跟随玩家 ✓ 含空间索引优化
图标平滑跟随(非格子跳跃) ✓ Round 4 已修复
运行时缩放/平移 ✓ 滚轮+键盘+手势 ⚠️ 仅滚轮+键盘 无手势/手柄
探索进度统计 UI API 完整但无 UI 显示 中等
区域配色主题 ✓ 各区域有专属配色 编辑器随机配色,运行时无 大(需 RegionSO
数据库热更 不响应 N2

六、总评

当前最终得分:82/100

优势

  • 完整的接口/ServiceLocator 解耦体系
  • 编辑器三工具SO Inspector + Custom Editor + 全局布局窗口)功能扎实
  • 热路径性能已优化(脏检查 + 缓存 + 空间索引)
  • 数据驱动设计支持策划独立迭代

真实差距(按修复成本排序)

  1. N1 Pin 不响应增删 — 1 行修复,是当前最高 ROI 的修复点
  2. N5 OnLoad 不恢复 region — 1 行修复,避免读档体验异常
  3. N4 CreatePin 无校验 — 3 行修复,避免脏数据进入存档
  4. N6 step③ 二次 PlaceCell — 5 行修复,性能小优化
  5. N3 索引重复实现 — 重构DRY 改善
  6. N2 数据库热更事件 — 中等改动,但显著提升策划工作流

仍未弥补的功能缺口Round 5/6 已标注,仍在):

  • 探索进度 UIAPI 有UI 无)
  • RegionSO区域配色/名称集中配置)
  • MapDesignSpec.md 策划文档
  • Pinch 缩放、手柄输入

如果完成 P0+P1N1/N5/N2/N3/N4评分预期回到 88-90 分;再补 RegionSO + 进度 UI 可达 93+


Round 7 旨在矫正 Round 6 在「运行时-编辑器协同」「事件响应完整性」两个视角的盲区。本轮重点发现的 N1/N2/N5 是 Round 1-6 全部漏检的真实功能/UX 缺陷,建议立即修复。