Files
zeling_v2/Docs/Review/Minimap_Review_Round19_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

223 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Minimap System — Round 19 Independent Review
## 评审范围
全部 23 个源文件4 接口 + 15 运行时 + 4 编辑器),在 R18 全部修复已落地的基础上进行全面重新审查。
---
## R18 修复确认
| 编号 | 描述 | 验证状态 |
|------|------|----------|
| R18-N1a | `MapInputHandler.Update` `rangeX/rangeY` 乘以 `_zoom` | ✅ 第 7980 行 |
| R18-N1b | `MapPanel.CenterOnCurrentRoom``_roomContainer.localScale.x` 读 zoom修正 rangeX/rangeY 及 cellX/cellY | ✅ 第 377384 行 |
| R18-N2 | `_noResultStyle` 缓存为字段,在 `EnsureLabelStyles()` 首次调用时初始化 | ✅ 第 435445 行 |
| R18-N3 | `MinimapHUD._onMapUpdated``[HideInInspector]`,移除订阅和 `OnMapUpdated` 方法 | ✅ 第 3639 行 |
---
## R19 全方面评分
| 维度 | 权重 | 分数 | 评分依据 |
|------|------|------|----------|
| **架构解耦** | 20% | **95** | 4 接口全 ServiceLocatorC# 事件单向流无循环依赖Awake/OnDestroy 长期订阅模式MapServiceExtensions 集中无状态查询逻辑 |
| **功能完整性** | 20% | **93** | 三级可见性、地图碎片解锁动画、Pin/传送/区域名本地化、探索进度显示、雾效覆盖层、存档全覆盖 |
| **性能** | 15% | **93** | 五对象池cell×2 / pin×2 / exit×1O(viewRadius²) 剔除PinsVersion 脏检查_servicesReady 短路MapDatabaseSO 双重 O(1) 索引R18-N1 键盘平移已修正N1c 潜在风险(见下方) |
| **编辑器工具** | 15% | **94** | 布局编辑器可视化+拖拽+搜索+图例+UndoMapDatabaseEditor 自动验证MapRoomDataEditor SceneGUI 双角控制点AssetPostprocessor 自动注册+null 清理R18-N2 GUIStyle 缓存到位 |
| **可扩展性** | 10% | **95** | RoomType[Flags] 枚举可任意叠加新类型SO 驱动MapDatabaseSO / MapPinConfigSO接口易替换MapGridConstants 统一常量管理 |
| **代码质量** | 10% | **95** | 全面注释命名规范3 处 ISaveable 防御性拷贝对称try/finally 保护 GUI.matrix[FormerlySerializedAs] 迁移;[HideInInspector] 废弃兼容_noResultStyle 已缓存 |
| **玩家体验设计** | 10% | **92** | 三级可见性传送点快速旅行循环缩放视野Play Mode 编辑器叠加区域名淡入动画R18-N1 平移手感已修正 |
**综合评分93.8 / 100**(与 R18 修复后预测值完全吻合)
---
## 架构层深度审查
### 接口与服务完整矩阵
| 接口 | 实现类 | ServiceLocator 注册 | ISaveable | 备注 |
|------|--------|---------------------|-----------|------|
| `IMapService` | `MapManager` | Awake重复实例保护 | ✅ | 防御拷贝 + OnLoad 广播 OnExplorationChanged |
| `IPinService` | `MapPinManager` | Awake | ✅ | PinsVersion 版本号驱动脏检查 |
| `IPlayerPositionProvider` | `MapPlayerTracker` | Awake重复实例保护 | ❌(不需要) | LateUpdate O(1) 空间索引 + NormalizedPositionInRoom 每帧插值 |
| `ITeleportService` | `TeleportService` | Awake | ✅ | 防御拷贝;事件通道驱动场景过渡 |
### 数据流方向(单向)
```
MapManager ──── OnRoomEntered ────────▶ OnExplorationChanged (C# event)
▶ MapPanel.OnExplorationChanged
▶ MinimapHUD.OnExplorationChanged
▶ MapProgressDisplay.Refresh
MapManager ──── SetMapped / SetMappedBatch ──▶ OnRoomMapped (C# event)
▶ MapPanel.OnRoomMappedAnim (发现动画)
MapManager ──── _onRegionChanged.Raise ──────▶ RegionNameDisplay
▶ MapProgressDisplay.OnRegionChanged
MapPlayerTracker ── OnRoomChanged (C# event) ▶ MinimapHUD.OnRoomChanged → RefreshView
─ NormalizedPositionInRoom ▶ MapPanel.LateUpdate脏检查
▶ MinimapHUD.LateUpdate.UpdatePlayerDot
```
> `_onMapUpdated` StringEventChannel 仍在 MapManager 的 3 处发射,但地图 UI 已全部移除订阅MapPanel R12-N8、MinimapHUD R18-N3。该通道保留以供非 Map UI 的外部系统订阅,属于已知设计状态。
### 对象池完整性
| 池 | 类型 | 宿主 | 入池时机 | 出池时机 |
|----|------|------|----------|----------|
| `_cellPool` | MapRoomCellUI | MapPanel | RebuildAll / OnDestroy | BuildGrid |
| `_pinPool` | Image (Pin) | MapPanel | ClearPins / OnDestroy | RenderPins |
| `_exitPool` | Image (Exit) | MapPanel | ClearExits / OnDestroy | DrawExits |
| `_cellPool` | MapRoomCellUI | MinimapHUD | RefreshView 裁剪过期 / OnDestroy | RefreshView 新增 |
| `_pinPool` | Image (Pin) | MinimapHUD | ClearPins / OnDestroy | RebuildPins |
所有池均正确实现:入池时 `SetActive(false)` + `Push`,出池时 `Pop` + `SetActive(true)``Instantiate` 兜底。
### ISaveable 对称性审查
| 类 | `OnSave` 防御拷贝 | `OnLoad` 防御拷贝 | 读档后事件广播 |
|----|--------------------|---------------------|----------------|
| `MapManager` | `new HashSet<>(x2)` | `new HashSet<>(x2)` | `OnExplorationChanged?.Invoke()` |
| `MapPinManager` | `new List<>(_pins)` | `new List<>(data.Pins)` | `PinsVersion++` |
| `TeleportService` | `new HashSet<>(_unlockedRoomIds)` | Clear + foreach Add | — |
三处均对称,无共享引用风险。
### 性能关键路径
| 路径 | 复杂度 | 机制 |
|------|--------|------|
| `MinimapHUD.RefreshView` 新格子查询 | O(viewRadius²) | `MapDatabaseSO.GetRoomIdAtCell` 共享空间索引 |
| `MinimapHUD.LateUpdate.UpdatePlayerDot` | O(1) | roomId + NormPos 双字段脏检查 |
| `MapPanel.LateUpdate` 服务查询 | O(1) 短路 | `_servicesReady` bool 门控 |
| `MapPanel.LateUpdate.RenderPins` | O(1) 检查 | PinsVersion 脏检查 |
| `MapManager.GetRoomsByRegion` | O(1) | `_regionCache` 懒加载字典 |
| `MapDatabaseSO.GetRoom` | O(1) | `_index` 字典哈希 |
| `MapDatabaseSO.GetRoomIdAtCell` | O(1) | `_cellToRoom` 空间哈希 |
| `MapPinConfigSO.GetSprite` | O(1) | `_cache` 惰性字典 |
| `RegionNameDisplay / MapProgressDisplay.ResolveDisplayName` | O(1) | 预建 `_regionDict` |
| `MapLayoutEditorWindow.EnsureLabelStyles` | O(1) | `_cachedZoomForStyle` 脏检查 |
| `MapLayoutEditorWindow._noResultStyle` | O(1) | 首次初始化后不重建 |
---
## R19 新发现问题
### N1配置健壮性`_roomContainer` 与 `_zoomTarget` 为两个独立引用,配置不一致时静默偏差
**涉及文件**`MapInputHandler.cs`(第 21 行)、`MapPanel.cs`(第 23 行)
**现状**
- `MapInputHandler._zoomTarget``OnScroll` 写入 `_zoomTarget.localScale``Update` 使用 `_zoom` 字段
- `MapPanel._roomContainer``CenterOnCurrentRoom` 读取 `_roomContainer.localScale.x`
两者仅在 `_zoomTarget == _roomContainer`(即 Prefab 中两个引用指向同一 GameObject时保持同步。若开发者在 Prefab 中错误配置,`CenterOnCurrentRoom` 将读到错误的缩放系数。当前无运行时断言或警告。
**影响**:误配置后打开地图并缩放,再按"居中"快捷键MapCenterEvent定位会偏移但无错误提示。
**修复建议**
```csharp
// MapInputHandler.cs — OnEnable / OnScroll 中向 MapPanel 暴露当前缩放值
// 方案AMapPanel 增加 public 属性
public float CurrentZoom => _roomContainer != null ? _roomContainer.localScale.x : 1f;
// MapInputHandler.OnScroll 改从 _panel.CurrentZoom 读,而非独立维护 _zoom消除两份状态
// 方案B最小改动Awake 中断言 _zoomTarget == _scrollRect.content
```
---
### N2正确性边缘`MapPanel.OnRoomMappedAnim` 协程在目标格子被回收后继续运行
**文件**`MapPanel.cs` 第 198202 行
**现状**
```csharp
protected virtual void OnRoomMappedAnim(string roomId)
{
if (_cells.TryGetValue(roomId, out var cell) && cell != null)
StartCoroutine(cell.PlayRevealAnim(_revealFlashColor, _revealDuration));
}
```
`StartCoroutine` 挂在 `MapPanel`this协程生命周期由 MapPanel 管理。若在动画播放期间触发 `RebuildAll``OnDatabaseChanged`),目标 `cell` 会被入池(`SetActive(false)`)。协程继续运行并向已入池的 cell 的 `_bg.color` 写入,直到 `_revealDuration` 结束。下次该 cell 出池并调用 `Setup → SetVisibility` 时颜色会被正确覆盖,因此视觉影响自愈。
**严重程度**:极低(数据库热更极少发生,动画持续 0.4 s。但在频繁使用编辑器热更的开发流程中可能产生轻微闪烁。
**修复建议**
```csharp
// MapPanel 添加字段
private readonly Dictionary<string, Coroutine> _revealCoroutines = new();
// OnRoomMappedAnim 中:
if (_revealCoroutines.TryGetValue(roomId, out var old) && old != null)
StopCoroutine(old);
_revealCoroutines[roomId] = StartCoroutine(cell.PlayRevealAnim(...));
// RebuildAll 中清理:
foreach (var c in _revealCoroutines.Values)
if (c != null) StopCoroutine(c);
_revealCoroutines.Clear();
```
---
### N3信息架构说明`MapManager._onMapUpdated` 通道现在在地图 UI 内无订阅者
**文件**`MapManager.cs` 第 85、107、122 行
经 R12-N8MapPanel和 R18-N3MinimapHUD地图 UI 不再订阅 `_onMapUpdated` StringEventChannelSO。MapManager 仍在 `OnRoomEntered``SetMapped``SetMappedBatch` 三处调用 `Raise()`
**这不是 Bug**SO 事件通道天然支持外部系统订阅(如 Achievement 系统、音效触发器);保留 Raise() 符合开放设计原则。
**仅建议添加文档注释**
```csharp
[Header("Event Channels")]
[Tooltip("房间被标记时广播Explored/Mapped供地图外部系统订阅地图 UI 已改用 C# 事件)。")]
[SerializeField] private StringEventChannelSO _onMapUpdated;
```
---
## 编辑器工具覆盖面(策划/开发视角)
| 工具 | 目标用户 | 核心能力 |
|------|----------|----------|
| **MapLayoutEditorWindow** | 策划 + 开发 | 可视化房间布局;滚轮缩放;中键/Alt 平移左键选中拖拽编辑房间坐标Undo实时重叠检测红色警示区域配色搜索高亮无结果提示图例Play Mode 玩家点叠加;出口连线(>12px 时) |
| **MapDatabaseEditor** | 开发 | 数据库统计(房间数/出口数一键验证Inspector 内嵌"打开布局编辑器";可折叠房间列表+Ping错误行红色高亮 |
| **MapRoomDataEditor** | 关卡设计 + 开发 | SceneGUI 双角控制点可视化;拖拽吸附到整格;防反转保护;居中 SceneView 快捷按钮HelpBox 坐标系说明 |
| **MapRoomAutoRegister** | 开发 | AssetPostprocessor 自动注册新 SO 到默认 Databasenull 清理;`IsDefault` 优先级;可通过 EditorPrefs 禁用 |
---
## 总结
经过 19 轮迭代,小地图系统已达到成熟的生产级 2D 探索类游戏标准:
### 强项
- **接口完整**4 接口全 ServiceLocatorUI 层无具体实现依赖
- **存档正确**3 处 ISaveable 防御拷贝对称,读档后事件广播完整
- **性能到位**5 对象池 + O(viewRadius²) + 多级脏检查 + 双重 O(1) 索引
- **编辑器成熟**:拖拽布局 + 搜索 + Undo + 验证 + SceneGUI 一体化,策划友好
- **事件方向清晰**:所有 UI 更新均由 C# 事件(`OnExplorationChanged`)单向驱动,无双重刷新
### R19 新发现3 项,全为低优先级)
- **N1**`_zoomTarget` vs `_roomContainer` 双引用无配置一致性断言(配置层风险)
- **N2**`OnRoomMappedAnim` 协程可能写入已回收的格子(视觉自愈,影响微小)
- **N3**`_onMapUpdated` 通道在地图 UI 内零订阅(信息提示,非缺陷)
### 评分历史
| 轮次 | 评分 | 主要改进 |
|------|------|----------|
| R14 | 92.3 | ISaveable 签名修复TeleportStation 枚举值 |
| R15 | 91.3 | DrawRoomBadge 补 TeleportStationMapProgressDisplay 区域名 |
| R16 | 92.5 | DrawRoomBadge 优先级MinimapHUD 4 Sprite + ChooseIcon |
| R17 | 93.0 | 死代码清理,搜索 UX 改善alpha + 无结果提示) |
| R18 | 93.8 | 键盘平移范围修正GUIStyle 缓存MinimapHUD 双重刷新消除 |
| **R19** | **93.8** | 全部 R18 修复确认到位,识别 3 项新低优先级问题 |