using System; using DG.Tweening; using TMPro; using UnityEngine; namespace Ichni.Story { /// /// 由 按需实例化的 Helper 专属对话气泡。 /// 每个实例只显示一条文本:淡入、停留、淡出后自动销毁,因此场景中不需要常驻气泡, /// 也不会与通用 MessageBox 队列产生耦合。 /// [RequireComponent(typeof(CanvasGroup))] public class StoryHelperTalk : MonoBehaviour { [SerializeField, Tooltip("显示 Helper 名称的文本。")] private TMP_Text speakerText; [SerializeField, Tooltip("显示随机 Helper 对话的文本。")] private TMP_Text contentText; [Header("Animation")] [SerializeField, Min(0.01f), Tooltip("淡入与缩放入场时间。")] private float showDuration = 0.18f; [SerializeField, Min(0f), Tooltip("气泡完整显示后的停留时间。")] private float visibleDuration = 4.5f; [SerializeField, Min(0.01f), Tooltip("淡出与缩放退场时间。")] private float hideDuration = 0.16f; [SerializeField, Range(0.5f, 1f), Tooltip("入场与退场时相对原始大小的缩放比例。")] private float initialScaleMultiplier = 0.92f; /// 气泡结束显示并准备销毁时触发,供创建者清理当前实例引用。 public event Action Closed; private CanvasGroup _canvasGroup; private RectTransform _rectTransform; private Sequence _sequence; private Vector3 _baseScale; private bool _isClosed; private void Awake() { _canvasGroup = GetComponent(); _rectTransform = transform as RectTransform; _baseScale = _rectTransform != null ? _rectTransform.localScale : Vector3.one; } /// /// 写入文本并播放完整的出现、停留与收回流程。 /// 所有 DOTween Sequence 均先 Pause,再手动调用 Play,符合项目约定。 /// public void Show(string speaker, string content) { if (speakerText == null || contentText == null || _rectTransform == null) { Debug.LogWarning("[StoryHelperTalk] Prefab 未配置 Speaker Text、Content Text 或 RectTransform,已取消显示。", this); CloseImmediately(); return; } speakerText.text = speaker; contentText.text = content; _canvasGroup.alpha = 0f; _rectTransform.localScale = _baseScale * initialScaleMultiplier; _sequence?.Kill(); _sequence = DOTween.Sequence() .Pause() .SetUpdate(true) .Append(_canvasGroup.DOFade(1f, showDuration)) .Join(_rectTransform.DOScale(_baseScale, showDuration).SetEase(Ease.OutBack)) .AppendInterval(visibleDuration) .Append(_canvasGroup.DOFade(0f, hideDuration)) .Join(_rectTransform.DOScale(_baseScale * initialScaleMultiplier, hideDuration).SetEase(Ease.InQuad)) .OnComplete(CloseImmediately); _sequence.Play(); } /// /// 立即关闭并销毁该运行时实例。再次点击 Helper 时会调用它,使旧文本不会与新文本叠加。 /// public void CloseImmediately() { if (_isClosed) return; _isClosed = true; _sequence?.Kill(); _sequence = null; Closed?.Invoke(this); Destroy(gameObject); } private void OnDestroy() { _sequence?.Kill(); } } }