코딩의 신 55

김동관·2025년 12월 18일

오늘 한 일

Unity 대사 시스템 추가 정리 (F키 상호작용 기반)

목표

NPC 근처에서 F 키를 누르면 대사 창 표시

F 키로 다음 대사 진행

더 이상 대사가 없으면 대사창 자동 종료

Player
 └─ DialogueTrigger (NPC와 상호작용 감지)

Canvas
 └─ DialogueCanvas
     └─ DialoguePanel (하단 UI)
         └─ TMP_Text (대사 텍스트)

핵심 클래스

DialogueManager : 대사 전체 제어

DialogueTrigger : NPC 근처에서 F 키 입력 감지

DataManager : 대사 데이터 관리 (ID 기반)

동작 흐름

플레이어가 NPC 범위 진입

F 키 입력

대화 중이 아니면 → 대사 시작

대화 중이면 → 다음 대사 출력

더 이상 출력할 대사가 없으면

대사창 비활성화

대화 종료 처리

DialogueManager 핵심 코드

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;
    }
}

DialogueTrigger (NPC 상호작용)

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;
    }
}

Unity 대사 시스템 – JSON 데이터 구조 정리

❌ 코드에 직접 쓰는 방식

대사 수정할 때마다 코드 수정

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으로 분리함으로써
유지보수성과 확장성을 모두 확보한 대화 시스템을 구현했다.

profile
아이디어 뱅크

0개의 댓글