3D 잔재 제거

This commit is contained in:
2026-07-24 21:11:32 +09:00
parent 0b02bacb3d
commit 53bad30836
55 changed files with 4269 additions and 292 deletions

View File

@@ -24,7 +24,7 @@ public class DialogPlayer : MonoBehaviour
// ── 전역 대화 진행 신호 (대화 중 월드 상호작용 차단용) ────────
// 선택 메뉴 단계부터 대사 종료까지, 어느 NPC든 대화가 진행 중이면 true.
// DialogInteractionBlocker가 구독해서 인터랙터를 잠근다.
// 이동 버튼·클릭 인터랙션 등이 AnyActiveChanged를 구독해 대화 중 입력을 잠그면 된다.
public static bool IsAnyActive => _activeCount > 0;
public static event Action<bool> AnyActiveChanged;
private static int _activeCount;
@@ -103,7 +103,7 @@ public async Awaitable Play()
}
}
// 조건/선택 메뉴를 건너뛰고 특정 대화 그룹을 강제로 시작한다 (EventZoneTrigger 등 UnityEvent 연결용).
// 조건/선택 메뉴를 건너뛰고 특정 대화 그룹을 강제로 시작한다 (버튼·컷신 등 UnityEvent 연결용).
// DB에 등록된 비트면 그 설정(진행도 보상 등)을 그대로 쓰고, 없으면 임시 비트로 재생한다.
// 완료 여부는 검사하지 않으므로(강제) 1회성이 필요하면 호출하는 쪽(존의 Trigger Once)에서 보장할 것.
public void PlayGroup(DialogGroup group)
@@ -204,26 +204,24 @@ private async Awaitable PlayBeat(StoryBeat beat)
}
routingHops = 0; // 실제 대사 노드에 도달 — 카운터 리셋
// 이 노드가 히든 분기를 가지면, 노드가 재생되는 동안 제스처 감시를 무장한다.
// 이 노드가 히든 분기를 가지면, 노드가 재생되는 동안 발동 감시를 무장한다.
// (무장 안 된 노드는 아래 대기/선택이 기존과 완전히 동일하게 동작)
bool armed = node.HiddenBranch != null;
if (armed) HiddenBranchResolver.Arm(node.HiddenGestureKey);
bool armed = node.HiddenBranches != null && node.HiddenBranches.Count > 0;
if (armed) HiddenBranchResolver.Arm(node.HiddenBranches);
try
{
bool diverted = await PlayNode(node); // 대사 표시 + 대기(무장 시 제스처 감시 포함)
bool diverted = await PlayNode(node); // 대사 표시 + 대기(무장 시 발동 감시 포함)
if (diverted)
{
RecordHiddenChoice(node);
node = node.HiddenBranch; // 대사 도중 제스처 발동 → 몰래 분기
node = TakeHiddenBranch(node); // 대사 도중 발동 → 몰래 분기
}
else if (node.Choices != null && node.Choices.Count > 0)
{
int picked = await WaitForChoice(node); // 메뉴(무장 시 제스처 감시 포함)
int picked = await WaitForChoice(node); // 메뉴(무장 시 발동 감시 포함)
if (picked == DivertIndex)
{
RecordHiddenChoice(node);
node = node.HiddenBranch; // 메뉴 도중 제스처 발동 → 메뉴엔 없던 히든 분기
node = TakeHiddenBranch(node); // 메뉴 도중 발동 → 메뉴엔 없던 히든 분기
}
else
{
@@ -341,7 +339,7 @@ private async Awaitable<bool> PlayNode(DialogNode node)
}
// 진행은 항상 플레이어 입력 대기.
// 히든 분기가 무장된 노드는 대기 중 제스처를 함께 감시(레이스)한다.
// 히든 분기가 무장된 노드는 대기 중 히든 발동을 함께 감시(레이스)한다.
if (HiddenBranchResolver.Armed)
return await WaitAdvanceOrDivert();
@@ -393,12 +391,26 @@ private void RecordChoice(DialogNode node, int index)
StoryManager.Instance.RecordChoice(code);
}
// 히든 분기 기록: 노드의 HiddenCode를 선택지 Code와 동일하게 StoryState에 영구 기록
private void RecordHiddenChoice(DialogNode node)
// 발동한 히든 분기로 이동한다. 어느 분기가 발동했는지는 Resolver가 키를 보고 이미 골라 뒀다.
// (Disarm보다 먼저 불려야 하므로 반드시 무장 구간 안에서 호출할 것)
private DialogNode TakeHiddenBranch(DialogNode node)
{
string code = DialogVariables.Format(node.HiddenCode); // {token} 치환
int index = HiddenBranchResolver.FiredIndex;
if (node.HiddenBranches == null || index < 0 || index >= node.HiddenBranches.Count)
{
// 정상 흐름에선 올 수 없다(발동 = 매칭 성공). 와도 대화가 끊기지 않게 원래 흐름으로.
Debug.LogWarning($"[DialogPlayer] 히든 분기 인덱스가 범위를 벗어남: {index}");
return node.Next;
}
var branch = node.HiddenBranches[index];
// 선택지 Code와 동일하게 StoryState에 영구 기록
string code = DialogVariables.Format(branch.Code); // {token} 치환
if (!string.IsNullOrEmpty(code))
StoryManager.Instance.RecordChoice(code);
return branch.Destination;
}
// 히든 분기 신호값 — 선택지 인덱스(0..n-1)와 절대 겹치지 않는 값.
@@ -416,11 +428,11 @@ private async Awaitable<int> WaitForChoice(DialogNode node)
if (!HiddenBranchResolver.Armed)
return await ChoiceHud.Instance.Show(node.ChoiceQuestion, node.Choices);
// 무장된 노드: 메뉴가 떠 있는 동안 제스처가 들어오면 메뉴를 취소하고 히든으로 분기.
// 무장된 노드: 메뉴가 떠 있는 동안 발동이 들어오면 메뉴를 취소하고 히든으로 분기.
if (HiddenBranchResolver.Fired)
return DivertIndex; // 메뉴가 뜨기 전에 이미 발동한 경우
void OnFired() => ChoiceHud.Instance.CancelPending(); // 제스처 → 메뉴 즉시 취소
void OnFired() => ChoiceHud.Instance.CancelPending(); // 발동 → 메뉴 즉시 취소
HiddenBranchResolver.FiredEvent += OnFired;
try
{
@@ -428,7 +440,7 @@ private async Awaitable<int> WaitForChoice(DialogNode node)
}
catch (OperationCanceledException)
{
// 우리가 제스처로 취소한 것이면 히든 분기, 아니면(씬 전환 등) 위로 전파
// 우리가 히든 발동으로 취소한 것이면 히든 분기, 아니면(씬 전환 등) 위로 전파
if (HiddenBranchResolver.Fired) return DivertIndex;
throw;
}
@@ -438,8 +450,8 @@ private async Awaitable<int> WaitForChoice(DialogNode node)
}
}
// 노드 대기 + 히든 제스처 감시(레이스). 무장된 노드에서만 호출된다.
// 반환: true = 대기 도중 제스처 발동(→히든 분기), false = 평범하게 진행(입력)
// 노드 대기 + 히든 발동 감시(레이스). 무장된 노드에서만 호출된다.
// 반환: true = 대기 도중 히든 발동(→히든 분기), false = 평범하게 진행(입력)
private async Awaitable<bool> WaitAdvanceOrDivert()
{
var im = InputManager.Instance;
@@ -454,7 +466,7 @@ private async Awaitable<bool> WaitAdvanceOrDivert()
{
while (true)
{
if (HiddenBranchResolver.Fired) return true; // 제스처 발동 → 히든 분기
if (HiddenBranchResolver.Fired) return true; // 히든 발동 → 히든 분기
if (advance) return false; // 진행 입력 → 평범하게 진행
if (timeLeft >= 0f)
{
@@ -503,15 +515,16 @@ private async Awaitable WaitForAdvanceInput()
}
}
//테스트용
//테스트용 — 캐릭터를 클릭하면 대화 시작 (Collider2D가 있어야 잡힌다)
private void Update()
{
if (Mouse.current == null) return;
if (!Mouse.current.leftButton.wasPressedThisFrame) return;
if (Camera.main == null) return;
var ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
if (Physics.Raycast(ray, out var hit) && hit.transform.IsChildOf(transform))
var world = Camera.main.ScreenToWorldPoint(Mouse.current.position.ReadValue());
var hit = Physics2D.OverlapPoint(world);
if (hit != null && hit.transform.IsChildOf(transform))
{
Debug.Log("캐릭터 클릭");
_ = Play(); // 테스트용 fire-and-forget (Update는 await 불가)