Add independent review report for Minimap system Round 7
- Validate fixes from Round 6 and identify new issues - Document findings including UX defects, editor integration flaws, and code quality concerns - Propose solutions and prioritize issues based on severity - Evaluate against standards of mature 2D Metroidvania games
This commit is contained in:
@@ -29,10 +29,26 @@ namespace BaseGames.Editor.Map
|
||||
/// </summary>
|
||||
private readonly HashSet<string> _cachedErrorRoomIds = new();
|
||||
|
||||
/// <summary>错误行文本颜色样式,惰性初始化后复用,避免 OnInspectorGUI 每帧分配。</summary>
|
||||
private GUIStyle _errorRowStyle;
|
||||
|
||||
private static readonly GUIContent LabelValidate = new GUIContent("重新验证", "检查重复 RoomId、出口目标缺失、房间网格重叠等问题");
|
||||
private static readonly GUIContent LabelOpenEditor = new GUIContent("打开布局编辑器", "在独立窗口中预览全局地图布局");
|
||||
|
||||
private void OnEnable() => _database = (MapDatabaseSO)target;
|
||||
private void OnEnable()
|
||||
{
|
||||
_database = (MapDatabaseSO)target;
|
||||
_errorRowStyle = null; // 编辑器皮肤切换时(亮/暗模式)需重建
|
||||
}
|
||||
|
||||
/// <summary>错误行 GUIStyle 惰性初始化,基于当前编辑器皮肤构建,避免 OnInspectorGUI 每帧分配。</summary>
|
||||
private GUIStyle GetErrorRowStyle()
|
||||
{
|
||||
if (_errorRowStyle == null)
|
||||
_errorRowStyle = new GUIStyle(EditorStyles.label)
|
||||
{ normal = { textColor = new Color(1f, 0.35f, 0.35f) } };
|
||||
return _errorRowStyle;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
@@ -122,9 +138,7 @@ namespace BaseGames.Editor.Map
|
||||
}
|
||||
|
||||
bool hasError = _cachedErrorRoomIds.Contains(room.RoomId);
|
||||
var rowStyle = hasError
|
||||
? new GUIStyle(EditorStyles.label) { normal = { textColor = new Color(1f, 0.35f, 0.35f) } }
|
||||
: EditorStyles.label;
|
||||
var rowStyle = hasError ? GetErrorRowStyle() : EditorStyles.label;
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField(
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e42e54d73570d0245b6bb4e722c3b0f8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -59,6 +59,25 @@ namespace BaseGames.Editor.Map
|
||||
|
||||
// ── 主 GUI ────────────────────────────────────────────────────────────
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
// 注册 Undo 回调:SceneView 中拖拽房间后执行 Ctrl+Z,窗口自动刷新
|
||||
Undo.undoRedoPerformed += OnUndoRedo;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
Undo.undoRedoPerformed -= OnUndoRedo;
|
||||
}
|
||||
|
||||
/// <summary>Undo/Redo 发生后清除验证缓存并触发重绘,确保布局视图与数据同步。</summary>
|
||||
private void OnUndoRedo()
|
||||
{
|
||||
_validationErrors = null;
|
||||
_errorRoomIds = null;
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
DrawToolbar();
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c889260fa9407545a7db0a014e1e176
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -39,7 +39,10 @@ namespace BaseGames.Editor.Map
|
||||
|
||||
EditorGUILayout.HelpBox(
|
||||
"在 Scene View 中可直接拖拽房间角点调整 GridPosition / GridSize。\n" +
|
||||
"拖动自动吸附到 1 格精度,支持 Undo。",
|
||||
"拖动自动吸附到 1 格精度,支持 Undo。\n\n" +
|
||||
"⚠ 坐标系说明:Scene View 中 1 格 = 1 世界单位(编辑器可视化坐标)。\n" +
|
||||
"运行时玩家追踪使用 worldUnitsPerCell(默认 18 世界单位/格)。\n" +
|
||||
"两者仅为独立坐标系,互不影响——格子布局数据(GridPosition/GridSize)是统一的格子单位,无需换算。",
|
||||
MessageType.Info);
|
||||
|
||||
if (GUILayout.Button("居中 Scene View 到此房间", GUILayout.Height(28)))
|
||||
|
||||
11
Assets/_Game/Scripts/World/Map/IPinService.cs.meta
Normal file
11
Assets/_Game/Scripts/World/Map/IPinService.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96f79378a72e0884eb67abdec7cedd2a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f10ab54d55ebf14a8c93cca7164230c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
11
Assets/_Game/Scripts/World/Map/MapGridConstants.cs.meta
Normal file
11
Assets/_Game/Scripts/World/Map/MapGridConstants.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8bb8a918cea77d4097634a071a13b4b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
11
Assets/_Game/Scripts/World/Map/MapInputHandler.cs.meta
Normal file
11
Assets/_Game/Scripts/World/Map/MapInputHandler.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4868a5b10a549ea43826ff162ecc6b5e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
11
Assets/_Game/Scripts/World/Map/MapRoomCellUI.cs.meta
Normal file
11
Assets/_Game/Scripts/World/Map/MapRoomCellUI.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0c5d13ad97e89a4b8f49b35620789c5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -94,7 +94,7 @@ namespace BaseGames.World.Map
|
||||
private void OnValidate() => _index = null; // 编辑器中修改 AllRooms 后强制重建索引
|
||||
|
||||
// ── 配置验证 ──────────────────────────────────────────────────────────
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// 检查数据库中的常见配置错误(RoomId 重复、格子重叠、出口悬空)。
|
||||
/// 编辑器侧调用;运行时不应调用(有 O(N²) 开销)。
|
||||
@@ -152,5 +152,6 @@ namespace BaseGames.World.Map
|
||||
|
||||
return errors;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
11
Assets/_Game/Scripts/World/Map/MapServiceExtensions.cs.meta
Normal file
11
Assets/_Game/Scripts/World/Map/MapServiceExtensions.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a93d2e2dddd51740807bd2ceebb68c9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -42,6 +42,11 @@ namespace BaseGames.World.Map
|
||||
// 复用 List 避免 RefreshView 每次分配临时 List(GC 友好)
|
||||
private readonly List<string> _toRemove = new List<string>(8);
|
||||
|
||||
// 空间索引:格子坐标 → 房间 ID,将 RefreshView step② 的 O(N) 遍历降至 O(viewRadius²)
|
||||
private Dictionary<Vector2Int, string> _spatialIndex;
|
||||
// 复用 HashSet 避免 RefreshView 每次分配(GC 友好)
|
||||
private readonly HashSet<string> _roomsInViewBuffer = new HashSet<string>(32);
|
||||
|
||||
private Vector2Int _currentCenter;
|
||||
private string _lastDotRoomId;
|
||||
private Vector2 _lastDotNormPos;
|
||||
@@ -53,6 +58,8 @@ namespace BaseGames.World.Map
|
||||
_mapSvc = ServiceLocator.GetOrDefault<IMapService>();
|
||||
_playerProvider = ServiceLocator.GetOrDefault<IPlayerPositionProvider>();
|
||||
|
||||
BuildSpatialIndex(_mapSvc?.Database);
|
||||
|
||||
if (_playerProvider != null)
|
||||
_playerProvider.OnRoomChanged += OnRoomChanged;
|
||||
|
||||
@@ -72,6 +79,7 @@ namespace BaseGames.World.Map
|
||||
_lastDotRoomId = null;
|
||||
_mapSvc = null;
|
||||
_playerProvider = null;
|
||||
_spatialIndex = null;
|
||||
}
|
||||
|
||||
private void ClearAllCells()
|
||||
@@ -81,6 +89,24 @@ namespace BaseGames.World.Map
|
||||
_cells.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建格子坐标 → 房间 ID 的哈希映射。
|
||||
/// 将 RefreshView step② 从 O(allRooms) 全量遍历降至 O(viewRadius²) 范围格点查询。
|
||||
/// 数据库变更时(如热更)应再次调用。
|
||||
/// </summary>
|
||||
private void BuildSpatialIndex(MapDatabaseSO db)
|
||||
{
|
||||
_spatialIndex = new Dictionary<Vector2Int, string>();
|
||||
if (db?.AllRooms == null) return;
|
||||
foreach (var room in db.AllRooms)
|
||||
{
|
||||
if (room == null) continue;
|
||||
for (int x = 0; x < room.GridSize.x; x++)
|
||||
for (int y = 0; y < room.GridSize.y; y++)
|
||||
_spatialIndex[new Vector2Int(room.GridPosition.x + x, room.GridPosition.y + y)] = room.RoomId;
|
||||
}
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
UpdatePlayerDot();
|
||||
@@ -133,16 +159,30 @@ namespace BaseGames.World.Map
|
||||
}
|
||||
foreach (var id in _toRemove) _cells.Remove(id);
|
||||
|
||||
// ② 实例化新进入范围的格子
|
||||
foreach (var room in db.AllRooms)
|
||||
// ② 用空间索引替代 O(N) 全量遍历,在可视范围格点上查询所属房间
|
||||
// 复杂度:O(viewRadius²) 替代 O(allRooms),大地图下效果显著
|
||||
_roomsInViewBuffer.Clear();
|
||||
if (_spatialIndex != null)
|
||||
{
|
||||
if (room == null || _cells.ContainsKey(room.RoomId)) continue;
|
||||
if (!RoomInView(room, minX, maxX, minY, maxY)) continue;
|
||||
for (int x = minX; x <= maxX; x++)
|
||||
for (int y = minY; y <= maxY; y++)
|
||||
{
|
||||
if (_spatialIndex.TryGetValue(new Vector2Int(x, y), out var rId))
|
||||
_roomsInViewBuffer.Add(rId);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var roomId in _roomsInViewBuffer)
|
||||
{
|
||||
if (_cells.ContainsKey(roomId)) continue;
|
||||
var room = db.GetRoom(roomId);
|
||||
if (room == null) continue;
|
||||
|
||||
var cell = Instantiate(_cellPrefab, _cellContainer);
|
||||
cell.Setup(room, _mapSvc.GetVisibility(room.RoomId), null);
|
||||
cell.SetColors(_colorExplored, _colorMapped, _colorUnknown);
|
||||
_cells[room.RoomId] = cell;
|
||||
PlaceCell(cell, room); // 立即设置正确的中心相对坐标,避免 Setup 默认偏移被 step③ 覆盖
|
||||
_cells[roomId] = cell;
|
||||
}
|
||||
|
||||
// ③ 重定位所有格子(中心发生变化时)
|
||||
|
||||
11
Assets/_Game/Scripts/World/Map/MinimapHUD.cs.meta
Normal file
11
Assets/_Game/Scripts/World/Map/MinimapHUD.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02879db752b9f0b4bb1b0c9834ad4d84
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user