- 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.
8.5 KiB
8.5 KiB
小地图系统 R26 独立评审报告
评审轮次:Round 26(R25 修复后全面重审)
评审基准:成熟 2D Metroidvania 游戏商业级标准 + 专业编辑器扩展
一、各维度评分
| 维度 | 满分 | 得分 | vs R25(修复后) |
|---|---|---|---|
| 架构设计 & 解耦合 | 25 | 24.0 | -0.5 |
| 性能 & 运行时效率 | 20 | 19.5 | ±0 |
| 编辑器工具质量 | 15 | 14.5 | ±0 |
| 代码质量 & 可维护性 | 15 | 13.8 | -0.2 |
| 存档 & 持久化 | 10 | 9.5 | ±0 |
| 可扩展性 | 10 | 9.5 | ±0 |
| 功能完整性 | 5 | 5.0 | ±0 |
| 综合 | 100 | 95.8 | -1.7 |
R25(修复后基准):97.5 → R26:95.8(发现 2 项新问题)
二、R25 修复全部落地确认
| 修复项 | 状态 |
|---|---|
C1:TeleportService.CanTeleportTo _mapSvc == null || ... → _mapSvc != null && ... |
✅ |
| N1:MapPanel._subs 死代码字段及 Clear() 调用全部移除 | ✅ |
N2:MapInputHandler.Awake 展开 + #if UNITY_EDITOR || DEVELOPMENT_BUILD 校验警告 |
✅ |
三、持续表现优秀的设计
| 维度 | 设计亮点 |
|---|---|
| 接口解耦 | IMapService / IPlayerPositionProvider / IPinService / ITeleportService 全通过 ServiceLocator;UI 层无任何具体类引用 |
| 执行顺序链 | MapManager(-700)→MapPlayerTracker(-600)→MapPinManager(-500)→TeleportService(-400)→UI(0);服务注册先于所有消费方 |
| 空间索引共享 | MapDatabaseSO 统一维护 _cellToRoom 字典;MinimapHUD 的 O(viewRadius²) 剔除与 MapPlayerTracker 的 O(1) 房间判定共享同一索引,无重复构建 |
| 五类对象池 | MapPanel(Cell/Exit/Pin)+ MinimapHUD(Cell/Pin);全量 Disable 入池、Enable 出池,OnDestroy 销毁剩余;发现动画协程自清理(R20-N1) |
| ISaveable 防御性拷贝 | MapManager / MapPinManager / TeleportService 三处对称:OnSave new HashSet/List,OnLoad Clear+foreach Add |
| _servicesReady 短路 | MapPanel + MinimapHUD 均在三服务就绪后置 true,消除每帧 ServiceLocator 查询 |
| LateUpdate 脏检查 | MapPanel 玩家图标(RoomId + NormPos 双字段);MinimapHUD 玩家圆点(同);Pin 版本号脏检查——全部避免无效 RectTransform 读写 |
| DRY | ChooseDisplayIcon 集中在 MapRoomDataSO;BuildRegionDict / ResolveRegionDisplayName 集中在 MapServiceExtensions;MapPanel + MinimapHUD 均通过委托调用 |
| 编辑器工具 | 拖拽/缩放/验证(4类错误)/搜索(RoomId/RegionId/RoomType三维)/图例/Play Mode 玩家叠加;GUIStyle 缓存;MatchesRoomType 不区分大小写 |
| 存档健壮性 | OnLoad 空集合防御、空 id 过滤;OnValidate 自动 Trim RoomId、迁移旧 bool 字段;delayCall -=;+= 防止 Inspector 快速操作重复触发 |
| UnsubscribeServices 有意不对称 | MapPanel(OnDestroy 末尾不清空引用)vs MinimapHUD(置 null + 重置标志,支持跨场景重连)——均有注释说明 |
四、新发现问题
N1 — Normal:ITeleportService 接口不完整(UnlockTeleportStation / NotifyTeleportCompleted 缺失)
文件:ITeleportService.cs(接口缺失),TeleportService.cs(L103–115 公开方法)
问题:
// ITeleportService 接口中存在的方法:
bool CanTeleportTo(string roomId);
void RequestTeleport(string targetRoomId);
event Action<string, string> OnTeleportRequested;
event Action<string> OnTeleportCompleted;
// ─── 缺失的两个写操作方法 ───
// TeleportService 上有 public 定义,但不在接口中:
public void NotifyTeleportCompleted(string arrivedRoomId); // 场景加载系统须调用
public void UnlockTeleportStation(string roomId); // 游戏触发器须调用
影响:
- 场景加载系统完成传送后需调用
NotifyTeleportCompleted,但通过ServiceLocator.GetOrDefault<ITeleportService>()只能拿到接口,无法访问该方法;调用方被迫使用具体类型(TeleportService)或反射,破坏接口解耦原则。 - 传送站触发器(
OnTriggerEnter等)需要调用UnlockTeleportStation,同样面临相同问题。 - 接口 + ServiceLocator 模式在整个系统中一致使用,此处缺口会让后续维护者混淆。
修复:在 ITeleportService 中补全两个方法:
/// <summary>解锁指定房间的传送点(游戏触发器调用)。</summary>
void UnlockTeleportStation(string roomId);
/// <summary>场景加载系统传送完成后调用,触发 OnTeleportCompleted 事件。</summary>
void NotifyTeleportCompleted(string arrivedRoomId);
N2 — Normal:MapPinManager 缺少 _isDuplicate 单例保护(与 MapManager / MapPlayerTracker 不一致)
文件:MapPin.cs(MapPinManager 类,L32–37)
问题:
// MapManager(正确):
private void Awake()
{
if (ServiceLocator.GetOrDefault<IMapService>() != null) { _isDuplicate = true; Destroy(gameObject); return; }
ServiceLocator.Register<IMapService>(this);
}
// MapPlayerTracker(正确):
private void Awake()
{
if (ServiceLocator.GetOrDefault<IPlayerPositionProvider>() != null) { _isDuplicate = true; ... }
...
}
// MapPinManager(缺少保护):
private void Awake()
{
// ← 无重复实例检测
ServiceLocator.Register<IPinService>(this); // 若已有注册,直接覆盖
}
影响:
- 若场景中意外存在两个 MapPinManager(Persistent 场景加载两次、DontDestroyOnLoad 重复等),第二个实例会覆盖 ServiceLocator 注册,但第一个实例的
ISaveable仍保持注册状态(OnEnable中加入 SaveableRegistry),导致存档时OnSave被调用两次、集合竞争写入SaveData.Map.Pins。 - 系统其他三处服务均有
_isDuplicate守卫,此处缺失属于架构一致性漏洞。
修复:
private void Awake()
{
if (ServiceLocator.GetOrDefault<IPinService>() != null)
{
_isDuplicate = true;
Destroy(gameObject);
return;
}
ServiceLocator.Register<IPinService>(this);
}
// OnEnable / OnDisable / OnDestroy 首行加 if (_isDuplicate) return;
五、各文件评分摘要
| 文件 | 状态 | 备注 |
|---|---|---|
| MapManager.cs | ✅ 完整 | 执行顺序/ISaveable/IMapService/缓存/LINQ 仅在非热路径 |
| MapPlayerTracker.cs | ✅ 完整 | O(1) 空间索引/平滑归一化/单例保护 |
| MapPinManager(MapPin.cs) | ⚠️ N2 | 缺少 _isDuplicate 保护 |
| TeleportService.cs | ⚠️ N1 | 接口缺口(UnlockTeleportStation/NotifyTeleportCompleted) |
| MapPanel.cs | ✅ 完整 | R25-N1 _subs 死代码已移除;五层解耦;三对象池 |
| MinimapHUD.cs | ✅ 完整 | O(viewRadius²) 剔除;跨场景重连;GC 友好缓冲区 |
| MapInputHandler.cs | ✅ 完整 | R24-N2 单一 scale 状态;R25-N2 配置警告 |
| MinimapInputHandler.cs | ✅ 完整 | 干净委托,单职责 |
| MapRoomCellUI.cs | ✅ 完整 | 双场景复用;PlayRevealAnim 颜色恢复 |
| MapRoomDataSO.cs / MapDatabaseSO | ✅ 完整 | ChooseDisplayIcon 集中;空间索引共享;OnValidate 含延迟通知防抖 |
| MapServiceExtensions.cs | ✅ 完整 | 纯静态无状态;DRY 中心 |
| RegionNameDisplay.cs | ✅ 完整 | CompositeDisposable 正确使用;协程 OnDisable 终止安全 |
| ITeleportService.cs | ⚠️ N1 | 缺少两个写操作方法声明 |
| MapLayoutEditorWindow.cs(编辑器) | ✅ 完整 | 搜索+图例+拖拽+验证+GUIStyle 缓存+✕按钮 |
六、修复优先级
| 编号 | 严重度 | 文件 | 影响 | 建议 |
|---|---|---|---|---|
| N1 | 🟡 Normal | ITeleportService.cs + TeleportService.cs | 接口不完整导致调用方被迫使用具体类型 | 立即修复 |
| N2 | 🟡 Normal | MapPin.cs(MapPinManager) | 多实例场景存档竞争写入 | 立即修复 |
七、评分历史
| 轮次 | 综合评分 | 主要变化 |
|---|---|---|
| R17 | 93.0 | 初轮重构后 |
| R18–R19 | 93.8 | 平移/缩放/协程修复 |
| R20 | 94.2 | DRY + 协程自清理 |
| R21 | 95.0 | ServicesReady 对称 |
| R22 | 96.5 | BuildRegionDict 集中 |
| R23 | 97.5 | MatchesRoomType + 执行顺序 |
| R24 | 97.5 | TeleportService 重建(遗留 C1) |
| R25(修复前) | 95.5 | 发现 C1 逻辑反转 + N1 死代码 + N2 无警告 |
| R25(修复后基准) | 97.5 | 三项全修复 |
| R26 | 95.8 | 发现 N1(接口不完整-1.2)+ N2(单例缺失-0.5) |
R26 评审完成时间:2026-05-25