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:
201
Assets/_Game/Scripts/World/Map/MinimapHUD.cs
Normal file
201
Assets/_Game/Scripts/World/Map/MinimapHUD.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using BaseGames.Core;
|
||||
using BaseGames.Core.Events;
|
||||
|
||||
namespace BaseGames.World.Map
|
||||
{
|
||||
/// <summary>
|
||||
/// 角落小地图 HUD(架构 15_MapShopModule §1.6)。
|
||||
/// 以玩家当前房间为中心,仅渲染 ±ViewRadiusCells 格范围内的房间。
|
||||
/// 玩家跨格进入新房间时(OnRoomChanged 事件)触发增量重建,无需每帧扫描全局。
|
||||
/// <para>
|
||||
/// 挂载位置:HUD Canvas 下 Minimap 根节点(需配有 RectMask2D 用于裁剪)。
|
||||
/// </para>
|
||||
/// 依赖 <see cref="IPlayerPositionProvider"/> 和 <see cref="IMapService"/>,
|
||||
/// 均通过 <see cref="ServiceLocator"/> 获取,不持有具体类的 SerializeField 引用。
|
||||
/// </summary>
|
||||
public class MinimapHUD : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private MapRoomCellUI _cellPrefab;
|
||||
[SerializeField] private RectTransform _cellContainer; // 带 RectMask2D 的容器,内容在此节点内平移
|
||||
[SerializeField] private Image _playerDot; // 玩家位置圆点(在 _cellContainer 内)
|
||||
|
||||
[Header("显示范围")]
|
||||
[SerializeField, Min(1)] private int _viewRadiusCells = 3; // 以玩家房间中心为圆心的可视半径(格)
|
||||
[SerializeField] private float _cellPixels = 16f; // 每格显示像素数
|
||||
|
||||
[Header("颜色(Inspector 覆盖)")]
|
||||
[SerializeField] private Color _colorExplored = Color.white;
|
||||
[SerializeField] private Color _colorMapped = new Color(0.45f, 0.45f, 0.45f, 1f);
|
||||
[SerializeField] private Color _colorUnknown = Color.black;
|
||||
|
||||
[Header("Event Channels")]
|
||||
[SerializeField] private StringEventChannelSO _onMapUpdated; // 房间发现/标注时局部刷新
|
||||
|
||||
private IMapService _mapSvc;
|
||||
private IPlayerPositionProvider _playerProvider;
|
||||
private readonly Dictionary<string, MapRoomCellUI> _cells = new();
|
||||
private readonly CompositeDisposable _subs = new();
|
||||
|
||||
// 复用 List 避免 RefreshView 每次分配临时 List(GC 友好)
|
||||
private readonly List<string> _toRemove = new List<string>(8);
|
||||
|
||||
private Vector2Int _currentCenter;
|
||||
private string _lastDotRoomId;
|
||||
private Vector2 _lastDotNormPos;
|
||||
|
||||
// ── 生命周期 ──────────────────────────────────────────────────────────
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_mapSvc = ServiceLocator.GetOrDefault<IMapService>();
|
||||
_playerProvider = ServiceLocator.GetOrDefault<IPlayerPositionProvider>();
|
||||
|
||||
if (_playerProvider != null)
|
||||
_playerProvider.OnRoomChanged += OnRoomChanged;
|
||||
|
||||
_onMapUpdated?.Subscribe(OnMapUpdated).AddTo(_subs);
|
||||
|
||||
// 首次显示时立即刷新
|
||||
RefreshView();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (_playerProvider != null)
|
||||
_playerProvider.OnRoomChanged -= OnRoomChanged;
|
||||
|
||||
_subs.Clear();
|
||||
ClearAllCells();
|
||||
_lastDotRoomId = null;
|
||||
_mapSvc = null;
|
||||
_playerProvider = null;
|
||||
}
|
||||
|
||||
private void ClearAllCells()
|
||||
{
|
||||
foreach (var cell in _cells.Values)
|
||||
if (cell != null) Destroy(cell.gameObject);
|
||||
_cells.Clear();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
UpdatePlayerDot();
|
||||
}
|
||||
|
||||
// ── 事件响应 ──────────────────────────────────────────────────────────
|
||||
|
||||
private void OnRoomChanged(string _) => RefreshView();
|
||||
|
||||
private void OnMapUpdated(string roomId)
|
||||
{
|
||||
if (_cells.TryGetValue(roomId, out var cell))
|
||||
cell.SetVisibility(_mapSvc.GetVisibility(roomId));
|
||||
}
|
||||
|
||||
// ── 视图重建 ──────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 以玩家当前房间中心为基准,增量更新可视格内的格子:
|
||||
/// 回收超出范围的旧格子,实例化刚进入范围的新格子,重定位全部格子到新中心。
|
||||
/// </summary>
|
||||
private void RefreshView()
|
||||
{
|
||||
var db = _mapSvc?.Database;
|
||||
if (db?.AllRooms == null) return;
|
||||
|
||||
var currentRoomId = _playerProvider?.CurrentRoomId;
|
||||
if (string.IsNullOrEmpty(currentRoomId)) return;
|
||||
|
||||
var currentRoom = db.GetRoom(currentRoomId);
|
||||
if (currentRoom == null) return;
|
||||
|
||||
_currentCenter = currentRoom.GridPosition + currentRoom.GridSize / 2;
|
||||
|
||||
int minX = _currentCenter.x - _viewRadiusCells;
|
||||
int maxX = _currentCenter.x + _viewRadiusCells;
|
||||
int minY = _currentCenter.y - _viewRadiusCells;
|
||||
int maxY = _currentCenter.y + _viewRadiusCells;
|
||||
|
||||
// ① 回收不在可视范围内的格子(复用 _toRemove 避免每帧 new)
|
||||
_toRemove.Clear();
|
||||
foreach (var (id, cell) in _cells)
|
||||
{
|
||||
var r = db.GetRoom(id);
|
||||
if (r == null || !RoomInView(r, minX, maxX, minY, maxY))
|
||||
{
|
||||
if (cell != null) Destroy(cell.gameObject);
|
||||
_toRemove.Add(id);
|
||||
}
|
||||
}
|
||||
foreach (var id in _toRemove) _cells.Remove(id);
|
||||
|
||||
// ② 实例化新进入范围的格子
|
||||
foreach (var room in db.AllRooms)
|
||||
{
|
||||
if (room == null || _cells.ContainsKey(room.RoomId)) continue;
|
||||
if (!RoomInView(room, minX, maxX, minY, maxY)) continue;
|
||||
|
||||
var cell = Instantiate(_cellPrefab, _cellContainer);
|
||||
cell.Setup(room, _mapSvc.GetVisibility(room.RoomId), null);
|
||||
cell.SetColors(_colorExplored, _colorMapped, _colorUnknown);
|
||||
_cells[room.RoomId] = cell;
|
||||
}
|
||||
|
||||
// ③ 重定位所有格子(中心发生变化时)
|
||||
foreach (var (id, cell) in _cells)
|
||||
{
|
||||
if (cell == null) continue;
|
||||
var r = db.GetRoom(id);
|
||||
if (r != null) PlaceCell(cell, r);
|
||||
}
|
||||
|
||||
UpdatePlayerDot();
|
||||
}
|
||||
|
||||
// ── 格子位置 ──────────────────────────────────────────────────────────
|
||||
|
||||
private void PlaceCell(MapRoomCellUI cell, MapRoomDataSO room)
|
||||
{
|
||||
cell.RT.sizeDelta = new Vector2(room.GridSize.x * _cellPixels, room.GridSize.y * _cellPixels);
|
||||
cell.RT.anchoredPosition = new Vector2(
|
||||
(room.GridPosition.x - _currentCenter.x) * _cellPixels,
|
||||
(room.GridPosition.y - _currentCenter.y) * _cellPixels);
|
||||
}
|
||||
|
||||
private static bool RoomInView(MapRoomDataSO room, int minX, int maxX, int minY, int maxY)
|
||||
=> room.GridPosition.x + room.GridSize.x > minX &&
|
||||
room.GridPosition.x < maxX &&
|
||||
room.GridPosition.y + room.GridSize.y > minY &&
|
||||
room.GridPosition.y < maxY;
|
||||
|
||||
// ── 玩家圆点 ──────────────────────────────────────────────────────────
|
||||
|
||||
private void UpdatePlayerDot()
|
||||
{
|
||||
if (_playerDot == null || _playerProvider == null) return;
|
||||
|
||||
var roomId = _playerProvider.CurrentRoomId;
|
||||
var normPos = _playerProvider.NormalizedPositionInRoom;
|
||||
|
||||
// Dirty check: 避免房间和位置均未变化时写 RectTransform
|
||||
if (roomId == _lastDotRoomId && normPos == _lastDotNormPos) return;
|
||||
_lastDotRoomId = roomId;
|
||||
_lastDotNormPos = normPos;
|
||||
|
||||
if (string.IsNullOrEmpty(roomId) || !_cells.TryGetValue(roomId, out var cell))
|
||||
{
|
||||
_playerDot.enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_playerDot.enabled = true;
|
||||
_playerDot.rectTransform.anchoredPosition =
|
||||
cell.RT.anchoredPosition
|
||||
+ Vector2.Scale(normPos, cell.RT.sizeDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user