오늘 한 일
목표
NPC 근처에서 F 키를 누르면 대사 창 표시
F 키로 다음 대사 진행
더 이상 대사가 없으면 대사창 자동 종료
Player
└─ DialogueTrigger (NPC와 상호작용 감지)
Canvas
└─ DialogueCanvas
└─ DialoguePanel (하단 UI)
└─ TMP_Text (대사 텍스트)
핵심 클래스
DialogueManager : 대사 전체 제어
DialogueTrigger : NPC 근처에서 F 키 입력 감지
DataManager : 대사 데이터 관리 (ID 기반)
동작 흐름
플레이어가 NPC 범위 진입
F 키 입력
대화 중이 아니면 → 대사 시작
대화 중이면 → 다음 대사 출력
더 이상 출력할 대사가 없으면
대사창 비활성화
대화 종료 처리
using UnityEngine;
using TMPro;
using System;
using System.Collections.Generic;
public class DialogueManager : MonoBehaviour
{
public static DialogueManager Instance { get; private set; }
[SerializeField] private GameObject dialogueCanvas;
[SerializeField] private TextMeshProUGUI dialogueText;
[SerializeField] private DataManager dataManager;
private Queue<string> dialogueQueue = new Queue<string>();
private bool isTalking = false;
private void Awake()
{
if (Instance == null) Instance = this;
else Destroy(gameObject);
}
public void StartDialogue(string dialogueID)
{
dialogueQueue.Clear();
List<string> lines = dataManager.GetDialogue(dialogueID);
foreach (var line in lines)
dialogueQueue.Enqueue(line);
dialogueCanvas.SetActive(true);
isTalking = true;
ShowNextDialogue();
}
public void ShowNextDialogue()
{
if (dialogueQueue.Count == 0)
{
EndDialogue();
return;
}
dialogueText.text = dialogueQueue.Dequeue();
}
private void EndDialogue()
{
dialogueCanvas.SetActive(false);
isTalking = false;
}
public bool IsTalking()
{
return isTalking;
}
}
using UnityEngine;
public class DialogueTrigger : MonoBehaviour
{
public string dialogueID;
private bool isPlayerNear = false;
private void Update()
{
if (!isPlayerNear) return;
if (Input.GetKeyDown(KeyCode.F))
{
if (DialogueManager.Instance.IsTalking())
DialogueManager.Instance.ShowNextDialogue();
else
DialogueManager.Instance.StartDialogue(dialogueID);
}
}
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
isPlayerNear = true;
}
private void OnTriggerExit(Collider other)
{
if (other.CompareTag("Player"))
isPlayerNear = false;
}
}
❌ 코드에 직접 쓰는 방식
대사 수정할 때마다 코드 수정
NPC 많아지면 유지보수 지옥
기획자 / 디자이너가 수정 불가
✅ JSON 사용 장점
대사 데이터를 외부 파일로 분리
NPC별 / 상황별 대사 관리 쉬움
나중에 다국어(Localization) 확장 가능
JSON 파일 위치
Assets
└─ Resources
└─ Dialogue
└─ dialogue.json
⚠️ Resources 폴더 안에 있어야 Resources.Load() 사용 가능
dialogue.json 구조 예시
{
"dialogues": [
{
"id": "npc_intro",
"lines": [
"깨어났군요...",
"이곳은 위험합니다.",
"당장 숲을 벗어나야 합니다."
]
},
{
"id": "npc_warning",
"lines": [
"아직 준비가 안 된 것 같군요.",
"무기를 먼저 챙기세요."
]
}
]
}
| 키 | 설명 |
|---|---|
id | 대사 묶음 식별자 |
lines | 실제 출력될 대사 배열 |
JSON 파싱용 데이터 클래스
[System.Serializable]
public class DialogueData
{
public string id;
public List<string> lines;
}
[System.Serializable]
public class DialogueDataList
{
public List<DialogueData> dialogues;
}
✔ Unity의 JsonUtility는
→ 배열 최상위 파싱이 불가
→ 반드시 래퍼 클래스(DialogueDataList) 필요
DataManager (JSON 로드 & 관리)
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
public class DataManager : MonoBehaviour
{
private Dictionary<string, List<string>> dialogueDict;
private void Awake()
{
LoadDialogueData();
}
private void LoadDialogueData()
{
TextAsset jsonFile = Resources.Load<TextAsset>("Dialogue/dialogue");
DialogueDataList dataList =
JsonUtility.FromJson<DialogueDataList>(jsonFile.text);
dialogueDict = new Dictionary<string, List<string>>();
foreach (var data in dataList.dialogues)
{
dialogueDict.Add(data.id, data.lines);
}
Debug.Log($"[Dialogue] Loaded Count : {dialogueDict.Count}");
}
public List<string> GetDialogue(string id)
{
if (dialogueDict.ContainsKey(id))
return dialogueDict[id];
Debug.LogWarning($"Dialogue ID not found : {id}");
return new List<string>();
}
}
DialogueTrigger에서 ID 사용
public class DialogueTrigger : MonoBehaviour
{
public string dialogueID = "npc_intro";
}
👉 NPC마다 다른 ID만 지정하면 끝
이 구조의 강점
✔ NPC 수가 늘어나도 코드 수정 없음
✔ 대사 추가 = JSON에 한 줄 추가
✔ 퀘스트 / 선택지 / 컷신 연동 쉬움
✔ 나중에 엑셀 → JSON 변환도 가능
speaker 필드 추가 → NPC 이름 표시
voiceClip 필드 → 음성 대사
condition → 퀘스트 진행도 분기
locale → 다국어 대응\
대사 데이터를 JSON으로 분리함으로써
유지보수성과 확장성을 모두 확보한 대화 시스템을 구현했다.