using UnityEngine; using UnityEngine.Serialization; using TMPro; using System.Collections.Generic; using BaseGames.Core; using BaseGames.Core.Events; namespace BaseGames.World.Map { /// /// 探索进度百分比 UI 组件。 /// 显示全局探索进度和当前区域探索进度,随 OnExplorationChanged 实时更新。 /// 挂在 HUD 或地图面板下;需配置两个 TMP_Text 字段和可选的 StringEventChannelSO。 /// 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 _regionDict; private void Awake() { BuildRegionDict(); } private void OnValidate() => BuildRegionDict(); private void OnEnable() { _mapSvc = ServiceLocator.GetOrDefault(); 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(); } /// 立即刷新进度文本。可由外部调用(例如地图面板打开时)。 public void Refresh() { if (_mapSvc == null) _mapSvc = ServiceLocator.GetOrDefault(); if (_mapSvc == null) return; // 全局探索进度 if (_globalProgressText != null) { float progress = _mapSvc.GetExplorationProgress(); try { _globalProgressText.text = string.Format(_globalFormat, 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(_regionFormat, regionProgress, regionDisplayName); } catch (System.FormatException) { _regionProgressText.text = $"{regionDisplayName}:{regionProgress:P0}"; Debug.LogWarning($"[MapProgressDisplay] _regionFormat 格式字符串配置有误:'{_regionFormat}',已回退到默认显示。", this); } } } } // ── 辅助方法 ────────────────────────────────────────────────────────── /// 预建 RegionId → Entry 字典,O(1) 查询。 private void BuildRegionDict() => _regionDict = MapServiceExtensions.BuildRegionDict(_regionNames); /// /// 将 regionId 解析为玩家可读的显示名。 /// 优先读 LocKey(本地化),其次 DisplayName,最后回退到 regionId 本身。 /// private string ResolveRegionDisplayName(string regionId) => MapServiceExtensions.ResolveRegionDisplayName(_regionDict, regionId); } }