게임클리어 이벤트

This commit is contained in:
2026-06-23 17:03:38 +09:00
parent fecc7556d6
commit b85855d4d6
25 changed files with 1317 additions and 28 deletions

View File

@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using UnityEngine.Events;
using UnityEngine.InputSystem;
using UnityEngine;
@@ -12,6 +14,18 @@ public struct RegionGroup
public DialogGroup Group;
}
// 마지막 선택지 코드(LastChoiceCode)를 인자로 넘기는 UnityEvent (인스펙터 노출용 구체 타입)
[System.Serializable]
public class ChoiceCodeEvent : UnityEvent<string> { }
// 노드의 EventKey ↔ 그 노드 재생 시 호출할 이벤트. 인자로 LastChoiceCode가 전달됨.
[System.Serializable]
public struct NodeEvent
{
public string Key;
public ChoiceCodeEvent Event;
}
[Tooltip("영역 이름 ↔ 그 영역에서 재생할 DialogGroup")]
[SerializeField] private List<RegionGroup> _regionGroups;
@@ -23,6 +37,10 @@ public struct RegionGroup
[SerializeField] private float _hudForwardOffset = 0.5f; // 화자→플레이어 방향으로 띄울 거리
[SerializeField] private float _hudLateralOffset = 0f; // 좌우 오프셋 (+ 플레이어 시점 오른쪽)
[Header("Dialog Events")]
[Tooltip("노드의 Event Key와 같은 Key가 그 노드 재생 시 호출됨")]
[SerializeField] private List<NodeEvent> _nodeEvents = new();
private Dictionary<string, DialogGroup> _regionMap;
private Animator _animator;
private int _initialGestureHash;
@@ -31,6 +49,11 @@ public struct RegionGroup
private readonly Dictionary<Transform, Quaternion> _originalRotations = new();
public bool IsPlaying { get; private set; }
// 마지막으로 고른 선택지 (인덱스/코드). DialogVariables에도 lastChoiceIndex / lastChoiceCode 로 저장됨
public int LastChoiceIndex { get; private set; } = -1;
public string LastChoiceCode { get; private set; }
public event Action<int, string> OnChoiceSelected; // (index, code)
private void Awake()
{
_regionMap = new Dictionary<string, DialogGroup>();
@@ -90,6 +113,7 @@ public async Awaitable Play(string region)
if (node.Choices != null && node.Choices.Count > 0)
{
int picked = await WaitForChoice(node);
RecordChoice(node, picked);
node = node.Choices[picked].DestinationNode;
}
else
@@ -169,6 +193,8 @@ private async Awaitable PlayNode(DialogNode node)
if (DialogHud.Instance != null)
DialogHud.Instance.Show(node.Speaker, node.TalkText, _hudChestHeight, _hudForwardOffset, _hudLateralOffset);
RaiseNodeEvent(node.EventKey); // EventKey 있으면 매칭 이벤트 호출
// 보이스 재생
if (node.Voice != null && node.Speaker != null)
{
@@ -193,17 +219,47 @@ private async Awaitable PlayNode(DialogNode node)
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;
// 진행 방식 결정
if (node.WaitForInput)
{
await WaitForAdvanceInput(); // B버튼 입력이 있어야만 다음으로
}
else
wait = node.LineDuration;
{
float wait = (node.Voice != null && node.Voice.Clip != null)
? node.Voice.Clip.length
: node.LineDuration;
if (wait > 0f)
await Awaitable.WaitForSecondsAsync(wait);
else
await WaitForAdvanceInput(); // 수동 진행
if (wait > 0f)
await Awaitable.WaitForSecondsAsync(wait);
else
await WaitForAdvanceInput(); // 지정 시간이 없으면 입력으로 진행
}
}
// 노드의 EventKey와 같은 Key를 가진 이벤트들을 호출
private void RaiseNodeEvent(string key)
{
if (string.IsNullOrEmpty(key)) return;
foreach (var e in _nodeEvents)
if (e.Key == key) e.Event?.Invoke(LastChoiceCode); // 마지막 선택지 코드를 인자로 전달
}
// 선택 결과 기록: 인덱스/코드를 프로퍼티 + DialogVariables에 저장하고 이벤트 발행
private void RecordChoice(DialogNode node, int index)
{
string code = (node.Choices != null && index >= 0 && index < node.Choices.Count)
? node.Choices[index].Code : null;
code = DialogVariables.Format(code); // {token} 치환 → 동적으로 생성된 코드 반영
LastChoiceIndex = index;
LastChoiceCode = code;
DialogVariables.Set("lastChoiceIndex", index.ToString());
if (!string.IsNullOrEmpty(code))
DialogVariables.Set("lastChoiceCode", code);
OnChoiceSelected?.Invoke(index, code);
}
private async Awaitable<int> WaitForChoice(DialogNode node)
@@ -219,10 +275,33 @@ private async Awaitable<int> WaitForChoice(DialogNode node)
}
// 대화 진행 입력(OnDialogNext = VR B버튼) 한 번을 대기
private async Awaitable WaitForAdvanceInput()
{
// TODO: VR 컨트롤러 버튼 입력 대기. 일단은 1초 대기
await Awaitable.WaitForSecondsAsync(1f);
var im = InputManager.Instance;
if (im == null)
{
// 입력 매니저 없으면 안전하게 잠깐 대기 후 진행
await Awaitable.WaitForSecondsAsync(1f);
return;
}
bool pressed = false;
void Handler() => pressed = true;
im.OnDialogNext_Event += Handler;
try
{
while (!pressed)
await Awaitable.NextFrameAsync(destroyCancellationToken);
}
catch (OperationCanceledException)
{
// 대기 중 오브젝트 파괴 시 조용히 종료
}
finally
{
im.OnDialogNext_Event -= Handler;
}
}
//테스트용