103 lines
3.8 KiB
C#
103 lines
3.8 KiB
C#
using System;
|
||
using DG.Tweening;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
|
||
namespace Ichni.Story
|
||
{
|
||
/// <summary>
|
||
/// 由 <see cref="StoryHelperController"/> 按需实例化的 Helper 专属对话气泡。
|
||
/// <para>每个实例只显示一条文本:淡入、停留、淡出后自动销毁,因此场景中不需要常驻气泡,
|
||
/// 也不会与通用 MessageBox 队列产生耦合。</para>
|
||
/// </summary>
|
||
[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;
|
||
|
||
/// <summary>气泡结束显示并准备销毁时触发,供创建者清理当前实例引用。</summary>
|
||
public event Action<StoryHelperTalk> Closed;
|
||
|
||
private CanvasGroup _canvasGroup;
|
||
private RectTransform _rectTransform;
|
||
private Sequence _sequence;
|
||
private Vector3 _baseScale;
|
||
private bool _isClosed;
|
||
|
||
private void Awake()
|
||
{
|
||
_canvasGroup = GetComponent<CanvasGroup>();
|
||
_rectTransform = transform as RectTransform;
|
||
_baseScale = _rectTransform != null ? _rectTransform.localScale : Vector3.one;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 写入文本并播放完整的出现、停留与收回流程。
|
||
/// 所有 DOTween Sequence 均先 Pause,再手动调用 Play,符合项目约定。
|
||
/// </summary>
|
||
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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 立即关闭并销毁该运行时实例。再次点击 Helper 时会调用它,使旧文本不会与新文本叠加。
|
||
/// </summary>
|
||
public void CloseImmediately()
|
||
{
|
||
if (_isClosed)
|
||
return;
|
||
|
||
_isClosed = true;
|
||
_sequence?.Kill();
|
||
_sequence = null;
|
||
Closed?.Invoke(this);
|
||
Destroy(gameObject);
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
_sequence?.Kill();
|
||
}
|
||
}
|
||
}
|