203 lines
7.3 KiB
C#
203 lines
7.3 KiB
C#
using Cysharp.Threading.Tasks;
|
|
using DG.Tweening;
|
|
using System;
|
|
using System.Threading;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.Serialization;
|
|
|
|
namespace CharacterSystem
|
|
{
|
|
public class MessageBox : MonoBehaviour
|
|
{
|
|
[SerializeField] private Transform _frame;
|
|
[SerializeField] private Vector2 _localPos;
|
|
[SerializeField] private Vector3 _visibleScale = Vector3.one;
|
|
[SerializeField, FormerlySerializedAs("nameLabel")] private TextMeshProUGUI _nameLabel;
|
|
[SerializeField, FormerlySerializedAs("textField")] private TextMeshProUGUI _textField;
|
|
[SerializeField] private AudioSource _audioSource;
|
|
[SerializeField, FormerlySerializedAs("typeClip")] private AudioClip _typeClip;
|
|
[SerializeField, Min(0.01f)] private float _minimumFontSize = 0.1f;
|
|
[SerializeField, Min(0.01f)] private float _maximumFontSize = 0.24f;
|
|
[SerializeField, Range(1, 16)] private int _fontSizeSearchIterations = 10;
|
|
|
|
private CancellationTokenSource _cancellationTokenSource;
|
|
private bool _isProcessing;
|
|
private float _lastTypeSoundTime;
|
|
private float _typeClipLength;
|
|
|
|
private void OnDestroy()
|
|
=> Stop();
|
|
|
|
public void Initialize(string characterName)
|
|
{
|
|
_nameLabel.text = characterName;
|
|
_nameLabel.gameObject.SetActive(false);
|
|
_frame.localPosition = _localPos;
|
|
_frame.localScale = Vector3.zero;
|
|
_lastTypeSoundTime = 0f;
|
|
_typeClipLength = _typeClip != null ? _typeClip.length : 0f;
|
|
}
|
|
|
|
public void PlayReplica(Replica replica, bool force = false, bool showName = false)
|
|
{
|
|
if (_isProcessing && force == false)
|
|
return;
|
|
|
|
if (force)
|
|
CancelCurrent();
|
|
|
|
SetOptimalFontSize(replica.text ?? string.Empty);
|
|
ProcessReplicaAsync(replica, showName).Forget();
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
CancelCurrent();
|
|
_cancellationTokenSource = null;
|
|
_isProcessing = false;
|
|
_audioSource.Stop();
|
|
_frame.DOKill();
|
|
_frame.localScale = Vector3.zero;
|
|
_nameLabel.gameObject.SetActive(false);
|
|
_textField.text = string.Empty;
|
|
}
|
|
|
|
private async UniTask ProcessReplicaAsync(Replica replica, bool showName)
|
|
{
|
|
var currentTokenSource = new CancellationTokenSource();
|
|
_cancellationTokenSource = currentTokenSource;
|
|
_isProcessing = true;
|
|
|
|
try
|
|
{
|
|
await PlayReplicaAsync(replica, showName, currentTokenSource.Token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
}
|
|
finally
|
|
{
|
|
if (_cancellationTokenSource == currentTokenSource)
|
|
{
|
|
_cancellationTokenSource = null;
|
|
_isProcessing = false;
|
|
}
|
|
|
|
currentTokenSource.Dispose();
|
|
}
|
|
}
|
|
|
|
private async UniTask PlayReplicaAsync(Replica replica, bool showName, CancellationToken cancellationToken)
|
|
{
|
|
_nameLabel.gameObject.SetActive(showName);
|
|
await GetShowSequence().WithCancellation(cancellationToken);
|
|
await TypeAsync(replica, cancellationToken);
|
|
await UniTask.Delay(1000, cancellationToken: cancellationToken);
|
|
await GetHideSequence().WithCancellation(cancellationToken);
|
|
|
|
_audioSource.Stop();
|
|
_nameLabel.gameObject.SetActive(false);
|
|
_textField.text = string.Empty;
|
|
}
|
|
|
|
private async UniTask TypeAsync(Replica replica, CancellationToken cancellationToken)
|
|
{
|
|
string message = replica.text ?? string.Empty;
|
|
|
|
if (replica.clip != null)
|
|
{
|
|
_audioSource.clip = replica.clip;
|
|
_audioSource.Play();
|
|
|
|
if (SoundManager.instance != null)
|
|
{
|
|
SoundManager.instance.DuckMusic(
|
|
fadeDuration: 0.2f,
|
|
holdDuration: Mathf.Max(replica.clip.length - 0.2f, 0f),
|
|
targetVolumePercent: 0.5f);
|
|
}
|
|
}
|
|
|
|
float duration = replica.Duration;
|
|
float elapsedTime = 0f;
|
|
_textField.text = string.Empty;
|
|
|
|
while (elapsedTime < duration && cancellationToken.IsCancellationRequested == false)
|
|
{
|
|
float progress = Mathf.Clamp01(elapsedTime / duration);
|
|
int symbolsToShow = Mathf.FloorToInt(message.Length * progress);
|
|
_textField.text = message.Substring(0, symbolsToShow);
|
|
|
|
bool shouldPlayTypeClip = _typeClipLength > 0f && Time.time - _lastTypeSoundTime > _typeClipLength;
|
|
if (shouldPlayTypeClip)
|
|
{
|
|
_audioSource.PlayOneShot(_typeClip);
|
|
_lastTypeSoundTime = Time.time;
|
|
}
|
|
|
|
await UniTask.Yield(cancellationToken);
|
|
elapsedTime += Time.deltaTime;
|
|
}
|
|
|
|
if (cancellationToken.IsCancellationRequested == false)
|
|
_textField.text = message;
|
|
}
|
|
|
|
private void CancelCurrent()
|
|
{
|
|
if (_cancellationTokenSource != null && _cancellationTokenSource.IsCancellationRequested == false)
|
|
_cancellationTokenSource.Cancel();
|
|
}
|
|
|
|
private void SetOptimalFontSize(string message)
|
|
{
|
|
_textField.enableAutoSizing = false;
|
|
|
|
float minimumFontSize = Mathf.Min(_minimumFontSize, _maximumFontSize);
|
|
float maximumFontSize = Mathf.Max(_minimumFontSize, _maximumFontSize);
|
|
|
|
if (string.IsNullOrEmpty(message))
|
|
{
|
|
_textField.fontSize = maximumFontSize;
|
|
return;
|
|
}
|
|
|
|
Rect fieldRect = _textField.rectTransform.rect;
|
|
Vector4 margin = _textField.margin;
|
|
float availableWidth = Mathf.Max(fieldRect.width - margin.x - margin.z, 0.01f);
|
|
float availableHeight = Mathf.Max(fieldRect.height - margin.y - margin.w, 0.01f);
|
|
|
|
for (int i = 0; i < _fontSizeSearchIterations; i++)
|
|
{
|
|
float candidateFontSize = (minimumFontSize + maximumFontSize) * 0.5f;
|
|
_textField.fontSize = candidateFontSize;
|
|
Vector2 preferredSize = _textField.GetPreferredValues(message, availableWidth, 0f);
|
|
|
|
if (preferredSize.x <= availableWidth && preferredSize.y <= availableHeight)
|
|
minimumFontSize = candidateFontSize;
|
|
else
|
|
maximumFontSize = candidateFontSize;
|
|
}
|
|
|
|
_textField.fontSize = minimumFontSize;
|
|
}
|
|
|
|
private Sequence GetShowSequence()
|
|
{
|
|
Sequence sequence = DOTween.Sequence();
|
|
sequence.Append(_frame.DOScale(_visibleScale, 0.2f).SetEase(Ease.OutBack));
|
|
sequence.PlayForward();
|
|
return sequence;
|
|
}
|
|
|
|
private Sequence GetHideSequence()
|
|
{
|
|
Sequence sequence = DOTween.Sequence();
|
|
sequence.Append(_frame.DOScale(Vector3.zero, 0.2f).SetEase(Ease.InBack));
|
|
sequence.PlayForward();
|
|
return sequence;
|
|
}
|
|
}
|
|
}
|