오늘 한 일
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;
}
{
"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
🎯 역할: “평상시 플레이 카메라”
마우스 입력
충돌 처리
회전 / 줌 / 거리 계산
-> 대화 중에는 반드시 꺼야 함