- 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.
223 lines
12 KiB
Markdown
223 lines
12 KiB
Markdown
# Minimap System — Round 19 Independent Review
|
||
|
||
## 评审范围
|
||
|
||
全部 23 个源文件(4 接口 + 15 运行时 + 4 编辑器),在 R18 全部修复已落地的基础上进行全面重新审查。
|
||
|
||
---
|
||
|
||
## R18 修复确认
|
||
|
||
| 编号 | 描述 | 验证状态 |
|
||
|------|------|----------|
|
||
| R18-N1a | `MapInputHandler.Update` `rangeX/rangeY` 乘以 `_zoom` | ✅ 第 79–80 行 |
|
||
| R18-N1b | `MapPanel.CenterOnCurrentRoom` 从 `_roomContainer.localScale.x` 读 zoom,修正 rangeX/rangeY 及 cellX/cellY | ✅ 第 377–384 行 |
|
||
| R18-N2 | `_noResultStyle` 缓存为字段,在 `EnsureLabelStyles()` 首次调用时初始化 | ✅ 第 435–445 行 |
|
||
| R18-N3 | `MinimapHUD._onMapUpdated` 改 `[HideInInspector]`,移除订阅和 `OnMapUpdated` 方法 | ✅ 第 36–39 行 |
|
||
|
||
---
|
||
|
||
## R19 全方面评分
|
||
|
||
| 维度 | 权重 | 分数 | 评分依据 |
|
||
|------|------|------|----------|
|
||
| **架构解耦** | 20% | **95** | 4 接口全 ServiceLocator;C# 事件单向流;无循环依赖;Awake/OnDestroy 长期订阅模式;MapServiceExtensions 集中无状态查询逻辑 |
|
||
| **功能完整性** | 20% | **93** | 三级可见性、地图碎片解锁动画、Pin/传送/区域名本地化、探索进度显示、雾效覆盖层、存档全覆盖 |
|
||
| **性能** | 15% | **93** | 五对象池(cell×2 / pin×2 / exit×1);O(viewRadius²) 剔除;PinsVersion 脏检查;_servicesReady 短路;MapDatabaseSO 双重 O(1) 索引;R18-N1 键盘平移已修正;N1c 潜在风险(见下方) |
|
||
| **编辑器工具** | 15% | **94** | 布局编辑器可视化+拖拽+搜索+图例+Undo;MapDatabaseEditor 自动验证;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 暴露当前缩放值
|
||
// 方案A:MapPanel 增加 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` 第 198–202 行
|
||
|
||
**现状**:
|
||
```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-N8(MapPanel)和 R18-N3(MinimapHUD)后,地图 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 到默认 Database;null 清理;`IsDefault` 优先级;可通过 EditorPrefs 禁用 |
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
经过 19 轮迭代,小地图系统已达到成熟的生产级 2D 探索类游戏标准:
|
||
|
||
### 强项
|
||
- **接口完整**:4 接口全 ServiceLocator,UI 层无具体实现依赖
|
||
- **存档正确**: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 补 TeleportStation,MapProgressDisplay 区域名 |
|
||
| R16 | 92.5 | DrawRoomBadge 优先级,MinimapHUD 4 Sprite + ChooseIcon |
|
||
| R17 | 93.0 | 死代码清理,搜索 UX 改善(alpha + 无结果提示) |
|
||
| R18 | 93.8 | 键盘平移范围修正,GUIStyle 缓存,MinimapHUD 双重刷新消除 |
|
||
| **R19** | **93.8** | 全部 R18 修复确认到位,识别 3 项新低优先级问题 |
|