134 lines
5.1 KiB
C#
134 lines
5.1 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|