using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using TMPro; using BaseGames.Core.Events; using BaseGames.Localization; namespace BaseGames.UI { /// /// Boss 血条 UI(架构 10_UIModule §4)。 /// 默认隐藏,Boss 战开始时从屏幕底部滑入。 /// 阶段标记点由 BossHPMax 与 PhaseThreshold 共同决定(此处由外部广播 EVT_BossHPMaxSet 设置)。 /// public class BossHPBar : MonoBehaviour { [SerializeField] private TMP_Text _bossNameText; [SerializeField] private Image _hpFill; [SerializeField] private Transform _phaseMarkersRoot; [SerializeField] private GameObject _phaseMarkerPrefab; [SerializeField] private float _slideDistance = 120f; // 滑入距离(像素) [SerializeField] private float _slideDuration = 0.3f; [Header("Event Channels")] [SerializeField] private BoolEventChannelSO _onBossFightToggled; // true=开始, false=结束 [SerializeField] private IntEventChannelSO _onBossHPChanged; [SerializeField] private StringEventChannelSO _onBossNameSet; [SerializeField] private IntEventChannelSO _onBossHPMaxSet; [SerializeField] private FloatEventChannelSO _onBossPhaseThreshold; // 每个阶段切换阈值(0-1) private int _maxHP; private RectTransform _rect; private Vector2 _shownPos; private Vector2 _hiddenPos; private Coroutine _slideCoroutine; private readonly List _pendingThresholds = new(); private readonly CompositeDisposable _subs = new(); private void Awake() { _rect = (RectTransform)transform; _shownPos = _rect.anchoredPosition; _hiddenPos = _shownPos - new Vector2(0, _slideDistance); _rect.anchoredPosition = _hiddenPos; gameObject.SetActive(false); } private void OnEnable() { _onBossFightToggled?.Subscribe(OnBossFightToggled).AddTo(_subs); _onBossHPChanged?.Subscribe(OnHPChanged).AddTo(_subs); _onBossNameSet?.Subscribe(OnNameSet).AddTo(_subs); _onBossHPMaxSet?.Subscribe(OnMaxSet).AddTo(_subs); _onBossPhaseThreshold?.Subscribe(OnPhaseThreshold).AddTo(_subs); } private void OnDisable() => _subs.Clear(); // ── 事件回调 ────────────────────────────────────────────────────────── private void OnBossFightToggled(bool started) { if (_slideCoroutine != null) StopCoroutine(_slideCoroutine); if (started) { gameObject.SetActive(true); _slideCoroutine = StartCoroutine(SlideTo(_shownPos)); } else { _pendingThresholds.Clear(); _slideCoroutine = StartCoroutine(SlideOut()); } } private void OnHPChanged(int hp) { if (_maxHP > 0) _hpFill.fillAmount = (float)hp / _maxHP; } private void OnNameSet(string bossName) { if (_bossNameText == null) return; string loc = !string.IsNullOrEmpty(bossName) ? LocalizationManager.Get(bossName, LocalizationTable.Character) : null; _bossNameText.text = !string.IsNullOrEmpty(loc) && loc != bossName ? loc : bossName; } private void OnMaxSet(int max) { _maxHP = max; // 清除旧标记 if (_phaseMarkersRoot != null) { // 逆序删除:避免正序枚举 Transform 子节点同时销毁时的迭代器失效 for (int i = _phaseMarkersRoot.childCount - 1; i >= 0; i--) Destroy(_phaseMarkersRoot.GetChild(i).gameObject); } // 延迟一帧:等 Canvas LayoutRebuilder 完成布局后再读取 rect.width,避免得到 0 StartCoroutine(RebuildMarkersAfterLayout()); } private System.Collections.IEnumerator RebuildMarkersAfterLayout() { yield return null; // 等待 Canvas 完成当前帧的 Layout 传递 foreach (var t in _pendingThresholds) PlacePhaseMarker(t); } private void OnPhaseThreshold(float threshold) { if (threshold <= 0f || threshold >= 1f) return; _pendingThresholds.Add(threshold); if (_maxHP > 0) PlacePhaseMarker(threshold); } private void PlacePhaseMarker(float threshold) { if (_phaseMarkersRoot == null || _phaseMarkerPrefab == null || _hpFill == null) return; var marker = Instantiate(_phaseMarkerPrefab, _phaseMarkersRoot); var markerRect = marker.GetComponent(); if (markerRect != null) { float barWidth = _hpFill.rectTransform.rect.width; markerRect.anchoredPosition = new Vector2(barWidth * threshold, 0f); } marker.SetActive(true); } // ── 动画协程 ────────────────────────────────────────────────────────── private IEnumerator SlideTo(Vector2 target) => UITween.MoveAnchored(_rect, target, _slideDuration); private IEnumerator SlideOut() { yield return UITween.MoveAnchored(_rect, _hiddenPos, _slideDuration); gameObject.SetActive(false); } } }