diff --git a/Assets/01_Scenes/_TestScenes/Test1.unity b/Assets/01_Scenes/_TestScenes/Test1.unity index b53cfdb0..44bb86d2 100644 --- a/Assets/01_Scenes/_TestScenes/Test1.unity +++ b/Assets/01_Scenes/_TestScenes/Test1.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:50a42d497c35ce48cc3b9e98ebd2c9aa6245069b3847868c723ccf0caba1dfef -size 26491 +oid sha256:2271ea0fcc999aa8e78f142e4bac412b287acddac4aa168c424de0dff6732a04 +size 28082 diff --git a/Assets/02_Scripts/Communication/Dialog/DialogPlayer.cs b/Assets/02_Scripts/Communication/Dialog/DialogPlayer.cs index 86cd958a..7ca8f017 100644 --- a/Assets/02_Scripts/Communication/Dialog/DialogPlayer.cs +++ b/Assets/02_Scripts/Communication/Dialog/DialogPlayer.cs @@ -20,6 +20,9 @@ public struct DialogEntry [Tooltip("이 대화를 처음 완료하면 메인 진행도 +N (필수 대화가 아니면 0)")] [Min(0)] public int ProgressOnComplete; + + [Tooltip("대화 선택 메뉴에 표시할 이름 (비우면 그룹 이름 사용)")] + public string MenuLabel; } // 노드의 EventKey ↔ 그 노드 재생 시 호출할 이벤트. @@ -30,9 +33,12 @@ public struct NodeEvent public UnityEvent Event; } - [Tooltip("이 NPC의 대화 후보들. 위에서부터 조건을 검사해 첫 번째로 만족하는 대화를 재생")] + [Tooltip("이 NPC의 대화 후보들. 조건을 만족하는 대화가 여럿이면 플레이어가 선택한다")] [SerializeField] private List _dialogs = new(); + [Tooltip("대화가 여러 개일 때 선택 메뉴 상단에 표시할 안내 문구")] + [SerializeField] private string _dialogSelectPrompt = "무슨 이야기를 나눌까?"; + [Header("Dialog HUD Placement")] // 씬에서 캐릭터 위치/주변(벽 등)에 맞춰 조절 [SerializeField] private float _hudChestHeight = 1.2f; // 화자 발 기준 가슴 높이 [SerializeField] private float _hudForwardOffset = 0.5f; // 화자→플레이어 방향으로 띄울 거리 @@ -70,32 +76,79 @@ public async Awaitable Play() { if (IsPlaying) return; - int index = FindPlayableIndex(); - if (index < 0) + // 선택 메뉴가 떠 있는 동안에도 재진입을 막아야 하므로 여기서 잠근다. + IsPlaying = true; + try { - Debug.Log($"[DialogPlayer] 조건에 맞는 대화가 없음: {name}"); - return; + var playable = FindPlayableIndices(); + if (playable.Count == 0) + { + Debug.Log($"[DialogPlayer] 조건에 맞는 대화가 없음: {name}"); + return; + } + + int index = playable.Count == 1 ? playable[0] : await SelectDialog(playable); + if (index < 0) return; // 선택 대기 중 취소됨 (씬 전환 등) + + await PlayEntry(_dialogs[index]); + } + finally + { + IsPlaying = false; } - await PlayEntry(_dialogs[index]); } - // 리스트 순서 = 우선순위. 조건을 만족하고 (반복 가능하거나 아직 안 한) 첫 대화. - private int FindPlayableIndex() + // 조건을 만족하고 (반복 가능하거나 아직 안 한) 대화들의 인덱스. 리스트 순서 유지. + private List FindPlayableIndices() { + var result = new List(); for (int i = 0; i < _dialogs.Count; i++) { var entry = _dialogs[i]; if (entry.Group == null) continue; if (!entry.Repeatable && StoryManager.Instance.IsDialogCompleted(entry.Group.name)) continue; if (entry.Condition != null && !entry.Condition.IsMet(_voice.Character)) continue; - return i; + result.Add(i); + } + return result; + } + + // 재생 가능한 대화가 여럿일 때 ChoiceHud로 플레이어에게 고르게 한다. 반환값은 _dialogs 인덱스. + private async Awaitable SelectDialog(List playable) + { + if (ChoiceHud.Instance == null) + return playable[0]; // 선택 UI가 없으면 기존처럼 최상단 우선 + + // ChoiceHud는 DialogHud를 따라 배치되므로, 먼저 화자 옆에 HUD를 띄운다. + if (DialogHud.Instance != null) + DialogHud.Instance.Show(_voice.Character, _dialogSelectPrompt, _hudChestHeight, _hudForwardOffset, _hudLateralOffset); + + var options = new List(playable.Count); + foreach (int i in playable) + { + var entry = _dialogs[i]; + string label = !string.IsNullOrWhiteSpace(entry.MenuLabel) + ? entry.MenuLabel + : entry.Group.DialogGroupName; + options.Add(new DialogChoice { ChoiceText = label }); // Code 없음 → 선택 이력에 기록 안 됨 + } + + try + { + int picked = await ChoiceHud.Instance.Show(null, options); + return playable[picked]; + } + catch (OperationCanceledException) + { + // 대기 중 ChoiceHud가 비활성화됨(씬 전환 등) — 재생하지 않음 + if (DialogHud.Instance != null) + DialogHud.Instance.Hide(); + return -1; } - return -1; } private async Awaitable PlayEntry(DialogEntry entry) { - IsPlaying = true; try { var node = entry.Group.StartNode; @@ -127,7 +180,6 @@ private async Awaitable PlayEntry(DialogEntry entry) } finally { - IsPlaying = false; if (DialogHud.Instance != null) DialogHud.Instance.Hide(); if (SoundManager.Instance != null) diff --git a/Assets/02_Scripts/UI/Communication/ChoiceHud.cs b/Assets/02_Scripts/UI/Communication/ChoiceHud.cs index 9351d05d..c9f2eb3d 100644 --- a/Assets/02_Scripts/UI/Communication/ChoiceHud.cs +++ b/Assets/02_Scripts/UI/Communication/ChoiceHud.cs @@ -14,6 +14,7 @@ public class ChoiceHud : MonoBehaviour [SerializeField] private Transform _rowContainer; [SerializeField] private DialogChoiceRow _rowPrefab; [SerializeField] private TMP_Text ChoiceQuestion; + [SerializeField] private RectTransform _ScrollRect; private readonly List _rows = new(); private AwaitableCompletionSource _completion; diff --git a/Assets/09_UI/Dialog/ChoiceRow.prefab b/Assets/09_UI/Dialog/ChoiceRow.prefab new file mode 100644 index 00000000..56f69f05 --- /dev/null +++ b/Assets/09_UI/Dialog/ChoiceRow.prefab @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:084bf06a6782fc04febdca3f1e3d2cb4247e37b9ef1cafb6aecb84386aaef556 +size 8327 diff --git a/Assets/09_UI/Dialog/ChoiceRow.prefab.meta b/Assets/09_UI/Dialog/ChoiceRow.prefab.meta new file mode 100644 index 00000000..474a8a1c --- /dev/null +++ b/Assets/09_UI/Dialog/ChoiceRow.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2beaedbeb265d9843b112ed8ff68287d +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/09_UI/Dialog/WorldDialogCanvas.prefab b/Assets/09_UI/Dialog/WorldDialogCanvas.prefab index 5d5b2d2e..4acbf2a1 100644 --- a/Assets/09_UI/Dialog/WorldDialogCanvas.prefab +++ b/Assets/09_UI/Dialog/WorldDialogCanvas.prefab @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58a081fbe7bc3261bc8b43ef1087a6622a7121d9be2913a9497a4972f1283c27 -size 18354 +oid sha256:1f07a05e0ac03aa8a09d98efa77b51781add0987723feabf669e965141fd077b +size 44994 diff --git a/Assets/XR/Settings/OpenXRPackageSettings.asset b/Assets/XR/Settings/OpenXRPackageSettings.asset index a7d939c2..f656a751 100644 --- a/Assets/XR/Settings/OpenXRPackageSettings.asset +++ b/Assets/XR/Settings/OpenXRPackageSettings.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ca1a77e36c8a3ae5c74364062a24544e3bb6f81850cf11bee894d10864c3255 -size 115406 +oid sha256:017ec09e4146e8fce7352d089f50e563651728620d083d0de17a00132cd85e18 +size 117853