From ad6ccadc8e429c9b443a5aef925b31cc753dfc4c Mon Sep 17 00:00:00 2001 From: nakjun Date: Fri, 17 Jul 2026 11:57:19 +0900 Subject: [PATCH] =?UTF-8?q?=EB=8C=80=ED=99=94=EC=A4=91=EC=97=90=20?= =?UTF-8?q?=EC=95=84=EC=9B=83=EB=9D=BC=EC=9D=B8=20=EC=95=88=EB=9C=A8?= =?UTF-8?q?=EA=B2=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Communication/Dialog/DialogPlayer.cs | 31 ++++++++++ .../Interact/DialogInteractionBlocker.cs | 56 +++++++++++++++++++ .../Interact/DialogInteractionBlocker.cs.meta | 2 + .../XR/Settings/OpenXR Package Settings.asset | 4 +- 4 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 Assets/02_Scripts/Interact/DialogInteractionBlocker.cs create mode 100644 Assets/02_Scripts/Interact/DialogInteractionBlocker.cs.meta diff --git a/Assets/02_Scripts/Communication/Dialog/DialogPlayer.cs b/Assets/02_Scripts/Communication/Dialog/DialogPlayer.cs index 2c6b8cef..7ed197b9 100644 --- a/Assets/02_Scripts/Communication/Dialog/DialogPlayer.cs +++ b/Assets/02_Scripts/Communication/Dialog/DialogPlayer.cs @@ -61,6 +61,33 @@ public struct NodeEvent // 선택 메뉴만 떠 있는 단계는 여기 안 잡힌다 (그 경우는 새 NPC가 메뉴를 취소하고 시작). private static DialogPlayer _entryInProgress; + // ── 전역 대화 진행 신호 (대화 중 월드 상호작용 차단용) ──────── + // 선택 메뉴 단계부터 대사 종료까지, 어느 NPC든 대화가 진행 중이면 true. + // DialogInteractionBlocker가 구독해서 인터랙터를 잠근다. + public static bool IsAnyActive => _activeCount > 0; + public static event Action AnyActiveChanged; + private static int _activeCount; + + private static void PushActive() + { + if (++_activeCount == 1) AnyActiveChanged?.Invoke(true); + } + + private static void PopActive() + { + if (_activeCount <= 0) return; + if (--_activeCount == 0) AnyActiveChanged?.Invoke(false); + } + + // Enter Play Mode에서 도메인 리로드를 꺼도 이전 상태가 안 남게 + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + private static void ResetStatics() + { + _entryInProgress = null; + _activeCount = 0; + AnyActiveChanged = null; + } + private void Awake() { _voice = GetComponent(); @@ -91,6 +118,7 @@ public async Awaitable Play() // 선택 메뉴가 떠 있는 동안에도 재진입을 막아야 하므로 여기서 잠근다. IsPlaying = true; + PushActive(); try { var playable = FindPlayableIndices(); @@ -128,6 +156,7 @@ public async Awaitable Play() RestoreRotations(); if (_entryInProgress == this) _entryInProgress = null; IsPlaying = false; + PopActive(); } } @@ -150,6 +179,7 @@ private async Awaitable PlayGroupForced(DialogGroup group) ChoiceHud.Instance.CancelPending(); IsPlaying = true; + PushActive(); try { // 등록된 항목이 있으면 Repeatable/ProgressOnComplete 설정을 그대로 사용 @@ -178,6 +208,7 @@ private async Awaitable PlayGroupForced(DialogGroup group) RestoreRotations(); if (_entryInProgress == this) _entryInProgress = null; IsPlaying = false; + PopActive(); } } diff --git a/Assets/02_Scripts/Interact/DialogInteractionBlocker.cs b/Assets/02_Scripts/Interact/DialogInteractionBlocker.cs new file mode 100644 index 00000000..2d42b7e9 --- /dev/null +++ b/Assets/02_Scripts/Interact/DialogInteractionBlocker.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.XR.Interaction.Toolkit; +using UnityEngine.XR.Interaction.Toolkit.Interactors; + +// 대화 중(선택 메뉴 포함) 월드 상호작용을 차단한다 — UI 상호작용은 유지. +// XR Origin(리그 루트)에 붙이면 자식의 모든 인터랙터를 찾아서, 대화가 시작될 때 +// Interaction Layer를 None으로 바꿔 3D 인터랙터블과의 호버/선택/활성화를 막는다. +// 캔버스 UI(선택지 버튼 등)는 인터랙션 레이어를 거치지 않고 XRUIInputModule로 +// 처리되므로 영향받지 않는다. 대화가 끝나면 원래 레이어로 복원. +public class DialogInteractionBlocker : MonoBehaviour +{ + [Tooltip("차단에서 제외할 인터랙터 (예: 대화 중에도 텔레포트를 허용하고 싶을 때 텔레포트 레이 인터랙터)")] + [SerializeField] private List _exclude = new(); + + // 차단 직전의 원래 레이어 — 복원용. 비어있지 않으면 차단 중이라는 뜻. + private readonly Dictionary _saved = new(); + + private void OnEnable() + { + DialogPlayer.AnyActiveChanged += OnDialogActiveChanged; + if (DialogPlayer.IsAnyActive) Block(); // 이미 대화 중에 활성화된 경우 + } + + private void OnDisable() + { + DialogPlayer.AnyActiveChanged -= OnDialogActiveChanged; + Restore(); + } + + private void OnDialogActiveChanged(bool active) + { + if (active) Block(); + else Restore(); + } + + private void Block() + { + if (_saved.Count > 0) return; // 이미 차단 중 + + // 매번 새로 수집 — 손에 든 도구 등 런타임에 생긴 인터랙터도 포함되게 + foreach (var interactor in GetComponentsInChildren(true)) + { + if (_exclude.Contains(interactor)) continue; + _saved[interactor] = interactor.interactionLayers; + interactor.interactionLayers = 0; // None — 3D 인터랙터블 전부 차단 + } + } + + private void Restore() + { + foreach (var kvp in _saved) + if (kvp.Key != null) kvp.Key.interactionLayers = kvp.Value; + _saved.Clear(); + } +} diff --git a/Assets/02_Scripts/Interact/DialogInteractionBlocker.cs.meta b/Assets/02_Scripts/Interact/DialogInteractionBlocker.cs.meta new file mode 100644 index 00000000..788d343a --- /dev/null +++ b/Assets/02_Scripts/Interact/DialogInteractionBlocker.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 77d7c7e6b5a73944fbcf03ba2bab1244 \ No newline at end of file diff --git a/Assets/XR/Settings/OpenXR Package Settings.asset b/Assets/XR/Settings/OpenXR Package Settings.asset index 71e94f88..bdc4ba99 100644 --- a/Assets/XR/Settings/OpenXR Package Settings.asset +++ b/Assets/XR/Settings/OpenXR Package Settings.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3da69876a48f5494e31ebce344a659b3de51d4e0ed5a787f8a792eab87591f82 -size 96376 +oid sha256:d8fbd07d77937ca0cde6d9ee01174edff3275a8f6016a1b53fb71036048c900f +size 96377