Files
zeling_v2/Assets/_Game/Scripts/World/Map/MapProgressDisplay.cs
2026-06-05 18:41:33 +08:00

155 lines
6.8 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
using UnityEngine.Serialization;
using TMPro;
using System.Collections.Generic;
using BaseGames.Core;
using BaseGames.Core.Events;
using BaseGames.Localization;
namespace BaseGames.World.Map
{
/// <summary>
/// 探索进度百分比 UI 组件。
/// 显示全局探索进度和当前区域探索进度,随 OnExplorationChanged 实时更新。
/// <para>挂在 HUD 或地图面板下;需配置两个 TMP_Text 字段和可选的 StringEventChannelSO。</para>
/// </summary>
public class MapProgressDisplay : MonoBehaviour
{
[Header("文本组件")]
[Tooltip("全局探索进度文本,例如 '已探索 42%'。留空则不更新。")]
[SerializeField] private TMP_Text _globalProgressText;
[Tooltip("当前区域进度文本,例如 '区域67%'。留空则不更新。")]
[SerializeField] private TMP_Text _regionProgressText;
[Header("格式字符串({0:P0} = 百分比,{1} = 区域显示名)")]
[SerializeField] private string _globalFormat = "已探索 {0:P0}";
[SerializeField] private string _regionFormat = "{1}{0:P0}";
[Header("区域名映射(与 RegionNameDisplay 共用同一机制)")]
[Tooltip("RegionId → 本地化显示名映射。未配置时直接显示 RegionId。")]
[FormerlySerializedAs("_regionNameEntries")]
[SerializeField] private RegionNameEntry[] _regionNames;
[Header("Event Channels")]
[Tooltip("区域切换事件;订阅后区域切换时自动刷新区域进度。")]
[SerializeField] private StringEventChannelSO _onRegionChanged;
private IMapService _mapSvc;
private string _currentRegionId;
private readonly CompositeDisposable _subs = new();
// R15-N2 RegionId → Entry 字典,将 ResolveRegionDisplayName 从 O(N) 降至 O(1)
private Dictionary<string, RegionNameEntry> _regionDict;
private void Awake()
{
BuildRegionDict();
}
private void OnValidate() => BuildRegionDict();
private void OnEnable()
{
_mapSvc = ServiceLocator.GetOrDefault<IMapService>();
if (_mapSvc != null)
{
_mapSvc.OnExplorationChanged += Refresh;
_currentRegionId = _mapSvc.CurrentRegionId;
}
_onRegionChanged?.Subscribe(OnRegionChanged).AddTo(_subs);
Refresh();
}
private void OnDisable()
{
if (_mapSvc != null)
{
_mapSvc.OnExplorationChanged -= Refresh;
_mapSvc = null;
}
_subs.Clear();
}
private void OnRegionChanged(string regionId)
{
_currentRegionId = regionId;
Refresh();
}
/// <summary>立即刷新进度文本。可由外部调用(例如地图面板打开时)。</summary>
public void Refresh()
{
if (_mapSvc == null)
_mapSvc = ServiceLocator.GetOrDefault<IMapService>();
if (_mapSvc == null) return;
// 全局探索进度
if (_globalProgressText != null)
{
float progress = _mapSvc.GetExplorationProgress();
try
{
_globalProgressText.text = string.Format(ResolveFormat(_globalFormat, "{0:P0}"), progress);
}
catch (System.FormatException)
{
_globalProgressText.text = $"{progress:P0}";
Debug.LogWarning($"[MapProgressDisplay] _globalFormat 格式字符串配置有误:'{_globalFormat}',已回退到默认显示。", this);
}
}
// 当前区域进度
if (_regionProgressText != null && !string.IsNullOrEmpty(_currentRegionId))
{
var rooms = _mapSvc.GetRoomsByRegion(_currentRegionId);
if (rooms != null && rooms.Length > 0)
{
int exploredCount = 0;
foreach (var r in rooms)
if (r != null && _mapSvc.IsExplored(r.RoomId)) exploredCount++;
float regionProgress = (float)exploredCount / rooms.Length;
// R15-N2 解析区域显示名(本地化 Key → DisplayName → RegionId 回退)
string regionDisplayName = ResolveRegionDisplayName(_currentRegionId);
try
{
_regionProgressText.text = string.Format(ResolveFormat(_regionFormat, "{1} {0:P0}"), regionProgress, regionDisplayName);
}
catch (System.FormatException)
{
_regionProgressText.text = $"{regionDisplayName}{regionProgress:P0}";
Debug.LogWarning($"[MapProgressDisplay] _regionFormat 格式字符串配置有误:'{_regionFormat}',已回退到默认显示。", this);
}
}
}
}
// ── 辅助方法 ──────────────────────────────────────────────────────────
/// <summary>
/// 将格式串字段解析为本地化格式串,并保证始终含 {0} 占位符:
/// ① 字段填本地化 Key如 "MAP_PROGRESS_GLOBAL")→ 返回本地化值(如 "已探索 {0:P0}"
/// ② 字段直接是格式串 → Get 原样返回(向后兼容);
/// ③ 本地化服务缺失 / Key 未命中(返回值无 {0})→ 回退到 <paramref name="fallback"/>
/// 确保即使本地化未就绪也不丢失百分比数值。
/// </summary>
private static string ResolveFormat(string keyOrFormat, string fallback)
{
if (string.IsNullOrEmpty(keyOrFormat)) return fallback;
string s = LocalizationManager.Get(keyOrFormat, LocalizationTable.UI);
// 检测 "{0"(兼容 "{0}"、"{0:P0}"、"{0:F1}" 等格式说明符);缺占位符视为未解析,用回退
return (!string.IsNullOrEmpty(s) && s.Contains("{0")) ? s : fallback;
}
/// <summary>预建 RegionId → Entry 字典O(1) 查询。</summary>
private void BuildRegionDict()
=> _regionDict = MapServiceExtensions.BuildRegionDict(_regionNames);
/// <summary>
/// 将 regionId 解析为玩家可读的显示名。
/// 优先读 LocKey本地化其次 DisplayName最后回退到 regionId 本身。
/// </summary>
private string ResolveRegionDisplayName(string regionId)
=> MapServiceExtensions.ResolveRegionDisplayName(_regionDict, regionId);
}
}