1. NPC 대사 관리 Script
using UnityEngine;
[CreateAssetMenu(fileName = "DialogueData", menuName = "Dialogue/Dialogue Data")]
public class DialogueData : ScriptableObject
{
[Header("대화 이름")]
[SerializeField] private string speakerName;
[Header("대사 목록")]
[TextArea(2, 4)]
[SerializeField] private string[] lines;
public string SpeakerName => speakerName;
public string[] Lines => lines;
}
using UnityEngine;
public class NPCDialogue : MonoBehaviour
{
[Header("NPC 대사 데이터")]
[SerializeField] private DialogueData dialogueData;
public DialogueData DialogueData => dialogueData;
public void StartDialogue()
{
if (dialogueData == null)
{
Debug.LogWarning($"[NPCDialogue] {gameObject.name}에 DialogueData가 없습니다.");
return;
}
DialogueManager.Instance.StartDialogue(dialogueData);
Debug.Log($"[NPCDialogue] {gameObject.name} 대화 시작 요청");
}
}
using System.Collections;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
public class DialogueManager : MonoBehaviour
{
public static DialogueManager Instance { get; private set; }
[Header("UI")]
[SerializeField] private GameObject dialoguePanel;
[SerializeField] private TMP_Text speakerNameText;
[SerializeField] private TMP_Text dialogueText;
[Header("타이핑 설정")]
[SerializeField] private float typingSpeed = 0.04f;
[Header("대화 종료 이벤트")]
[SerializeField] private UnityEvent onDialogueEnd;
private DialogueData currentDialogueData;
private int currentIndex;
private bool isTyping;
private string currentLine;
private Coroutine typingCoroutine;
private void Awake()
{
if (Instance != null && Instance != this)
{
Debug.LogWarning("[DialogueManager] 중복된 DialogueManager가 제거되었습니다.");
Destroy(gameObject);
return;
}
Instance = this;
}
public void StartDialogue(DialogueData dialogueData)
{
if (dialogueData == null)
{
Debug.LogWarning("[DialogueManager] 전달받은 DialogueData가 없습니다.");
return;
}
if (dialogueData.Lines == null || dialogueData.Lines.Length == 0)
{
Debug.LogWarning("[DialogueManager] 출력할 대사가 없습니다.");
return;
}
currentDialogueData = dialogueData;
currentIndex = 0;
dialoguePanel.SetActive(true);
speakerNameText.text = currentDialogueData.SpeakerName;
ShowCurrentLine();
Debug.Log($"[DialogueManager] 대화 시작: {currentDialogueData.SpeakerName}");
}
public void OnClickNext()
{
if (currentDialogueData == null)
{
return;
}
if (isTyping)
{
CompleteCurrentLine();
return;
}
MoveNextLine();
}
private void ShowCurrentLine()
{
currentLine = currentDialogueData.Lines[currentIndex];
if (typingCoroutine != null)
{
StopCoroutine(typingCoroutine);
}
typingCoroutine = StartCoroutine(TypeLine(currentLine));
}
private IEnumerator TypeLine(string line)
{
isTyping = true;
dialogueText.text = string.Empty;
foreach (char letter in line)
{
dialogueText.text += letter;
yield return new WaitForSeconds(typingSpeed);
}
isTyping = false;
typingCoroutine = null;
Debug.Log($"[DialogueManager] 문장 출력 완료: {line}");
}
private void CompleteCurrentLine()
{
if (typingCoroutine != null)
{
StopCoroutine(typingCoroutine);
typingCoroutine = null;
}
dialogueText.text = currentLine;
isTyping = false;
Debug.Log("[DialogueManager] 현재 문장 즉시 출력");
}
private void MoveNextLine()
{
currentIndex++;
if (currentIndex >= currentDialogueData.Lines.Length)
{
EndDialogue();
return;
}
ShowCurrentLine();
Debug.Log($"[DialogueManager] 다음 문장 이동: {currentIndex}");
}
private void EndDialogue()
{
dialogueText.text = string.Empty;
speakerNameText.text = string.Empty;
dialoguePanel.SetActive(false);
Debug.Log("[DialogueManager] 대화 종료");
currentDialogueData = null;
currentIndex = 0;
onDialogueEnd?.Invoke();
}
}
using UnityEngine;
public class PlayerInteraction : MonoBehaviour
{
private NPCDialogue currentNPC;
private void Update()
{
if (Input.GetKeyDown(KeyCode.F))
{
TryTalk();
}
}
private void TryTalk()
{
if (currentNPC == null)
{
Debug.Log("[PlayerInteraction] 대화 가능한 NPC가 없습니다.");
return;
}
currentNPC.StartDialogue();
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.TryGetComponent(out NPCDialogue npcDialogue))
{
currentNPC = npcDialogue;
Debug.Log($"[PlayerInteraction] 대화 가능 NPC 감지: {other.name}");
}
}
private void OnTriggerExit2D(Collider2D other)
{
if (other.TryGetComponent(out NPCDialogue npcDialogue) && currentNPC == npcDialogue)
{
currentNPC = null;
Debug.Log($"[PlayerInteraction] 대화 가능 NPC 해제: {other.name}");
}
}
}
2. 사용법
1. SO를 생성한다.
2. NPCDialogue을 NPC에 넣는다.
3. NPC대사에 해당하는 SO를 넣는다.
3. DialogueManager를 빈 오브젝트에 넣은 후 Text 들을 연결한다.