코딩의 신 56

김동관·2025년 12월 19일

오늘 한 일

  • [ ]NPC 대화 기능
  • [ ]NPC 대화 시에 카메라 NPC에게로 확대 및 회전
  • [ ]NPC 대화 시에 NPC가 Player를 바라보게
  • [ ]NPC 대화 Panel UI 수정

Unity 대화 시스템 + 카메라 연출 + 입력 제어 정리

NPC와 대화하는 시스템을 만들면서
UI / 데이터 / 입력 / 카메라 / 플레이어 제어를 단계적으로 분리해 구현

전체 구조 한눈에 보기

오늘 만든 시스템은 크게 5개 역할로 나뉜다.

PlayerInteraction
        ↓
NPCInteractable
        ↓
DialogueManager
        ↓
DialogueTableLoader (JSON)
        ↓
DialogueCameraController

그리고 대화 중에는

플레이어 이동 ❌

마우스 카메라 회전 ❌

NPC & 카메라 연출 ⭕

이 흐름을 목표로 설계했다.

PlayerInteraction.cs

역할: “플레이어 입력의 시작점”

담당 역할

화면 중앙에서 Raycast

상호작용 대상 탐색

F 키 입력 처리

대화 중이면 Next() 호출

if (Input.GetKeyDown(KeyCode.F))
{
    if (DialogueManager.Instance.IsDialogueActive)
    {
        DialogueManager.Instance.Next();
        return;
    }
    TryInteract();
}

핵심 포인트

F 키 하나로 역할이 바뀜

평소 → NPC 상호작용

대화 중 → 다음 대사

-> 입력 분기 처리를 PlayerInteraction에서 담당해서
다른 스크립트들이 깔끔해짐

NPCInteractable.cs

역할: “NPC가 대화 시작을 요청”

담당 역할

IInteractable 구현

대화 시작 ID 보유

DialogueManager 호출

DialogueManager.Instance.StartDialogue(
    startDialogueID,
    OnDialogueEnd,
    transform
);

핵심 포인트

NPC는 대화 시스템을 직접 관리하지 않음

“나는 이 대사를 시작한다”만 전달

카메라 / UI / 입력 제어는 DialogueManager 책임

-> NPC는 최대한 멍청하게 설계 (중요)

DialogueManager.cs

역할: “대화 시스템의 컨트롤 타워”

오늘 작업의 핵심 💡

담당 역할 정리

대화 상태 관리 (IsDialogueActive)

JSON 대사 로드

대사 출력

다음 대사 처리

대화 종료 처리

입력 잠금 / 해제

카메라 연출 호출

대화 시작 흐름

public void StartDialogue(string startID, Action onEnd, Transform npcTransform)
{
    IsDialogueActive = true;

    // 입력 잠금
    playerMovement.enabled = false;
    thirdPersonCamera.enabled = false;

    dialogueCanvas.SetActive(true);

    dialogueCamera.StartDialogueCamera(npcTransform);

    ShowCurrent();
}

✔ 플레이어 이동 멈춤
✔ 마우스 카메라 멈춤
✔ 대화 UI 활성화
✔ NPC 바라보는 카메라 연출 시작

다음 대사 처리

public void Next()
{
    var data = dataManager.DialogueTableLoader.GetDialogue(currentDialogueID);

    if (data == null || string.IsNullOrEmpty(data.NextID))
    {
        EndDialogue();
        return;
    }

    currentDialogueID = data.NextID;
    ShowCurrent();
}

✔ 다음 ID가 있으면 → 다음 대사
✔ 없으면 → 대화 종료

-> “대사가 끝나면 자동 종료” 구조

대화 종료

private void EndDialogue()
{
    IsDialogueActive = false;

    dialogueCanvas.SetActive(false);

    // 입력 복구
    playerMovement.enabled = true;
    thirdPersonCamera.enabled = true;

    dialogueCamera.EndDialogueCamera();
}

✔ UI 닫기
✔ 플레이어 이동 복구
✔ 카메라 원래대로 복귀

DialogueTableLoader.cs

역할: “JSON 대사 테이블 로더”

담당 역할

Resources/JSON/Dialogue_Data_Table.json 로드

Dictionary 형태로 관리

ID 기반 조회

public DialogueRow GetDialogue(string id)
{
    dialogueDict.TryGetValue(id, out var row);
    return row;
}

JSON 구조 예시

{
  "Dialogues": [
    {
      "DialogueID": "DLG_1001",
      "Text": "정신이 드는가, 외지인이여?",
      "NextID": "DLG_1002"
    }
  ]
}

데이터와 로직 완전 분리

DialogueCameraController.cs

역할: “대화 전용 카메라 연출”

이 스크립트는 ThirdPersonCamera와 완전히 분리됨.

대화 중 카메라 연출

Vector3 targetPos = currentNPCTarget.position;
targetPos.y = mainCamera.transform.position.y;

Vector3 dir = (targetPos - mainCamera.transform.position).normalized;
Quaternion lookRot = Quaternion.LookRotation(dir);

✔ Y값 고정 → 위아래 떨림 제거
✔ NPC를 자연스럽게 바라봄
✔ FOV 서서히 줌인

오늘 핵심 설계 포인트 정리
✔ 잘한 점

입력 / UI / 카메라 / 데이터 완전 분리

F 키 하나로 상호작용 + 대화 진행

대화 종료 조건 명확

카메라 떨림 원인 정확히 분리 해결

✔ 확장 쉬운 구조

선택지 대화

NPC 얼굴 회전

음성 출력

컷신 대화

트러블 슈팅

카메라가 NPC로 회전시에 떨리는 현상

왜 카메라가 떨렸나?

ThirdPersonCamera LateUpdate와

DialogueCamera Coroutine이 동시에 회전 제어

✔ 해결:

대화 시작 시 ThirdPersonCamera enabled = false

종료 후 다시 enabled = true

ThirdPersonCamera.cs
🎯 역할: “평상시 플레이 카메라”

마우스 입력

충돌 처리

회전 / 줌 / 거리 계산

-> 대화 중에는 반드시 꺼야 함

profile
아이디어 뱅크

0개의 댓글