using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using TMPro; using BaseGames.Core.Events; namespace BaseGames.UI { /// /// 单条通知弹窗(架构 10_UIModule §11)。 /// 由 ToastManager 控制显示和自动隐藏。 /// [RequireComponent(typeof(CanvasGroup))] public class ToastNotification : MonoBehaviour { [SerializeField] private TMP_Text _titleText; [SerializeField] private TMP_Text _bodyText; [SerializeField] private Image _icon; [SerializeField] private float _displayDuration = 3f; [SerializeField] private float _fadeDuration = 0.25f; private CanvasGroup _cg; private Coroutine _hideCoroutine; /// 淡入 + 保持 + 淡出的总时长(供 ToastManager 队列计时用)。 public float TotalTime => _displayDuration + _fadeDuration * 2f; private void Awake() { _cg = GetComponent(); _cg.alpha = 0f; } public void Show(string title, string body, Sprite icon = null) { if (_titleText != null) _titleText.text = title; if (_bodyText != null) _bodyText.text = body; if (_icon != null) { _icon.sprite = icon; _icon.enabled = icon != null; } gameObject.SetActive(true); if (_hideCoroutine != null) StopCoroutine(_hideCoroutine); _hideCoroutine = StartCoroutine(AutoHide()); } private IEnumerator AutoHide() { // 淡入 yield return StartCoroutine(FadeTo(1f)); // 保持 yield return new WaitForSecondsRealtime(_displayDuration); // 淡出 yield return StartCoroutine(FadeTo(0f)); gameObject.SetActive(false); } private IEnumerator FadeTo(float target) { float start = _cg.alpha; float elapsed = 0f; while (elapsed < _fadeDuration) { _cg.alpha = Mathf.Lerp(start, target, elapsed / _fadeDuration); elapsed += Time.unscaledDeltaTime; yield return null; } _cg.alpha = target; } } // ───────────────────────────────────────────────────────────────────────── /// /// 通知队列管理器(架构 10_UIModule §11 ToastManager)。 /// 同时只显示一条 Toast;队列不为空时前一条结束后立即显示下一条。 /// 订阅 EVT_AchievementUnlocked / EVT_AbilityUnlocked 事件频道。 /// public class ToastManager : MonoBehaviour { [SerializeField] private ToastNotification _toast; [Header("Event Channels")] [SerializeField] private StringEventChannelSO _onAchievementUnlocked; // EVT_AchievementUnlocked [SerializeField] private StringEventChannelSO _onAbilityUnlocked; // EVT_AbilityUnlocked private readonly Queue<(string title, string body, Sprite icon)> _queue = new(); private readonly CompositeDisposable _subs = new(); private bool _showing; private void OnEnable() { _onAchievementUnlocked?.Subscribe(OnAchievement).AddTo(_subs); _onAbilityUnlocked?.Subscribe(OnAbility).AddTo(_subs); } private void OnDisable() { _subs.Clear(); } private void OnAchievement(string id) => Enqueue("成就解锁", id, null); private void OnAbility(string abilityId) => Enqueue("能力获得", abilityId, null); public void Enqueue(string title, string body, Sprite icon = null) { _queue.Enqueue((title, body, icon)); if (!_showing) StartCoroutine(ProcessQueue()); } private IEnumerator ProcessQueue() { _showing = true; while (_queue.Count > 0) { var (title, body, icon) = _queue.Dequeue(); if (_toast != null) _toast.Show(title, body, icon); // 等待 Toast 完成后再显示下一条(与 ToastNotification._displayDuration/_fadeDuration 保持同步) float wait = _toast != null ? _toast.TotalTime + 0.1f : 3.6f; yield return new WaitForSecondsRealtime(wait); } _showing = false; } } }