using Cysharp.Threading.Tasks; using DG.Tweening; using System.Threading; using System.Threading.Tasks; using TMPro; using UnityEngine; namespace CharacterSystem { public class MessageBox : MonoBehaviour { [SerializeField] private Transform _frame; [SerializeField] private Vector2 _localPos; [SerializeField] private TextMeshProUGUI nameLabel; [SerializeField] private TextMeshProUGUI textField; [SerializeField] private AudioSource _audioSource; [SerializeField] private AudioClip typeClip; private bool nowTyping; private CancellationTokenSource _cts; private float lastTypeSoundTime; private float typeClipLength; public void Initialize(string characterName) { nameLabel.text = characterName; _frame.position = _localPos; lastTypeSoundTime = 0; typeClipLength = typeClip != null ? typeClip.length : 0; } public async UniTask PlayReplica(Replica replica, bool force = false) { if (nowTyping && force == false) return; Stop(); _cts = new(); nowTyping = true; await GetShowSequence().WithCancellation(_cts.Token); await TypeAsync(replica, _cts.Token); await UniTask.Delay(1000, cancellationToken: _cts.Token); await GetHideSequence().WithCancellation(_cts.Token); Stop(); } private async UniTask TypeAsync(Replica replica, CancellationToken ct) { string message = replica.text; if (replica.Duration == 0 || string.IsNullOrEmpty(message)) { textField.text = message; return; } if (replica.clip != null) { _audioSource.clip = replica.clip; _audioSource.Play(); SoundManager.instance.MusicFade(fadeDuration: 0.2f, holdDuration: replica.clip.length - 0.2f, targetVolumePercent: 0.5f); } float elapsed = 0; textField.text = string.Empty; while (elapsed < replica.Duration && ct.IsCancellationRequested == false) { float percent = Mathf.Clamp01(elapsed / replica.Duration); int symbolsToShow = (int)(message.Length * percent); string text = message.Substring(0, symbolsToShow); textField.text = text; bool shouldPlayClip = typeClipLength != 0 && Time.time - lastTypeSoundTime > typeClipLength; if (shouldPlayClip) { _audioSource.PlayOneShot(typeClip); lastTypeSoundTime = Time.time; } await UniTask.Yield(ct); elapsed += Time.deltaTime; } if (ct.IsCancellationRequested == false) { textField.text = message; } } public void Stop() { if (_cts != null) { _cts.Cancel(); _cts.Dispose(); _cts = null; } nowTyping = false; textField.text = string.Empty; } private Sequence GetShowSequence() { Sequence seq = DOTween.Sequence(); seq.Append(_frame.DOScale(1, 0.2f).SetEase(Ease.OutBack)); return seq; } private Sequence GetHideSequence() { Sequence seq = DOTween.Sequence(); seq.Append(_frame.DOScale(0, 0.2f).SetEase(Ease.InBack)); return seq; } } }