21. 유니티 탑다운 2D 게임 제작 - 퀘스트 시스템 구현
퀘스트 대화
오브젝트 데이타 스크립트를 따로 만든것처럼
퀘스트 데이터 스크립트를 따로 만듬
* 실전에서는 주로 xml SQL 등의 데이터 베이스를 통해 관리함
public class QuestData : MonoBehaviour
{
public string questName;
public int[] npcID;
public QuestData(string name, int[] ID)
{
questName = name;
npcID = ID;
}
}
퀘스트 데이터에 필요한 것은
퀘스트 이름, 퀘스트 아이디 인덱스
이 스크립트는 구조체로 사용
다른 스크립트에서 구조체를 사용하기 용이하도록 생성자를 따로 만들어서 사용
public QuestManager questManager;
NPC ID를 받고 퀘스트 번호를 반환하는 함수 생성
int questTalkIndex = questManager.GetQuestTalkIndex(id);
string talkData = talkmanager.GetTalk(id + questTalkIndex, talkIndex);
퀘스트를 매니저를 변수로 생성 후 퀘스트 번호를 가져옴
퀘스트 번호 + NPC ID = 퀘스트 대화 데이터 ID
//퀘스트 대화
talkData.Add(10 + 1000, new string[] { "우리마을에는 슬픈 전설이 있어... #0", "일단 트윈테일 여자애한테 말 거셈. #1", " 하야쿠 하야쿠 #3" });
talkData.Add(10 + 2000, new string[] { "아틔시미나토아쿠아! #0", "아틔시 퍼펙토나 메이드상! #1", "오츠아쿠아~ #2" });
퀘스트 대화는 10(퀘스트 아이디) + 1000(NPC 아이디) 와 같은 형식으로 삽입시킴
하지만 이방법으로 삽입하면 퀘스트 순서에 상관없이 대화문이 나옴
퀘스트 대화순서 변수 생성
퀘스트 순서 끝날시 퀘스트 인덱스 변수 1 올림
if (talkData == null)
{
isAction = false;
talkIndex = 0;
questManager.CheckQuest();
return;
}
대화 끝날때 CheckQuest()를 통해 인덱스 1 더해서 퀘스트 다음 과정으로 가게함
public void CheckQuest(int id)
{
if(id == questList[questId].npcID[questActionIndex])
questActionIndex++;
}
CheckQuest() 에서는
NPC의 아이디를 인자로 받아서
만약 QuestData 구조체의 npcID 배열순서
예를들어 Index가 0이면
questList.Add(10, new QuestData("첫 마을 방문 후 NPC들과 대화", new int[] {1000, 2000 }));
여기서 1000을 불러와서
이 1000이 Npc ID와 같으면 인덱스 1 상승
void NextQuest()
{
questId += 10;
questActionIndex = 0;
}
다음 퀘스트를 받게 해주는 함수 작성
다음 퀘스트를 받으면 인덱스 초기화
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class QuestManager : MonoBehaviour
{
public int questId;
public int questActionIndex;
Dictionary<int, QuestData> questList;
void Awake()
{
questList = new Dictionary<int, QuestData>();
GenerateData();
}
void GenerateData()
{
questList.Add(10, new QuestData("첫 마을 방문 후 NPC들과 대화", new int[] {1000, 2000 }));
questList.Add(20, new QuestData("남자한테 돌아가기", new int[] { 2000, 1000 }));
questList.Add(30, new QuestData("동전 찾기", new int[] { 1000, 2000 }));
}
public int GetQuestTalkIndex(int id)
{
return questId + questActionIndex;
}
public void CheckQuest(int id)
{
if(id == questList[questId].npcID[questActionIndex])
questActionIndex++;
if (questActionIndex == questList[questId].npcID.Length)
{
NextQuest();
}
}
void NextQuest()
{
questId += 10;
questActionIndex = 0;
}
}
완성된 퀘스트 매니저 스크립트
퀘스트 오브젝트 구현
void ControlObject()
{
switch (questId)
{
case 10:
break;
case 20:
if (questActionIndex == 2)
questObject[0].SetActive(true);
break;
case 30:
if(questActionIndex==1)
questObject[0].SetActive(false);
break;
}
}
컨트롤 오브젝트 함수를 통해
퀘스트 아이디가 각 케이스와 같을때마다
퀘스트 오브젝트를 OnOff
public void CheckQuest(int id)
{
//퀘스트가 현재 진행과정과 같은지 확인
if (id == questList[questId].npcID[questActionIndex]) {
questActionIndex++;
//컨트롤 퀘스트 오브젝트
ControlObject();
}
//현재 퀘스트가 끝났을 경우
if (questActionIndex == questList[questId].npcID.Length)
{
NextQuest();
}
}
퀘스트 인덱스를 상승시킬때 퀘스트 오브젝트 컨트롤 수행
하지만 퀘스트 수행 중 다른 NPC에게 말을 걸면 제대로 대화가 되지 않는 문제가 있음
예외 처리
if (!talkData.ContainsKey(id))
{
//해당 퀘스트 진행 순서중 대사가 없을때.
//퀘스트의 맨처음 대사를 가져옴
if (talkIndex == talkData[id - (id % 10)].Length)
return null;
else
return talkData[id - (id % 10)][talkIndex];
}
* talkData.ContainsKey(talkIndex) : 딕셔너리에 키값이 있는지 없는지 검사해주는 코드
GetTalk 코드에 NpcID값이 없을경우 퀘스트 대화순서를 아예 제거하고 재탐색
id - (id % 10) : id 값에서 10 나머지 값을 뻄
예시 : id =1022 일경우 1022 - (1022 % 10) = 1020을 리턴
만약 talkIndex 값이 그렇게 나온 1020에 해당하는 배열의 길이가 talkIndex값과 같을때
(퀘스트라인 막바지였을때) = null값을 호출
아닐 경우 다시 그 퀘스트의 talkIndex에 해당하는 순서의 대화를 불러옴
하지만 이경우 그냥 평범한 오브젝트나 NPC에게 말을 걸때 제약이 생김
public string GetTalk(int id, int talkIndex)
{
if (!talkData.ContainsKey(id))
{
if (!talkData.ContainsKey(id - id % 10))
{
//퀘스트 맨처음 대사마저 없을때
//기본 대사를 가져옴
if (talkIndex == talkData[id - (id % 100)].Length)
return null;
else
return talkData[id - (id % 100)][talkIndex];
}
//만약 퀘스트 진행 순서 대사가 없다면
//퀘스트 맨처음의 대사를 가져온다.
else
{
if (talkIndex == talkData[id - (id % 10)].Length)
{
return null;
}
else
return talkData[id - (id % 10)][talkIndex];
}
}
if (talkIndex >= talkData[id].Length)
{
return null;
}
return talkData[id][talkIndex];
}
제약 조건을 좀 더 추가해서 스크립트 작성
예시) 만약 퀘스트 상태가 10이고 talkIndex가 2일때 100인 오브젝트를 만지면 id : 110이 GetTalk 함수에 들어옴
그러면 첫 조건 (!talkData.ContainsKey(id)) 에서 id가 없기 때문에 다음 조건으로 넘어감
다음 조건 (!talkData.ContainsKey(id - id % 10)) 에서 110 - 0 = 110 이기 때문에 다음 조건으로 넘어감
그리고 마지막 조건 (talkIndex == talkData[id - (id % 100)].Length)에서 2 != (110 - 10 = 100의 길이 1)
이기 때문에 talkData[110 - (10)][talkIndex]; 으로 100 값의 talkData
talkData.Add(100, new string[] {"코레와... 책상이네요."});
이라는 데이터를 불러옴
로직 다듬기
다음으로 지저분한 로직을 다듬어야함
public string GetTalk(int id, int talkIndex)
{
if (!talkData.ContainsKey(id))
{
if (!talkData.ContainsKey(id - id % 10))
{
//퀘스트 맨처음 대사마저 없을때
//기본 대사를 가져옴
if (talkIndex == talkData[id - (id % 100)].Length)
return null;
else
return talkData[id - (id % 100)][talkIndex];
}
//만약 퀘스트 진행 순서 대사가 없다면
//퀘스트 맨처음의 대사를 가져온다.
else
{
if (talkIndex == talkData[id - (id % 10)].Length)
{
return null;
}
else
return talkData[id - (id % 10)][talkIndex];
}
}
if (talkIndex >= talkData[id].Length)
{
return null;
}
return talkData[id][talkIndex];
}
위 코드에서
if (talkIndex == talkData[id - (id].Length)
return null;
else
return talkData[id - (id][talkIndex];
이부분이 숫자만 바뀐 상태로 반복되기 때문에 이부분을
GetTalk()함수를 재귀함수로 써서 줄여줌
public string GetTalk(int id, int talkIndex)
{
if (!talkData.ContainsKey(id))
{
if (!talkData.ContainsKey(id - id % 10))
{
//퀘스트 맨처음 대사마저 없을때
//기본 대사를 가져옴
return GetTalk(id - (id % 100), talkIndex);
}
else
{
//만약 퀘스트 진행 순서 대사가 없다면
//퀘스트 맨처음의 대사를 가져온다.
return GetTalk(id - (id % 100), talkIndex);
}
}
if (talkIndex >= talkData[id].Length)
{
return null;
}
return talkData[id][talkIndex];
}
* 리턴 값이 있는 재귀함수는 return까지 같이 써주어야함
퀘스트 이름 띄우기
void Start()
{
Debug.Log(questManager.CheckQuest());
}
게임 매니저에 Start함수를 작성 후
디버그 로그로 CheckQuest() 호출
하지만 이대로면 오류 발생
public void CheckQuest(int id)
{
//퀘스트가 현재 진행과정과 같은지 확인
if (id == questList[questId].npcID[questActionIndex]) {
questActionIndex++;
//컨트롤 퀘스트 오브젝트
ControlObject();
}
//현재 퀘스트가 끝났을 경우
if (questActionIndex == questList[questId].npcID.Length)
{
NextQuest();
}
}
public string CheckQuest()
{
return questList[questId].name;
}
퀘스트매니저 스크립트에 함수는 같지만 인자가 다른 함수를 작성
* 오버로딩 : 매개변수에 따라 함수 호출
어차피 가져와야하는 것은 퀘스트 이름뿐이기 떄문에 퀘스트 이름만 리턴