Files
zeling_v2/Assets/_Game/Scripts/UI/ToastManager.cs
2026-05-25 13:21:41 +08:00

134 lines
5.1 KiB
C#
Raw 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 System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using BaseGames.Core.Events;
using BaseGames.Localization;
namespace BaseGames.UI
{
/// <summary>
/// 单条通知弹窗(架构 10_UIModule §11
/// 由 ToastManager 控制显示和自动隐藏。
/// </summary>
[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;
private WaitForSecondsRealtime _waitDisplay; // 缓存:避免每次 Show 堆分配
/// <summary>淡入 + 保持 + 淡出的总时长(供 ToastManager 队列计时用)。</summary>
public float TotalTime => _displayDuration + _fadeDuration * 2f;
private void Awake()
{
_cg = GetComponent<CanvasGroup>();
_cg.alpha = 0f;
_waitDisplay = new WaitForSecondsRealtime(_displayDuration);
}
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));
// 保持(复用缓存的 WaitForSecondsRealtime
yield return _waitDisplay;
// 淡出
yield return StartCoroutine(FadeTo(0f));
gameObject.SetActive(false);
}
private IEnumerator FadeTo(float target)
=> UITween.FadeCanvasGroup(_cg, target, _fadeDuration);
}
// ─────────────────────────────────────────────────────────────────────────
/// <summary>
/// 通知队列管理器(架构 10_UIModule §11 ToastManager
/// 同时只显示一条 Toast队列不为空时前一条结束后立即显示下一条。
/// 订阅 EVT_AchievementUnlocked / EVT_AbilityUnlocked 事件频道。
/// </summary>
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 WaitForSecondsRealtime _queueWait; // 缓存:避免 ProcessQueue 每条堆分配
private void Awake()
{
// _toast.TotalTime 基于 Inspector 常量,只需 Awake 时初始化一次
if (_toast != null)
_queueWait = new WaitForSecondsRealtime(_toast.TotalTime + 0.1f);
}
private void OnEnable()
{
_onAchievementUnlocked?.Subscribe(OnAchievement).AddTo(_subs);
_onAbilityUnlocked?.Subscribe(OnAbility).AddTo(_subs);
}
private void OnDisable()
{
_subs.Clear();
}
private void OnAchievement(string id)
=> Enqueue(LocalizationManager.Get("TOAST_ACHIEVEMENT_TITLE", LocalizationTable.UI), id, null);
private void OnAbility(string abilityId)
=> Enqueue(LocalizationManager.Get("TOAST_ABILITY_TITLE", LocalizationTable.UI), 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();
// _toast 为 null 时直接终止队列(不能显示,也无需等待)
if (_toast == null) break;
_toast.Show(title, body, icon);
// 使用缓存的等待对象_toast 为 null 时已在上方 break
if (_queueWait == null)
_queueWait = new WaitForSecondsRealtime(_toast.TotalTime + 0.1f);
yield return _queueWait;
}
_showing = false;
}
}
}