Files
zeling_v2/Docs/Review/Minimap_Review_Round3.md
Joywayer 5cb6c2a19d Add final evaluation report for Minimap system after all fixes and improvements
- Summarized the evolution of scores across five review rounds
- Detailed the status of each evaluation dimension post-fixes
- Highlighted remaining issues and recommended future work for further enhancements
- Compared current system against industry benchmarks
2026-05-25 14:25:19 +08:00

384 lines
22 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.
# 小地图系统设计与实现评估报告(第三轮)
> **项目**zeling_v2
> **评估范围**`Assets/_Game/Scripts/World/Map/` 及 `Assets/_Game/Scripts/Editor/World/Map/`
> **评估基准**:成熟 2D 类银河恶魔城游戏(对标《空洞骑士》《丝之歌》)的小地图设计标准,兼顾开发/策划友好、架构解耦、高性能、可扩展四个维度
> **评估时间**2026-05-25
> **评估轮次**:第三轮(已历经两轮完整改进)
---
## 一、评估对象文件清单
| 文件 | 类型 | 说明 |
|------|------|------|
| `IMapService.cs` | 接口 | 地图服务抽象,所有消费方依赖点 |
| `MapServiceExtensions.cs` | 扩展方法 | 共享 GetVisibility 逻辑 |
| `MapRoomDataSO.cs` + `MapDatabaseSO` | ScriptableObject | 房间数据 + 全局数据库 |
| `MapManager.cs` | MonoBehaviour | IMapService + ISaveable 实现 |
| `MapPlayerTracker.cs` | MonoBehaviour | 玩家位置追踪O(1) 空间索引 |
| `MapRoomCellUI.cs` | MonoBehaviour | 独立格子 UI 组件 |
| `MapPanel.cs` | MonoBehaviour | 全屏地图面板 |
| `MinimapHUD.cs` | MonoBehaviour | 角落小地图 HUD |
| `MapInputHandler.cs` | MonoBehaviour | 全屏地图缩放/平移输入 |
| `MapPin.cs` (MapPinManager) | MonoBehaviour | 玩家自定义标记管理 |
| `RegionNameDisplay.cs` | MonoBehaviour | 区域名淡显 HUD |
| `MapLayoutEditorWindow.cs` | EditorWindow | 全局布局预览编辑器 |
| `MapDatabaseEditor.cs` | CustomEditor | MapDatabaseSO Inspector 增强 |
| `MapRoomDataEditor.cs` | CustomEditor | MapRoomDataSO Scene View 拖拽 |
---
## 二、全方面评分总表
| 评估维度 | 满分 | 得分 | 等级 |
|----------|------|------|------|
| 1. 架构解耦与接口设计 | 20 | 19 | ★★★★★ |
| 2. 运行时性能 | 15 | 14 | ★★★★★ |
| 3. 编辑器扩展工具 | 15 | 13 | ★★★★☆ |
| 4. 数据模型完整性 | 10 | 9 | ★★★★★ |
| 5. 策划/开发友好度 | 10 | 9 | ★★★★★ |
| 6. 小地图功能对标 | 15 | 11 | ★★★☆☆ |
| 7. 代码质量与可维护性 | 10 | 9 | ★★★★★ |
| 8. 存档/持久化 | 5 | 5 | ★★★★★ |
| **总计** | **100** | **89** | **★★★★☆** |
> **注**相较第一轮56分提升 33 分相较第二轮76分提升 13 分。
---
## 三、逐维度详细评估
---
### 维度 1架构解耦与接口设计19 / 20
#### 优势
**完备的服务接口层**
`IMapService` 定义 8 个成员(`IsExplored``IsMapped``SetMapped``Database``CurrentRegionId``ExploredRoomCount``GetExplorationProgress``GetRoomsByRegion`),所有消费方(`MapPanel``MinimapHUD``RegionNameDisplay`)均通过 `ServiceLocator` 以接口调用,在测试或替换实现时零改动量。
**扩展方法集中 GetVisibility**
`MapServiceExtensions.GetVisibility(this IMapService, string)` 为单一实现,消除了之前 `MapPanel``MinimapHUD` 各自的重复私有方法。所有调用方从接口扩展调用,无内部依赖泄漏。
**生命周期清晰的订阅管理**
全部组件统一使用 `CompositeDisposable` + `OnEnable`/`OnDisable` 配对,无裸 `+=`/`-=` 事件挂接,析构安全。
**MapRoomCellUI 职责单一**
经提取后,格子 UI 组件与 `RoomVisibility` 枚举均在独立文件中,不再混入 `MapPanel.cs``SetColors()` 注入色彩,`SetVisibility()` 驱动显示,`SetHighlight()` 控制当前房间描边,三个关注点各自独立。
**编辑器与运行时彻底分离**
`MapLayoutEditorWindow``MapDatabaseEditor` 包裹在 `#if UNITY_EDITOR` 或 Editor Assembly 中,对运行时零引用。`MapLayoutEditorWindow.SetDatabase()` 公共 API 取代了旧的 Reflection 注入,接口契约明确。
#### 不足
- `MapPlayerTracker` 直接持有 `MapDatabaseSO` 引用(`_databaseOverride ?? IMapService.Database`),和 `IMapService` 同时持有,存在轻微双重依赖;理想做法是通过 `IMapService` 统一提供,或注入专门的 `ISpatialIndex` 接口。(-1 分)
---
### 维度 2运行时性能14 / 15
#### 优势
**MapPlayerTracker O(1) 空间索引**
`Dictionary<Vector2Int, string>``Start()` 建立,`LateUpdate``WorldToCell` → 哈希查找 → 等格子提前返回,整条路径无分配、无线性扫描,符合 60fps 高频调用要求。
**MapPanel 双脏标记**
`_lastIconRoomId` + `_lastIconNormPos` 联合脏检查,玩家静止时 `LateUpdate` 完全跳过 `RectTransform` 读写;`OnMapUpdated` 事件驱动单格刷新,而非全量重建。
**MinimapHUD 增量重建**
`OnRoomChanged` 事件触发,仅回收超出 `_viewRadiusCells` 的格子,仅实例化新进入范围的格子;`UpdatePlayerDot``_lastDotRoomId` + `_lastDotNormPos` 双脏检查,无效帧零写入。
**MinimapHUD OnDisable 正确清理**
`ClearAllCells()` 销毁全部格子 GameObjects 并清空字典,`_lastDotRoomId = null`,重新激活时不会累积孤儿实例。
**GetExplorationProgress 缓存计数**
`_totalRoomCount = -1` sentinel首次计算后缓存`OnLoad` 时重置,消除原来的 O(N) 每帧遍历。
**MapDatabaseSO 懒索引**
`GetRoom()` 首次调用时建立 `Dictionary<string, MapRoomDataSO>``OnDisable` 清理,域重载安全。
#### 不足
- `RefreshView` 中"回收超出范围的格子"步骤需先构建 `List<string>` toRemove每次房间切换一次堆分配对于频繁换房间的高速移动场景可考虑复用预分配 buffer 或延迟回收池。(-1 分)
---
### 维度 3编辑器扩展工具13 / 15
#### 优势
**MapLayoutEditorWindow 功能完整**
- 滚轮缩放6~96 px/格),中键/Alt+左键拖拽平移
- `TrySelectRoom` 使用与 `DrawMapArea` 完全相同的 `origin = mapRect.size * 0.5f + _panOffset` 公式,命中测试精确
- 8 色调色板按区域自动上色,视觉区分度高
- Boss/存档点/商店 badge 标记(★/♦/¥)
- 缩放 ≥12f 时显示出口连线,连线矩阵正确保存/还原(`prevMatrix`/`prevColor`
- `ValidateAll()` 结果 inline HelpBox 展示(仅显示前 3 条,带计数)
- 点击任意房间:`Selection.activeObject` + `PingObject`,打通 Inspector/Project 导航链
**MapDatabaseEditor 统计与 Ping**
- 统计面板:房间数、出口总数
- 验证按钮调用 `ValidateAll()`,结果 HelpBox 渲染
- `SetDatabase(db)` 公共 API 调用 MapLayoutEditorWindow无 Reflection 脆弱注入
- 可折叠房间列表(最大高 200px错误行红色标注 + 每行 Ping 按钮
**MapRoomDataEditor Scene View 拖拽**
(已有实现,提供策划直接在 Scene 中拖拽调整房间格子位置的能力)
**OnValidate 防御性校验**
`MapRoomDataSO.OnValidate()` 强制 `GridSize` 每轴 ≥1防止零尺寸房间进入运行时。
#### 不足
- **无撤销Undo支持**:在 MapLayoutEditorWindow 中直接修改房间 GridPosition如果未来支持拖拽编辑或手动改变数据目前无 `Undo.RecordObject` 包裹Inspector 编辑已由 Unity 序列化系统处理,但 EditorWindow 侧的未来扩展需注意。(-1 分)
- **MapLayoutEditorWindow 无持久化 Window State**:关闭窗口后 `_zoom``_panOffset` 丢失;重新打开需要手动重置视图。可通过 `EditorPrefs` 简单持久化。(-1 分)
---
### 维度 4数据模型完整性9 / 10
#### 优势
**MapRoomDataSO 字段完整**
`RoomId``RegionId``DisplayName``GridPosition``GridSize``RoomOutlineTex``Exits`(含 `TargetRoomId`/`ExitGridPos`/`Direction`/`PreferredTransitionType`)、`IsBossRoom`/`IsSavePoint`/`IsShop``MapIconOverride``EstimatedMemoryKB`,覆盖主流银河恶魔城房间元数据需求。
**三级可见性设计**
`RoomVisibility { Unknown, Explored, Mapped }` 精确对应《空洞骑士》的「未知 → 进入显示轮廓 → 购买地图碎片显示完整格子」三级模型。
**RoomExitData 完备**
包含过渡类型 `PreferredTransitionType``Seamless`/`AtmosphericFade`)、出口方向、出口格子坐标,支持流式加载和场景切换决策。
**ValidateAll 四项检查**
null 元素 / 空 RoomId / RoomId 重复 / 格子重叠 / 出口目标不存在,覆盖最常见的数据配置错误。
#### 不足
- `MapRoomDataSO` 缺少 `ConnectedRegionIds`(用于"区域地图碎片解锁连带邻近房间"这类机制);也缺少 `AmbientMusicKey` 等与地图强耦合的环境配置(此类数据可通过扩展字段或额外 SO 补充,属于设计扩展点,不构成缺陷,但影响长期可扩展性)。(-1 分)
---
### 维度 5策划/开发友好度9 / 10
#### 优势
**Inspector 所见即所得**
`[Header]``[Tooltip]``[Range]``[Min]` 注解全面,策划无需看代码即可理解每个字段含义。`EstimatedMemoryKB` 的 Tooltip 详细说明测量方法和 0 的语义。
**一键验证流程**
MapDatabaseEditor Inspector → 点击"重新验证" → HelpBox 显示所有错误 + 每行 Ping → 点击 Ping 跳转到对应 SO → Inspector 直接修改;整条流程在 Inspector 面板内闭环。
**布局编辑器降低理解成本**
策划可在 MapLayoutEditorWindow 中直观查看整体地图结构,无需手动比对多个 SO 的 GridPosition 数值。颜色分区、Badge 符号、连线均辅助理解地图拓扑。
**RegionNameDisplay 本地化对接**
支持 LocKey → `LocalizationManager` 回落链,多语言游戏无需修改组件,仅填写 SO 配置。
**MapPin 完整的标记生命周期**
`CreatePin`/`AddPin`/`RemovePin` 公共 API挂接 ISaveable策划可在不修改代码的情况下扩展 `PinType`
#### 不足
- `MapLayoutEditorWindow` 暂无直接拖拽修改房间位置的功能(仅预览/选择/验证),策划仍需在 Inspector 中手动输入 `GridPosition`Scene View 拖拽已有 `MapRoomDataEditor`,但编辑器窗口里的一体化编辑体验不完整。(-1 分)
---
### 维度 6小地图功能对标11 / 15
#### 与《空洞骑士》《丝之歌》对标分析
| 功能特性 | 《空洞骑士》 | 目标系统 | 实现状态 |
|----------|-------------|----------|----------|
| 全屏地图面板 | ✅ | ✅ MapPanel | **已实现** |
| 角落小地图 HUD | ✅ | ✅ MinimapHUD | **已实现** |
| 三级可见性(未知/轮廓/完整) | ✅ | ✅ RoomVisibility | **已实现** |
| 区域颜色分区 | ✅ | ✅ RegionId + Palette | **已实现** |
| 玩家位置图标 | ✅ | ✅ _playerIconImg | **已实现** |
| 自定义标记(地图钉) | ✅ | ✅ MapPinManager | **已实现** |
| 区域名称淡显 | ✅ | ✅ RegionNameDisplay | **已实现** |
| 全屏地图缩放/平移 | ✅ | ✅ MapInputHandler | **已实现** |
| 当前房间高亮 | ✅ | ✅ SetHighlight() | **已实现** |
| 打开时自动居中到玩家位置 | ✅ | ✅ CenterOnCurrentRoom | **已实现** |
| 房间非矩形轮廓贴图 | ✅ | ✅ RoomOutlineTex | **已实现** |
| 存档点/商店/Boss 图标 | ✅ | ✅ ChooseIcon | **已实现** |
| 出口连接线(门缝) | ✅ | ✅ DrawExits | **已实现** |
| 地图碎片购买解锁 Mapped 状态 | ✅ | ✅ SetMapped | **已实现** |
| 探索进度百分比 UI | ✅ | ⚠️ API 有UI 未接入 | **API 就绪UI 缺失** |
| 地图碎片 NPC 购买流程 | ✅ | ⚠️ SetMapped API 有,购买 UI/NPC 接入未看到 | **部分** |
| 房间内传送点标记 | 部分 | ❌ 无专用 PinType | **缺失** |
| 小地图开/关切换动画 | ✅ | ❌ 无动画,直接 SetActive | **缺失** |
| 地图缩略图(全游戏鸟瞰) | 丝之歌有 | ❌ 无 | **缺失** |
| 多层地图Z 层/地下/天空) | 部分 | ❌ 无层级概念 | **超出当前范围** |
**已实现核心功能比例14/20 = 70%**
#### 主要缺口
1. **探索进度 UI 未接入**-1`GetExplorationProgress()` API 完备,但系统中无对应的 UI 组件(进度条/百分比文字)消费此数据。
2. **小地图开关缺动画**-1`MinimapHUD` 直接 Enable/Disable无淡入淡出或缩放过渡在视觉感知上低于参考游戏。
3. **购买地图碎片流程未闭环**-1`SetMapped` API 存在,但无对应的 ShopItem/NPC 触发流程(可能在 Shop 模块,但与地图系统的对接文档/接口未体现)。
4. **缺乏传送点 PinType**-1`PinType` 枚举中无 `Warp`/`Teleporter` 类型,银河恶魔城后期解锁传送网络时无法标注。
---
### 维度 7代码质量与可维护性9 / 10
#### 优势
**命名一致性**
`_camelCase` 私有字段、`PascalCase` 公共成员、`EVT_` 事件命名、`SO` 后缀 ScriptableObject全项目统一。
**XML 文档注释完整**
每个 `public` 成员和非平凡 `private` 方法均有 `<summary>` 注释;关键算法(空间索引、增量重建、脏标记)有内联说明。
**单一职责落实**
提取后:`MapRoomCellUI`(格子显示)、`MapPlayerTracker`(坐标转换)、`MapManager`(状态管理)、`MapPanel`(全屏面板)、`MinimapHUD`HUD各司其职无明显职责交叉。
**防御性编程**
null 检查(`_database?.AllRooms`)、`TryGetValue` 替代双重查询、`TryGetComponent` 替代 `GetComponent` + null 检查,关键路径均有保护。
**MapPin.cs 文件名说明**
顶部注释明确说明 `MapPin.cs` 包含 `MapPinManager` 类的历史原因,消除未来维护者的困惑。
#### 不足
- `MapPanel.CenterOnCurrentRoom()` 内调用了 `Canvas.ForceUpdateCanvases()`,这会强制重新布局整个 Canvas 树,在大型 UI 层级中可能引发性能尖峰;理想做法是延迟一帧或用 `LayoutRebuilder.ForceRebuildLayoutImmediate` 仅重建局部。(-1 分)
---
### 维度 8存档/持久化5 / 5
**完美实现**
- `MapManager` 实现 `ISaveable``OnSave` 深拷贝 `_exploredRooms`/`_mappedRooms``OnLoad` 防御性重建两个 HashSet并重置 `_totalRoomCount` 缓存。
- `MapPinManager` 实现 `ISaveable`:标记列表直接存入 `SaveData.Map.Pins`,加载时 null 安全。
- 两者均通过 `OnEnable/OnDisable` 注册/取消注册 `ISaveableRegistry`,无需静态引用,测试替换零成本。
---
## 四、系统架构总览图(文字版)
```
┌─────────────────────────────────────────────────────────────────────┐
│ IMapService接口层
│ IsExplored / IsMapped / SetMapped / Database / CurrentRegionId │
│ ExploredRoomCount / GetExplorationProgress / GetRoomsByRegion │
└───────────────────────┬─────────────────────────────────────────────┘
│ ServiceLocator
┌─────────────▼──────────────┐
│ MapManager │ 实现 IMapService + ISaveable
│ - HashSet<> _exploredRooms│
│ - HashSet<> _mappedRooms │
│ - _totalRoomCount (cache) │
│ - OnRoomEntered (event) │
└────────────────────────────┘
┌─────────────────┐ OnRoomChanged ┌─────────────────────┐
│ MapPlayerTracker│ ─────────────────▶ │ MinimapHUD │
│ - SpatialIndex │ │ - 增量格子重建 │
│ - O(1) LateUpd │ │ - ClearAllCells │
│ - NormPosInRoom │ │ - Dirty dot tracking │
└────────┬────────┘ └─────────────────────┘
│ CurrentRoomId / NormalizedPos
┌─────────────────────────────────────────────────────────┐
│ MapPanel全屏地图
│ - BuildGrid (首次) / RefreshAllCells (重开) │
│ - LateUpdate 双脏标记 │
│ - CenterOnCurrentRoom / SetHighlight │
│ - MapInputHandler (缩放/平移) [RequireComponent] │
└─────────────────────────────────────────────────────────┘
┌────────────────────────┐ ┌────────────────────────┐
│ MapRoomCellUI │ │ MapServiceExtensions │
│ - SetColors (注入) │ │ - GetVisibility(this │
│ - SetVisibility (enum) │ │ IMapService, roomId) │
│ - SetHighlight │ └────────────────────────┘
│ - Tooltip Hover │
└────────────────────────┘
Editor ──────────────────────────────────────────────────────
┌─────────────────────────┐ ┌──────────────────────────┐
│ MapLayoutEditorWindow │◀──│ MapDatabaseEditor │
│ - 缩放/平移/区域色 │ │ - SetDatabase() API │
│ - TrySelectRoom (精确) │ │ - ValidateAll + HelpBox │
│ - DrawLine (矩阵修复) │ │ - Ping 房间列表 │
│ - SetDatabase() 公共API │ └──────────────────────────┘
└─────────────────────────┘
```
---
## 五、仍需改进的项目(优先级排序)
### 🔴 高优先级(影响体验/正确性)
| # | 问题 | 位置 | 建议 |
|---|------|------|------|
| H1 | 小地图 HUD 开关无过渡动画 | `MinimapHUD` | 添加 `CanvasGroup` alpha 淡入/淡出 Coroutine参考 `RegionNameDisplay.ShowSequence()` |
| H2 | 探索进度 UI 未接入 | 无对应 UI 组件 | 新建 `MapProgressUI` 组件,订阅 `_onMapUpdated`,调用 `IMapService.GetExplorationProgress()` 渲染进度条 |
### 🟡 中优先级(功能完整性)
| # | 问题 | 位置 | 建议 |
|---|------|------|------|
| M1 | 缺少传送点 PinType | `SaveData.cs` PinType 枚举 | 新增 `Warp`(传送点)、`Landmark`(地标)枚举值 |
| M2 | 地图碎片购买与 SetMapped 未闭环 | 跨模块 | 在 Shop/NPC 模块提供触发 `IMapService.SetMapped(roomId)` 的接口调用文档或示例 |
| M3 | MapLayoutEditorWindow 不持久化视口状态 | `MapLayoutEditorWindow` | `OnDisable`/`OnEnable``EditorPrefs` 保存/恢复 `_zoom``_panOffset` |
| M4 | Canvas.ForceUpdateCanvases() 性能风险 | `MapPanel.CenterOnCurrentRoom` | 改为 `LayoutRebuilder.ForceRebuildLayoutImmediate(content)` 仅重建地图 content 节点 |
### 🟢 低优先级(锦上添花)
| # | 问题 | 位置 | 建议 |
|---|------|------|------|
| L1 | MapLayoutEditorWindow 无拖拽编辑 | `MapLayoutEditorWindow` | 实现 `MouseDrag`(非 Alt对选中房间的 `GridPosition` 修改,包裹 `Undo.RecordObject` |
| L2 | MinimapHUD toRemove 每帧分配 | `MinimapHUD.RefreshView` | 复用 `_toRemoveBuffer = new List<string>(8)` 字段,`Clear()` 后重用 |
| L3 | 缺少多层地图支持 | `MapRoomDataSO` | 新增 `MapLayer` 字段(`int``MapPanel` / `MinimapHUD` 按层过滤渲染 |
| L4 | MapDatabaseSO 仅在 OnDisable 清理索引 | `MapDatabaseSO` | 在 `OnValidate` 中也清理 `_index = null`,使编辑器改动后立即失效旧缓存 |
---
## 六、与参考游戏对标总结
### 《空洞骑士》对标
| 特性 | 评价 |
|------|------|
| 三级可见性机制 | 完全对标,`RoomVisibility` 枚举设计正确 |
| 地图碎片购买解锁 | 机制完备(`SetMapped`),流程闭环待补全 |
| 区域颜色分区 | 编辑器侧已有 8 色调色板,运行时侧待配色 SO |
| 自定义标记 | 基础完备,`PinType` 枚举可扩展 |
| 全屏/HUD 双模式 | 完全对标 |
| 玩家位置动态追踪 | 完全对标O(1) 性能优于参考实现 |
### 《丝之歌》对标
| 特性 | 评价 |
|------|------|
| 动态小地图(实时更新) | 完全对标(增量重建 + 事件驱动) |
| 区域名称入场动画 | 完全对标(`RegionNameDisplay` + 淡入淡出) |
| 地图缩略图/鸟瞰 | 未实现 |
| 探索百分比显示 | API 就绪UI 待实现 |
---
## 七、综合评价
经过三轮评估与两轮完整改进,本小地图系统已从 56 分的基础原型成长为一个**架构扎实、性能优良、工具完善**的银河恶魔城地图系统骨架。
**核心优势**
1. **接口驱动架构**让整个地图模块可测试、可替换,`IMapService` 是系统的核心稳定点
2. **O(1) 玩家追踪 + 增量 HUD 重建 + 多层脏标记**三处性能优化组合,在复杂地图(数百房间)下依然保持高帧率
3. **编辑器工具链完整**:布局预览 → 数据验证 → Ping 导航,策划无需写任何代码即可配置和调试地图
4. **三级可见性**精确对标《空洞骑士》设计,为地图碎片经济系统提供坚实基础
**核心差距**(主要体现在功能层面,非架构层面):
- 探索进度 UI 组件缺失
- 小地图开关无过渡动画
- PinType 扩展未覆盖传送点等特殊类型
**建议路线**:按 H1→H2→M1→M3 顺序完成剩余改进,即可达到可发行水平。
---
*报告生成于 zeling_v2 项目第三轮评估评估者GitHub Copilot CLI*