| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
- 구현
- 워크플로
- 해시
- unityui
- FSM
- 언리얼엔진
- Unity2D
- Firebase
- 스파르타내일배움캠프TIL
- 내일배움캠프
- 알고리즘
- 문자열
- UnrealEngine
- 스택
- Unity3d
- 유클리드호제법
- Inventory
- Photon
- 포톤
- 순열
- 이분탐색
- 스파르타내일배움캠프
- 유니티
- Unity
- BFS
- C++
- c#
- UE4
- QueryDSL
- 프로그래머스
- Today
- Total
개발 낙서장
[Unity 3D] NPC 대화 본문
구현 동기
퀘스트를 만들었으면 NPC와 대화해 수락, 진행, 완료를 할 수 있어야 한다.
특정 조건을 만족하면 자동으로 완료되는 퀘스트들도 있지만 나는 NPC를 통해서만 수락, 완료가 되도록 했다.
구현 내용
대화 UI
우선 NPC에게 말을 걸었을 때 나타나는 UI를 가장 먼저 만들어야 한다.
대화 UI는 이 UI 하나에서 대화, 퀘스트 보기, 수락, 완료 등이 다 진행되도록 구현했다.

지금 와서 보니 조금 난잡하기도 하고 어느 정도 수정이 필요해 보이긴 하지만
일단 기능에는 문제가 없으니...
이렇게 UI를 다 만들었다면 동작하게 할 스크립트가 필요하다.
나는 대화 UI도 간단한 상태 패턴으로 구현했다.
먼저 Enum으로 현재 UI의 상태들을 정의해줬다.

이 UI에 존재하는 모든 버튼을 누르면 호출되는 함수이다.
버튼의 개수가 좀 있어서 각 버튼 별로 호출 함수를 따로 만들기보다는 함수 하나를 호출하는 게 훨씬 낫다고 생각했다.
버튼에 리스너를 등록할 때 각 State에 맞는 int값을 매개변수로 받아 각 상태에 맞게 UI를 세팅하도록 했다.
퀘스트 리스트
퀘스트를 수락하려면 먼저 NPC한테서 어떤 퀘스트를 받을 수 있는지 목록을 봐야 할 것이다.

퀘스트의 진행 정도에 따라 수락 가능, 진행 중, 완료 가능, 수락 불가로 나누게 했고
이를 Dictionary<Quest, int>에 넣어 정렬해 주었다.

Value는 0부터 3까지 차례대로 완료 가능, 진행 중, 수락 가능, 수락 불가로 지정하여 Dictionary에 들어가게 했다.
GC를 방지하기 위해 각 퀘스트는 버튼 프리팹으로 생성해 놓고 오브젝트 풀링 기법을 이용하여 UI에 생성, 활성화, 비활성화되도록 했다.
public void OnClickQuest()
{
_npcDialogue._selectedQuest = _quest.Key;
_questManager = PlayerController.instance.QuestManager;
QuestData quest = _questManager.GetQuest(_quest.Key);
if (quest != null && quest.CanComplete)
{
_npcDialogue.SetState((int)NPCDialogueState.QUESTDETAIL);
return;
}
else if(_quest.Value == 3)
{
string s = string.Format("<선행 조건>\nLv : {0}\n선행 퀘스트 : {1}", _quest.Key._requiredLevel, _quest.Key._precednetQuest == null ? "없음" : _quest.Key._precednetQuest._title);
_npcDialogue.SetState((int)NPCDialogueState.DIALOGUE);
_npcDialogue.SetContentText(s);
return;
}
if (_quest.Key._type == Quest.Type.DIALOGUE)
{
DialogueQuest dq = (DialogueQuest)_quest.Key;
if(_questManager.FindQuest(_quest.Key))
{
DialogueQuestData dialogueQuest = quest as DialogueQuestData;
NPC npc = _npcDialogue._npc;
if (dq._targetNPC[dialogueQuest._currentIndex] == npc)
{
_npcDialogue.SetQuestDialogue(dq._npcDialogue[dialogueQuest._currentIndex]._sentences);
_npcDialogue.SetState((int)NPCDialogueState.QUESTDIALOGUE);
}
}
else
{
if (_quest.Key._startNPC == _npcDialogue._npc)
{
_npcDialogue.SetQuestDialogue(_quest.Key._dialogues);
_npcDialogue.SetState((int)NPCDialogueState.QUESTDIALOGUE);
}
}
}
else if (_quest.Key._type == Quest.Type.HUNTING)
_npcDialogue.SetState((int)NPCDialogueState.QUESTDETAIL);
}
public void OnClickQuest()
{
_npcDialogue._selectedQuest = _quest.Key;
_questManager = PlayerController.instance.QuestManager;
QuestData quest = _questManager.GetQuest(_quest.Key);
if (quest != null && quest.CanComplete)
{
_npcDialogue.SetState((int)NPCDialogueState.QUESTDETAIL);
return;
}
else if(_quest.Value == 3)
{
string s = string.Format("<선행 조건>\nLv : {0}\n선행 퀘스트 : {1}", _quest.Key._requiredLevel, _quest.Key._precednetQuest == null ? "없음" : _quest.Key._precednetQuest._title);
_npcDialogue.SetState((int)NPCDialogueState.DIALOGUE);
_npcDialogue.SetContentText(s);
return;
}
if (_quest.Key._type == Quest.Type.DIALOGUE)
{
DialogueQuest dq = (DialogueQuest)_quest.Key;
if(_questManager.FindQuest(_quest.Key))
{
DialogueQuestData dialogueQuest = quest as DialogueQuestData;
NPC npc = _npcDialogue._npc;
if (dq._targetNPC[dialogueQuest._currentIndex] == npc)
{
_npcDialogue.SetQuestDialogue(dq._npcDialogue[dialogueQuest._currentIndex]._sentences);
_npcDialogue.SetState((int)NPCDialogueState.QUESTDIALOGUE);
}
}
else
{
if (_quest.Key._startNPC == _npcDialogue._npc)
{
_npcDialogue.SetQuestDialogue(_quest.Key._dialogues);
_npcDialogue.SetState((int)NPCDialogueState.QUESTDIALOGUE);
}
}
}
else if (_quest.Key._type == Quest.Type.HUNTING)
_npcDialogue.SetState((int)NPCDialogueState.QUESTDETAIL);
}
각 퀘스트 버튼을 눌렀을 때 호출되는 함수이다.
- 현재 퀘스트가 완료 가능 혹은 수락 불가 상태인지
- 대화 퀘스트일 경우 현재 대화중인 NPC와 내 퀘스트 진행도가 맞는지
- 사냥 퀘스트인 경우
이렇게 3단계로 나누었다. 대화 퀘스트 역시 이 UI에서 진행되게 하였기 때문에 따로 조건을 짰다.
퀘스트 디테일
public void AcceptQuest()
{
QuestData questData = null;
if (_quest._type == Quest.Type.HUNTING)
{
questData = new HuntingQuestData(_quest as HuntingQuest);
}
else if (_quest._type == Quest.Type.DIALOGUE)
{
questData = new DialogueQuestData(_quest as DialogueQuest);
}
else
{
Debug.Log("!!!Check Quest Type!!!");
return;
}
PlayerController.instance.QuestManager.AcceptQuest(questData);
questData.OnQuestComplete.AddListener(_npcDialogue._npcAI.CheckQuestState);
_npcDialogue._npcAI.CheckQuestState();
_npcDialogue.SetState((int)NPCDialogueState.INIT);
}
public void CompleteQuest()
{
QuestManager questManager = PlayerController.instance.QuestManager;
questManager.CompleteQuest(_quest, _npcDialogue._npcAI.CheckQuestState);
_npcDialogue._npcAI.CheckQuestState();
_npcDialogue.gameObject.SetActive(false);
if(_quest._isTimeline)
TimelineController.instance.PlayFromTimeline(_quest._playableDirector, _quest._timeline);
}
퀘스트가 완료 가능하거나 수락 불가할 때, 사냥 퀘스트일 때에는 퀘스트 정보를 볼 수 있도록 했다.
구현 후기

처음 UI 구상할 때는 '퀘스트 리스트만 가져와서 플레이어 퀘스트와 비교하면 끝이네?'라고 되게 간단할 거라고 생각했는데 꽤 오래 걸렸던 것 같다.
특히 대화 퀘스트의 존재 때문에 더 까다로웠던 것 같다.
단순히 사냥 퀘스트만 있고 수락, 완료만 누르게 한다면 굉장히 간단했겠지만 대화 진행까지 이 UI에서 하다 보니 npc 비교, 대화 내용, 대화 진행도 등 따져야 할 것들이 많아졌다.
대화 퀘스트의 구현을 포기했다면 훨씬 쉬웠겠지만 사냥 퀘스트 하나로는 뭔가 부족하다고 생각해 그래도 끝까지 구현했다.
Scriptable Object에서 대화 내용을 수정만 해주면 실제 게임 UI에서 바로 나타나는 걸 보니 조금 뿌듯한 것 같기도?
'Unity > Devlog' 카테고리의 다른 글
| [Unity 2D] 캐릭터 움직임 (1) | 2023.01.23 |
|---|---|
| [Unity 3D] NPC 퀘스트 팝업 (0) | 2023.01.16 |
| [Unity 3D] 퀘스트 (2) | 2023.01.09 |
| [Unity 3D] Collider의 크기를 넘겨줄 때 주의사항 (0) | 2023.01.09 |
| [Unity 3D] 몬스터 체력바 수정 (0) | 2023.01.06 |