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.
This commit is contained in:
@@ -2,10 +2,28 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using BaseGames.Core.Events;
|
||||
|
||||
namespace BaseGames.World.Map
|
||||
{
|
||||
/// <summary>
|
||||
/// 房间功能类型标记(可多选)。
|
||||
/// 替代原先的 IsBossRoom / IsSavePoint / IsShop 三个独立 bool,
|
||||
/// 支持复合类型(如一个房间既是存档点也是商店),并便于扩展新类型。
|
||||
/// </summary>
|
||||
[System.Flags]
|
||||
public enum RoomType
|
||||
{
|
||||
None = 0,
|
||||
BossRoom = 1 << 0,
|
||||
SavePoint = 1 << 1,
|
||||
Shop = 1 << 2,
|
||||
Merchant = 1 << 3,
|
||||
Challenge = 1 << 4,
|
||||
TeleportStation = 1 << 5,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单个房间的地图数据 SO(架构 15_MapShopModule §1.1)。
|
||||
/// 资产路径: Assets/_Game/Data/Map/Rooms/Room_{RoomId}.asset
|
||||
@@ -28,11 +46,27 @@ namespace BaseGames.World.Map
|
||||
[Header("出口信息")]
|
||||
public RoomExitData[] Exits; // 该房间所有出口定义
|
||||
|
||||
[Header("特殊标记")]
|
||||
public bool IsBossRoom;
|
||||
public bool IsSavePoint;
|
||||
public bool IsShop;
|
||||
public Sprite MapIconOverride; // null = 按 isXxx 自动选择图标
|
||||
[Header("房间类型标记(可多选)")]
|
||||
public RoomType RoomFlags; // 支持多类型组合,替代旧的三个 bool 字段
|
||||
[HideInInspector] public bool IsBossRoom; // 旧字段,保留序列化兼容性;OnValidate 自动迁移到 RoomFlags
|
||||
[HideInInspector] public bool IsSavePoint; // 旧字段,保留序列化兼容性;OnValidate 自动迁移到 RoomFlags
|
||||
[HideInInspector] public bool IsShop; // 旧字段,保留序列化兼容性;OnValidate 自动迁移到 RoomFlags
|
||||
public Sprite MapIconOverride; // null = 按 RoomFlags 自动选择图标
|
||||
|
||||
/// <summary>
|
||||
/// R20-N2 集中图标优先级逻辑,替代 MapPanel / MinimapHUD 各自重复的 ChooseIcon 实现。
|
||||
/// 优先级:MapIconOverride > SavePoint > BossRoom > Shop > TeleportStation。
|
||||
/// 对应 Sprite 未配置时返回 null(格子不显示图标)。
|
||||
/// </summary>
|
||||
public Sprite ChooseDisplayIcon(Sprite savePoint, Sprite boss, Sprite shop, Sprite teleport)
|
||||
{
|
||||
if (MapIconOverride != null) return MapIconOverride;
|
||||
if (RoomFlags.HasFlag(RoomType.SavePoint) || IsSavePoint) return savePoint;
|
||||
if (RoomFlags.HasFlag(RoomType.BossRoom) || IsBossRoom) return boss;
|
||||
if (RoomFlags.HasFlag(RoomType.Shop) || IsShop) return shop;
|
||||
if (RoomFlags.HasFlag(RoomType.TeleportStation)) return teleport;
|
||||
return null;
|
||||
}
|
||||
|
||||
[Header("流式加载")]
|
||||
[Tooltip("此房间场景资产的预估内存(KB)。\n" +
|
||||
@@ -45,7 +79,46 @@ namespace BaseGames.World.Map
|
||||
{
|
||||
// 保证 GridSize 每轴最小为 1,防止零尺寸房间导致碰撞和渲染异常
|
||||
GridSize = new Vector2Int(Mathf.Max(1, GridSize.x), Mathf.Max(1, GridSize.y));
|
||||
|
||||
// R11-N11 自动修剪 RoomId 首尾空格,避免 " Room_A " 与 "Room_A" 被视为不同键
|
||||
if (!string.IsNullOrEmpty(RoomId) && RoomId != RoomId.Trim())
|
||||
RoomId = RoomId.Trim();
|
||||
|
||||
// R12-N9 将旧 bool 字段迁移到 RoomFlags(RoomFlags 为 None 且旧字段有值时执行一次)
|
||||
if (RoomFlags == RoomType.None)
|
||||
{
|
||||
if (IsBossRoom) RoomFlags |= RoomType.BossRoom;
|
||||
if (IsSavePoint) RoomFlags |= RoomType.SavePoint;
|
||||
if (IsShop) RoomFlags |= RoomType.Shop;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// R11-N2 先 -= 再 +=,保证同一 delayCall 序列中最多执行一次,
|
||||
// 防止 Inspector 快速拖动滑条时重复追加 N×FindAssets 导致卡顿
|
||||
UnityEditor.EditorApplication.delayCall -= NotifyOwningDatabases;
|
||||
UnityEditor.EditorApplication.delayCall += NotifyOwningDatabases;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void NotifyOwningDatabases()
|
||||
{
|
||||
if (this == null) return;
|
||||
var guids = UnityEditor.AssetDatabase.FindAssets("t:MapDatabaseSO");
|
||||
foreach (var guid in guids)
|
||||
{
|
||||
var path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
|
||||
var db = UnityEditor.AssetDatabase.LoadAssetAtPath<MapDatabaseSO>(path);
|
||||
if (db?.AllRooms == null) continue;
|
||||
if (System.Array.IndexOf(db.AllRooms, this) < 0) continue;
|
||||
db.InvalidateIndex();
|
||||
|
||||
// Play Mode 下同时广播事件,让 UI 立即重建
|
||||
if (UnityEngine.Application.isPlaying)
|
||||
BaseGames.Core.ServiceLocator.GetOrDefault<IMapService>()?.NotifyDatabaseChanged();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -59,6 +132,10 @@ namespace BaseGames.World.Map
|
||||
"Seamless:无缝切换(同区域相邻房间首选);\n" +
|
||||
"AtmosphericFade:短暂淡出 + 区域名提示(跨大区域边界首选)。")]
|
||||
public TransitionType PreferredTransitionType;
|
||||
|
||||
[Tooltip("是否已手动配置出口格子坐标。\n" +
|
||||
"未勾选时,连线回退到房间中心,避免 (0,0) 与合法原点坐标产生歧义。")]
|
||||
public bool HasCustomExitPos; // R12-N5 替代 ExitGridPos != Vector2Int.zero 哨兵用法
|
||||
}
|
||||
|
||||
public enum ExitDirection { Up, Down, Left, Right }
|
||||
@@ -72,9 +149,20 @@ namespace BaseGames.World.Map
|
||||
[CreateAssetMenu(menuName = "BaseGames/World/Map/MapDatabase")]
|
||||
public class MapDatabaseSO : ScriptableObject
|
||||
{
|
||||
public MapRoomDataSO[] AllRooms;
|
||||
[SerializeField, FormerlySerializedAs("AllRooms")]
|
||||
private MapRoomDataSO[] _allRooms;
|
||||
|
||||
private Dictionary<string, MapRoomDataSO> _index;
|
||||
[SerializeField, Tooltip("勾选后,AssetPostprocessor 自动注册的新房间会优先加入此 Database;多个勾选时取 GUID 排序首个。")]
|
||||
private bool _isDefault;
|
||||
|
||||
/// <summary>所属全部房间(只读视图)。编辑器写入请通过 <see cref="EditorSetRooms"/>。</summary>
|
||||
public MapRoomDataSO[] AllRooms => _allRooms;
|
||||
|
||||
/// <summary>是否被标记为默认 Database。<see cref="MapRoomAutoRegister"/> 据此决定新建房间归属。</summary>
|
||||
public bool IsDefault => _isDefault;
|
||||
|
||||
private Dictionary<string, MapRoomDataSO> _index;
|
||||
private Dictionary<Vector2Int, string> _cellToRoom; // 格子坐标 → 房间 ID 空间索引(共享给 MinimapHUD/MapPlayerTracker,避免重复构建)
|
||||
|
||||
/// <summary>运行时快速查找(首次调用时建立索引)。</summary>
|
||||
public MapRoomDataSO GetRoom(string roomId)
|
||||
@@ -82,16 +170,77 @@ namespace BaseGames.World.Map
|
||||
if (_index == null)
|
||||
{
|
||||
if (AllRooms == null) return null;
|
||||
_index = AllRooms.Where(r => r != null)
|
||||
.ToDictionary(r => r.RoomId);
|
||||
// R29-N2 使用 TryAdd(首条胜出),防止重复 RoomId 触发 ArgumentException;
|
||||
// 编辑器侧 ValidateAll 负责提示策划修复数据,运行时继续工作不崩溃。
|
||||
_index = new Dictionary<string, MapRoomDataSO>();
|
||||
foreach (var r in AllRooms)
|
||||
if (r != null && !string.IsNullOrEmpty(r.RoomId))
|
||||
_index.TryAdd(r.RoomId, r);
|
||||
}
|
||||
_index.TryGetValue(roomId, out var r);
|
||||
return r;
|
||||
}
|
||||
|
||||
private void OnDisable() => _index = null; // SO 卸载时清理缓存
|
||||
/// <summary>
|
||||
/// 在指定格子坐标处查询所属房间 ID(O(1) 哈希查找)。
|
||||
/// 首次调用时惰性构建空间索引,由所有消费方(小地图/玩家追踪)共享,避免重复构建。
|
||||
/// </summary>
|
||||
public string GetRoomIdAtCell(Vector2Int cell)
|
||||
{
|
||||
EnsureSpatialIndex();
|
||||
return _cellToRoom != null && _cellToRoom.TryGetValue(cell, out var id) ? id : null;
|
||||
}
|
||||
|
||||
private void OnValidate() => _index = null; // 编辑器中修改 AllRooms 后强制重建索引
|
||||
private void EnsureSpatialIndex()
|
||||
{
|
||||
if (_cellToRoom != null) return;
|
||||
_cellToRoom = new Dictionary<Vector2Int, string>();
|
||||
if (AllRooms == null) return;
|
||||
foreach (var room in AllRooms)
|
||||
{
|
||||
if (room == null) continue;
|
||||
for (int x = 0; x < room.GridSize.x; x++)
|
||||
for (int y = 0; y < room.GridSize.y; y++)
|
||||
_cellToRoom[new Vector2Int(room.GridPosition.x + x, room.GridPosition.y + y)] = room.RoomId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>数据库变更时(编辑器热改 / 运行时热更)强制让索引下次访问时重建。</summary>
|
||||
public void InvalidateIndex()
|
||||
{
|
||||
_index = null;
|
||||
_cellToRoom = null;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// 编辑器专用:写入 <see cref="AllRooms"/> 数组并强制失效空间索引。
|
||||
/// 替代直接赋值 public 字段,确保 <see cref="MapRoomAutoRegister"/> / 测试代码不绕过封装。
|
||||
/// </summary>
|
||||
public void EditorSetRooms(MapRoomDataSO[] rooms)
|
||||
{
|
||||
_allRooms = rooms;
|
||||
InvalidateIndex();
|
||||
}
|
||||
#endif
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
// SO 卸载时清理缓存
|
||||
_index = null;
|
||||
_cellToRoom = null;
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
// 编辑器中修改 AllRooms 后强制重建索引
|
||||
_index = null;
|
||||
_cellToRoom = null;
|
||||
#if UNITY_EDITOR
|
||||
if (UnityEngine.Application.isPlaying)
|
||||
BaseGames.Core.ServiceLocator.GetOrDefault<IMapService>()?.NotifyDatabaseChanged();
|
||||
#endif
|
||||
}
|
||||
|
||||
// ── 配置验证 ──────────────────────────────────────────────────────────
|
||||
#if UNITY_EDITOR
|
||||
@@ -111,6 +260,15 @@ namespace BaseGames.World.Map
|
||||
if (AllRooms[i] == null) { errors.Add($"AllRooms[{i}] 为 null"); continue; }
|
||||
if (string.IsNullOrEmpty(AllRooms[i].RoomId))
|
||||
errors.Add($"AllRooms[{i}]({AllRooms[i].name})RoomId 为空");
|
||||
else
|
||||
{
|
||||
// R11-N11 首尾空格检查(OnValidate 已自动 Trim,此处兜底提示未经 OnValidate 的旧资产)
|
||||
if (AllRooms[i].RoomId != AllRooms[i].RoomId.Trim())
|
||||
errors.Add($"'{AllRooms[i].name}' RoomId 含首尾空格,请在 Inspector 中保存触发自动修剪");
|
||||
// 特殊字符检查:/ \ | 等可能影响路径/键处理的字符
|
||||
if (AllRooms[i].RoomId.IndexOfAny(new[]{ '/', '\\', '|', '<', '>', '*', '?' }) >= 0)
|
||||
errors.Add($"'{AllRooms[i].RoomId}' 含非法字符(/ \\ | < > * ?),可能影响场景名匹配和存档键");
|
||||
}
|
||||
}
|
||||
|
||||
// ② RoomId 重复
|
||||
|
||||
Reference in New Issue
Block a user