Инкапсулированы методы появления и ухода персонажа Инкапсулирована работа с аниматором Работа с репликами вынесена в ScriptableObject Логика MessageBox вынесена в отдельный класс Переработан event bus персонажей Сложность и призовой модуль вынесены в отдельные классы Character.cs приведён к Single Responsibility Principle.
122 lines
3.7 KiB
C#
122 lines
3.7 KiB
C#
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;
|
|
}
|
|
}
|
|
} |