192 lines
6.0 KiB
C#
192 lines
6.0 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine.InputSystem;
|
|
using UnityEngine;
|
|
using VRShopping.UI;
|
|
|
|
[RequireComponent(typeof(CharacterVoiceObject))]
|
|
public class DialogPlayer : MonoBehaviour
|
|
{
|
|
[SerializeField] private List<DialogGroup> _dialogGroups;
|
|
private Dictionary<string, DialogGroup> _dialogGroupMap;
|
|
private Animator _animator;
|
|
private int _initialGestureHash;
|
|
private int _initialExpressionHash;
|
|
private bool _hasInitialExpression;
|
|
private readonly Dictionary<Transform, Quaternion> _originalRotations = new();
|
|
public bool IsPlaying { get; private set; }
|
|
|
|
private void Awake()
|
|
{
|
|
_dialogGroupMap = new Dictionary<string, DialogGroup>();
|
|
foreach (var g in _dialogGroups)
|
|
_dialogGroupMap[g.DialogGroupName] = g;
|
|
|
|
_animator = GetComponentInChildren<Animator>();
|
|
|
|
if (_animator != null)
|
|
{
|
|
_initialGestureHash = _animator.GetCurrentAnimatorStateInfo(0).fullPathHash;
|
|
if (_animator.layerCount > 1)
|
|
{
|
|
_initialExpressionHash = _animator.GetCurrentAnimatorStateInfo(1).fullPathHash;
|
|
_hasInitialExpression = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Awaitable Play()
|
|
{
|
|
if(_dialogGroups.Count > 0)
|
|
_ = Play(_dialogGroups[0].DialogGroupName);
|
|
}
|
|
|
|
public async Awaitable Play(string groupName)
|
|
{
|
|
if (IsPlaying) return;
|
|
if (!_dialogGroupMap.TryGetValue(groupName, out var group))
|
|
{
|
|
Debug.LogWarning($"[DialogPlayer] 그룹 없음: {groupName}");
|
|
return;
|
|
}
|
|
|
|
IsPlaying = true;
|
|
try
|
|
{
|
|
var node = group.StartNode;
|
|
while (node != null)
|
|
{
|
|
await PlayNode(node);
|
|
|
|
if (node.Choices != null && node.Choices.Count > 0)
|
|
{
|
|
int picked = await WaitForChoice(node);
|
|
node = node.Choices[picked].DestinationNode;
|
|
}
|
|
else
|
|
{
|
|
node = node.Next;
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
IsPlaying = false;
|
|
RestoreDefaultAnimations();
|
|
RestoreRotations();
|
|
}
|
|
|
|
Debug.Log("[DialogPlayer] 대화 종료");
|
|
}
|
|
|
|
private void RestoreDefaultAnimations()
|
|
{
|
|
if (_animator == null) return;
|
|
_animator.CrossFade(_initialGestureHash, 0.3f, 0, normalizedTimeOffset: 0f);
|
|
if (_hasInitialExpression)
|
|
_animator.CrossFade(_initialExpressionHash, 0.3f, 1, normalizedTimeOffset: 0f);
|
|
}
|
|
|
|
private async Awaitable RotateTowardPlayer(Transform target)
|
|
{
|
|
if (Camera.main == null) return;
|
|
var playerCam = Camera.main.transform;
|
|
|
|
float duration = 0.5f;
|
|
float elapsed = 0f;
|
|
|
|
while (elapsed < duration)
|
|
{
|
|
Vector3 dir = playerCam.position - target.position;
|
|
dir.y = 0f;
|
|
if (dir.sqrMagnitude > 0.0001f)
|
|
{
|
|
var targetRot = Quaternion.LookRotation(dir);
|
|
target.rotation = Quaternion.Slerp(target.rotation, targetRot, 10f * Time.deltaTime);
|
|
}
|
|
elapsed += Time.deltaTime;
|
|
await Awaitable.NextFrameAsync();
|
|
}
|
|
}
|
|
|
|
private void RestoreRotations()
|
|
{
|
|
foreach (var kvp in _originalRotations)
|
|
{
|
|
if (kvp.Key != null)
|
|
_ = RotateToRotation(kvp.Key, kvp.Value);
|
|
}
|
|
_originalRotations.Clear();
|
|
}
|
|
|
|
private async Awaitable RotateToRotation(Transform target, Quaternion targetRotation)
|
|
{
|
|
float duration = 0.5f;
|
|
float elapsed = 0f;
|
|
while (elapsed < duration)
|
|
{
|
|
if (target == null) return;
|
|
target.rotation = Quaternion.Slerp(target.rotation, targetRotation, 10f * Time.deltaTime);
|
|
elapsed += Time.deltaTime;
|
|
await Awaitable.NextFrameAsync();
|
|
}
|
|
}
|
|
|
|
private async Awaitable PlayNode(DialogNode node)
|
|
{
|
|
var speakerName = node.Speaker != null ? node.Speaker.Name : "(누구?)";
|
|
Debug.Log($"[{speakerName}] {node.TalkText}");
|
|
|
|
// 보이스 재생
|
|
if (node.Voice != null && node.Speaker != null)
|
|
{
|
|
var voiceObj = CharacterVoiceObject.Find(node.Speaker);
|
|
if (voiceObj != null && node.Voice.Clip != null)
|
|
voiceObj.Play(node.Voice.Clip);
|
|
}
|
|
|
|
// 플레이어 향해 회전
|
|
if (node.LookAtPlayer && node.Speaker != null)
|
|
{
|
|
var voiceObj = CharacterVoiceObject.Find(node.Speaker);
|
|
if (voiceObj != null)
|
|
{
|
|
_originalRotations.TryAdd(voiceObj.transform, voiceObj.transform.rotation);
|
|
_ = RotateTowardPlayer(voiceObj.transform);
|
|
}
|
|
}
|
|
|
|
if (node.Gesture != null)
|
|
_animator.CrossFade(node.Gesture.StateName, node.Gesture.CrossFadeDuration, node.Gesture.AnimationLayer);
|
|
if (node.Expression != null)
|
|
_animator.CrossFade(node.Expression.StateName, node.Expression.CrossFadeDuration, node.Expression.AnimationLayer);
|
|
|
|
// 대기 시간 결정
|
|
float wait = 0f;
|
|
if (node.Voice != null && node.Voice.Clip != null)
|
|
wait = node.Voice.Clip.length;
|
|
else
|
|
wait = node.LineDuration;
|
|
|
|
if (wait > 0f)
|
|
await Awaitable.WaitForSecondsAsync(wait);
|
|
else
|
|
await WaitForAdvanceInput(); // 수동 진행
|
|
}
|
|
|
|
private async Awaitable<int> WaitForChoice(DialogNode node)
|
|
{
|
|
if (ChoiceHud.Instance == null)
|
|
{
|
|
Debug.LogWarning("[DialogPlayer] ChoiceHud 없음 — 0번 자동 선택");
|
|
return 0;
|
|
}
|
|
return await ChoiceHud.Instance.Show(node.Speaker, node.ChoiceQuestion ,node.Choices);
|
|
}
|
|
|
|
private async Awaitable WaitForAdvanceInput()
|
|
{
|
|
// TODO: VR 컨트롤러 버튼 입력 대기. 일단은 1초 대기
|
|
await Awaitable.WaitForSecondsAsync(1f);
|
|
}
|
|
}
|