- 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.
197 lines
9.1 KiB
Markdown
197 lines
9.1 KiB
Markdown
# Minimap System — Round 18 Independent Review
|
||
|
||
## 评审范围
|
||
|
||
全部 23 个源文件(4 接口 + 15 运行时 + 4 编辑器),在 R17 全部修复已落地的基础上进行全面重新审查。
|
||
|
||
---
|
||
|
||
## R17 修复确认
|
||
|
||
| 编号 | 描述 | 验证状态 |
|
||
|------|------|----------|
|
||
| R17-N1 | 删除 `MapLayoutEditorWindow._cachedErrorRoomIds` 死代码字段 | ✅ 已确认,字段已不存在 |
|
||
| R17-N2 | 搜索活跃时非匹配房间 alpha 0.28 → 0.08 | ✅ 已确认,第 335 行 |
|
||
| R17-N3 | 搜索无结果时居中显示提示文本 | ✅ 已确认,第 363–373 行 |
|
||
|
||
---
|
||
|
||
## R18 全方面评分(修复前)
|
||
|
||
| 维度 | 权重 | 分数 | 说明 |
|
||
|------|------|------|------|
|
||
| 架构解耦 | 20% | 95 | 四接口全 ServiceLocator;事件单向流;无循环依赖;服务订阅 Awake 长期持有不随 OnEnable/Disable 失效 |
|
||
| 功能完整性 | 20% | 92 | 三级可见性、Pin、传送、本地化区域名、地图碎片解锁动画、存档一致;全部覆盖 |
|
||
| 性能 | 15% | 91 | 三池(cell/pin/exit)+ O(viewRadius²) 剔除 + PinsVersion 脏检查 + _servicesReady 短路;N1 键盘平移未乘缩放;N3 MinimapHUD 双重刷新 |
|
||
| 编辑器工具 | 15% | 93 | 布局编辑器:拖拽/搜索/图例/Play Mode 叠加/Undo-Redo/AssetPostprocessor 自动注册;R17 UX 修复有效;N2 每帧重建 noResultStyle |
|
||
| 可扩展性 | 10% | 95 | RoomType[Flags] 可任意叠加新类型;SO 驱动;接口易替换;MapPinConfigSO 集中 Pin 配置 |
|
||
| 代码质量 | 10% | 93 | 命名规范、注释充实、防御拷贝三处对称;N2 GUIStyle 每 OnGUI 帧重建 |
|
||
| 玩家体验设计 | 10% | 88 | 键盘平移范围未乘 `_zoom`,缩放后平移速度感知偏差 |
|
||
|
||
**综合(修复前):92.4 / 100**
|
||
|
||
---
|
||
|
||
## R18 新发现问题
|
||
|
||
### N1(中,正确性):`MapInputHandler` 键盘平移范围未乘缩放系数
|
||
|
||
**文件**:`Assets/_Game/Scripts/World/Map/MapInputHandler.cs` 第 76–87 行
|
||
|
||
**现状**:
|
||
```csharp
|
||
Vector2 contentSize = content.rect.size;
|
||
Vector2 viewportSize = viewport.rect.size;
|
||
float rangeX = contentSize.x - viewportSize.x;
|
||
float rangeY = contentSize.y - viewportSize.y;
|
||
```
|
||
|
||
`RectTransform.rect.size` 是本地空间(未缩放)的尺寸。当 `_zoomTarget.localScale = (_zoom, _zoom, 1)` 改变后,内容在屏幕上的视觉尺寸为 `contentSize × _zoom`,ScrollRect 的实际可滚动范围也随之放大。
|
||
|
||
当前代码中 `rangeX = contentSize.x - viewportSize.x` 不含缩放因子,导致:
|
||
- 放大时(`_zoom > 1`):每帧移动距离 `delta.x / rangeX` 被高估,平移速度感知快于预期;
|
||
- 缩小时(`_zoom < 1`):平移速度感知慢于预期;
|
||
- 同样问题影响 `MapPanel.CenterOnCurrentRoom` 中的 `rangeX / rangeY` 计算。
|
||
|
||
**修复**:
|
||
```csharp
|
||
// MapInputHandler.Update
|
||
float rangeX = contentSize.x * _zoom - viewportSize.x;
|
||
float rangeY = contentSize.y * _zoom - viewportSize.y;
|
||
|
||
// MapPanel.CenterOnCurrentRoom — 同步修复 rangeX/rangeY
|
||
float rangeX = contentSize.x * /* zoom */ - viewSize.x; // 需从 MapInputHandler 传入或独立持有
|
||
```
|
||
> **注意**:`MapPanel.CenterOnCurrentRoom` 中无法直接读取 `MapInputHandler._zoom`;最简方案是从 `_roomContainer.localScale.x` 读取当前缩放值。
|
||
|
||
---
|
||
|
||
### N2(低,编辑器性能):`noResultStyle` 每次 `OnGUI` 重新分配
|
||
|
||
**文件**:`Assets/_Game/Scripts/Editor/World/Map/MapLayoutEditorWindow.cs` 第 365–371 行
|
||
|
||
**现状**:
|
||
```csharp
|
||
var noResultStyle = new GUIStyle(EditorStyles.boldLabel)
|
||
{
|
||
alignment = TextAnchor.MiddleCenter,
|
||
normal = { textColor = new Color(1f, 0.8f, 0.2f, 0.8f) },
|
||
fontSize = 13,
|
||
};
|
||
```
|
||
|
||
此段仅在"搜索有内容 && 无匹配"时执行,但每次 `OnGUI`(编辑器 60 fps 或更高频率)均分配一个新 `GUIStyle` 对象。与已有的 `_roomLabelStyle`、`_badgeBossStyle`、`_badgeNormalStyle` 的缓存模式不一致。
|
||
|
||
**修复**:添加 `_noResultStyle` 字段,在 `EnsureLabelStyles()` 中一并初始化(`fontSize = 13` 固定,无需随 `_zoom` 变化)。
|
||
|
||
---
|
||
|
||
### N3(低,架构一致性):`MinimapHUD` 保留 `_onMapUpdated` 订阅导致双重刷新
|
||
|
||
**文件**:`Assets/_Game/Scripts/World/Map/MinimapHUD.cs` 第 90 行 + 第 205–209 行
|
||
|
||
**现状**:
|
||
```csharp
|
||
// OnEnable:
|
||
_onMapUpdated?.Subscribe(OnMapUpdated).AddTo(_subs);
|
||
|
||
// OnMapUpdated:
|
||
private void OnMapUpdated(string roomId)
|
||
{
|
||
if (_cells.TryGetValue(roomId, out var cell))
|
||
cell.SetVisibility(_mapSvc.GetVisibility(roomId));
|
||
}
|
||
```
|
||
|
||
`MapPanel` 已在 R12-N8 移除 `_onMapUpdated` 订阅,改由 `OnExplorationChanged` 统一处理。但 `MinimapHUD` 未跟进:
|
||
|
||
每次房间被探索或标记时,`MapManager` 先后触发:
|
||
1. `_onMapUpdated?.Raise(roomId)` → `MinimapHUD.OnMapUpdated(roomId)` — 更新单个格子
|
||
2. `OnExplorationChanged?.Invoke()` → `MinimapHUD.OnExplorationChanged()` — 刷新全部活跃格子
|
||
|
||
步骤 1 的工作完全被步骤 2 覆盖,造成重复写入。对于小视野(4–9 格)影响可忽略,但在视野半径较大时每次探索会多做一次无效的单格更新。
|
||
|
||
**修复**:从 `MinimapHUD` 移除 `_onMapUpdated` SerializeField 及其订阅,保留 `[HideInInspector, SerializeField]` 字段并标注废弃说明(与 MapPanel 的处理方式对齐,保留 Prefab 序列化兼容性),删除 `OnMapUpdated` 私有方法。
|
||
|
||
---
|
||
|
||
## 架构深度审查
|
||
|
||
### 接口层(4 接口)
|
||
|
||
| 接口 | 实现 | 注册方式 | 状态 |
|
||
|------|------|----------|------|
|
||
| `IMapService` | `MapManager` | `ServiceLocator.Register<T>` in Awake | ✅ 完整 |
|
||
| `IPinService` | `MapPinManager` | ServiceLocator in Awake | ✅ 完整 |
|
||
| `IPlayerPositionProvider` | `MapPlayerTracker` | ServiceLocator in Awake | ✅ 完整 |
|
||
| `ITeleportService` | `TeleportService` | ServiceLocator in Awake | ✅ 完整 |
|
||
|
||
### 存档一致性(3 处 ISaveable)
|
||
|
||
| 实现类 | 防御拷贝 | 覆盖加载 | 广播更新 |
|
||
|--------|----------|----------|----------|
|
||
| `MapManager.OnSave/OnLoad` | ✅ `new HashSet<>(_x)` | ✅ `new HashSet<>` 后赋值 | ✅ `OnExplorationChanged?.Invoke()` |
|
||
| `MapPinManager.OnSave/OnLoad` | ✅ `new List<>(_pins)` | ✅ `new List<>` 后赋值 | ✅ `PinsVersion++` |
|
||
| `TeleportService.OnSave/OnLoad` | ✅ `new HashSet<>(_unlockedRoomIds)` | ✅ Clear + foreach Add | ✅(通过传送服务事件) |
|
||
|
||
### 对象池完整性
|
||
|
||
| 池 | 容纳类型 | 所在组件 | 入池时机 |
|
||
|----|----------|----------|----------|
|
||
| `_cellPool` | MapRoomCellUI | MapPanel | RebuildAll |
|
||
| `_pinPool` | Image (Pin) | MapPanel | ClearPins |
|
||
| `_exitPool` | Image (Exit) | MapPanel | ClearExits |
|
||
| `_cellPool` | MapRoomCellUI | MinimapHUD | RefreshView 裁剪过期格子 |
|
||
| `_pinPool` | Image (Pin) | MinimapHUD | ClearPins |
|
||
|
||
### 性能关键路径
|
||
|
||
| 路径 | 复杂度 | 机制 |
|
||
|------|--------|------|
|
||
| `MinimapHUD.RefreshView` 新格子查询 | O(viewRadius²) | `MapDatabaseSO.GetRoomIdAtCell` 共享空间索引 |
|
||
| `MapPanel.LateUpdate` 服务查询 | O(1) 短路 | `_servicesReady` bool 门控 |
|
||
| `MapPanel.LateUpdate` Pin 渲染 | O(1) 检查 | `PinsVersion` 脏检查 |
|
||
| `MapManager.GetRoomsByRegion` | O(1) | `_regionCache` 懒加载字典 |
|
||
| `MapDatabaseSO.GetRoom` | O(1) | `_index` 字符串→SO 哈希索引 |
|
||
| `MapDatabaseSO.GetRoomIdAtCell` | O(1) | `_cellToRoom` 格子坐标→ID 空间索引 |
|
||
| `MinimapHUD.UpdatePlayerDot` | O(1) | roomId + NormPos 双字段脏检查 |
|
||
| `MapLayoutEditorWindow.EnsureLabelStyles` | O(1) | `_cachedZoomForStyle` 脏检查 |
|
||
|
||
### 编辑器工具覆盖面
|
||
|
||
| 工具 | 能力 |
|
||
|------|------|
|
||
| `MapLayoutEditorWindow` | 可视化布局预览;滚轮缩放;中键/Alt 平移;房间拖拽编辑(Undo);区域配色;验证 + 错误高亮;搜索高亮(R17 UX 改善);图例;Play Mode 玩家点叠加 |
|
||
| `MapDatabaseEditor` | Inspector 内嵌"在布局编辑器中打开"按钮 |
|
||
| `MapRoomDataEditor` | SceneGUI 双角控制点可视化 GridPosition/GridSize |
|
||
| `MapRoomAutoRegister` | `AssetPostprocessor` 自动注册新 SO 到默认 Database;删除 null 清理;可禁用 |
|
||
|
||
---
|
||
|
||
## R18 修复预期评分
|
||
|
||
| 维度 | 权重 | 当前 | 修复后 | 变化 |
|
||
|------|------|------|--------|------|
|
||
| 架构解耦 | 20% | 95 | 95 | — |
|
||
| 功能完整性 | 20% | 92 | 92 | — |
|
||
| 性能 | 15% | 91 | 93 | +2(N1+N3)|
|
||
| 编辑器工具 | 15% | 93 | 94 | +1(N2)|
|
||
| 可扩展性 | 10% | 95 | 95 | — |
|
||
| 代码质量 | 10% | 93 | 95 | +2(N2+N3 架构一致)|
|
||
| 玩家体验设计 | 10% | 88 | 92 | +4(N1 修复后平移手感正确)|
|
||
|
||
**综合(修复前):92.4 / 100 → 修复后:93.8 / 100**
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
经过 18 轮迭代,小地图系统整体架构已达到生产级 2D 探索类游戏标准:
|
||
|
||
- **接口完整**:四接口零具体类依赖,ServiceLocator 完全解耦
|
||
- **存档对称**:三处 ISaveable 均实现防御拷贝,读档后广播正确事件
|
||
- **性能到位**:五池 + O(viewRadius²) + 多级脏检查 + _servicesReady 短路
|
||
- **编辑器成熟**:拖拽、搜索、Undo、Play Mode 叠加、自动注册一体化
|
||
|
||
R18 新发现 3 项问题均为低/中优先级,不影响功能正确性(N1 影响手感,N2/N3 为轻微性能与架构一致性问题)。
|