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
This commit is contained in:
2026-05-25 14:25:19 +08:00
parent a1f9122153
commit 5cb6c2a19d
64 changed files with 2358 additions and 32937 deletions

View File

@@ -1,49 +1,126 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using BaseGames.Core;
namespace BaseGames.World.Map
{
/// <summary>
/// 将玩家世界坐标转换为地图格子坐标,供 MapPanel 显示玩家位置图标(架构 15_MapShopModule §1.4
/// 将玩家世界坐标转换为地图格子坐标,供 MapPanel / MinimapHUD 显示玩家位置图标。
/// 挂在 Player GameObject 上LateUpdate 每帧计算)。
/// <para>
/// 性能:首次启动时建立 Dictionary&lt;Vector2Int, string&gt; 空间索引,
/// LateUpdate 房间判定为 O(1) 哈希查找。归一化位置每帧从世界坐标精确插值,
/// 确保图标跟随玩家平滑移动,而非以格子步长离散跳动。
/// </para>
/// 通过 <see cref="ServiceLocator"/> 注册为 <see cref="IPlayerPositionProvider"/>。
/// </summary>
public class MapPlayerTracker : MonoBehaviour
public class MapPlayerTracker : MonoBehaviour, IPlayerPositionProvider
{
[SerializeField] private Transform _playerTransform;
[SerializeField] private MapDatabaseSO _database;
[SerializeField] private MapDatabaseSO _databaseOverride; // 可选:直接指定;留空则从 IMapService 获取
[Header("世界坐标 → 格子坐标换算参数")]
[SerializeField] private float _worldUnitsPerCell = 18f; // 1 格 = N 世界单位
[Tooltip("1 格对应的世界单位数。请在关卡编辑器中测量房间实际尺寸后填入,确保与关卡设计对齐。")]
[SerializeField] private float _worldUnitsPerCell = 18f;
/// <summary>玩家当前所在房间 ID(用于地图高亮当前房间)。</summary>
/// <summary>玩家当前所在房间 ID;未在任何已知房间内时为 null。</summary>
public string CurrentRoomId { get; private set; }
/// <summary>玩家在当前格子房间内的归一化坐标0~1。</summary>
/// <summary>
/// 玩家在当前房间内的归一化坐标0~1
/// 基于世界坐标精确插值,每帧更新,可用于平滑移动图标。
/// </summary>
public Vector2 NormalizedPositionInRoom { get; private set; }
// 缓存上一帧的格子坐标;格子不变则跳过 O(N) 搜索
private Vector2Int _lastCellPos = new Vector2Int(int.MinValue, int.MinValue);
/// <summary>玩家进入新房间时触发(参数为新房间 ID。</summary>
public event Action<string> OnRoomChanged;
private void LateUpdate()
private MapDatabaseSO _database;
private Dictionary<Vector2Int, string> _cellToRoomId;
private MapRoomDataSO _currentRoom; // 当前房间数据缓存,避免 LateUpdate 每帧 GetRoom 查找
private Vector2Int _lastCellPos = new Vector2Int(int.MinValue, int.MinValue);
private void Awake()
{
if (_playerTransform == null || _database?.AllRooms == null) return;
// 单例保护:同一时刻只允许一个 IPlayerPositionProvider 存在
if (ServiceLocator.GetOrDefault<IPlayerPositionProvider>() != null) return;
ServiceLocator.Register<IPlayerPositionProvider>(this);
}
Vector2Int cellPos = WorldToCell(_playerTransform.position);
if (cellPos == _lastCellPos) return; // 格子未变,无需重新搜索
_lastCellPos = cellPos;
private void Start()
{
_database = _databaseOverride
?? ServiceLocator.GetOrDefault<IMapService>()?.Database;
BuildSpatialIndex();
}
private void OnDestroy()
{
ServiceLocator.Unregister<IPlayerPositionProvider>(this);
}
/// <summary>
/// 构建格子坐标 → 房间 ID 的哈希映射,将 LateUpdate 的查询从 O(N) 降至 O(1)。
/// 房间数据变化时(运行时热更)可再次调用重建索引。
/// </summary>
public void BuildSpatialIndex()
{
_cellToRoomId = new Dictionary<Vector2Int, string>();
if (_database?.AllRooms == null) return;
foreach (var room in _database.AllRooms)
{
if (room == null) continue;
var rect = new RectInt(room.GridPosition, room.GridSize);
if (rect.Contains(cellPos))
for (int x = 0; x < room.GridSize.x; x++)
for (int y = 0; y < room.GridSize.y; y++)
{
CurrentRoomId = room.RoomId;
Vector2 inRoom = (Vector2)(cellPos - room.GridPosition);
NormalizedPositionInRoom = new Vector2(
inRoom.x / Mathf.Max(1, room.GridSize.x),
inRoom.y / Mathf.Max(1, room.GridSize.y));
var cell = new Vector2Int(room.GridPosition.x + x, room.GridPosition.y + y);
_cellToRoomId[cell] = room.RoomId;
}
}
}
private void LateUpdate()
{
if (_playerTransform == null || _cellToRoomId == null) return;
Vector2Int cellPos = WorldToCell(_playerTransform.position);
bool cellChanged = cellPos != _lastCellPos;
if (cellChanged)
{
_lastCellPos = cellPos;
if (!_cellToRoomId.TryGetValue(cellPos, out var newRoomId))
{
// 玩家离开所有已知房间
CurrentRoomId = null;
_currentRoom = null;
NormalizedPositionInRoom = Vector2.zero;
return;
}
var prevRoomId = CurrentRoomId;
CurrentRoomId = newRoomId;
_currentRoom = _database.GetRoom(newRoomId);
if (newRoomId != prevRoomId)
OnRoomChanged?.Invoke(newRoomId);
}
// 每帧从世界坐标精确计算归一化位置,实现平滑图标跟随
if (_currentRoom != null)
{
var worldMin = new Vector2(
_currentRoom.GridPosition.x * _worldUnitsPerCell,
_currentRoom.GridPosition.y * _worldUnitsPerCell);
var worldSize = new Vector2(
_currentRoom.GridSize.x * _worldUnitsPerCell,
_currentRoom.GridSize.y * _worldUnitsPerCell);
var localPos = (Vector2)_playerTransform.position - worldMin;
NormalizedPositionInRoom = new Vector2(
Mathf.Clamp01(localPos.x / Mathf.Max(1f, worldSize.x)),
Mathf.Clamp01(localPos.y / Mathf.Max(1f, worldSize.y)));
}
}
@@ -53,3 +130,4 @@ namespace BaseGames.World.Map
Mathf.FloorToInt(worldPos.y / _worldUnitsPerCell));
}
}