게임에서 퀘스트는 방향성을 제공해 주고 게임의 목적성과 재미를 높여주며, 진행 상황을 추적하고 보상을 제공하여 게임을 보다 흥미롭게 만들어줍니다. 퀘스트 시스템을 구현하고 정리하였습니다.

 

💬 서론

  • 본 글은 게임을 모두 만든 후에 작성한 글로 핵심 주제 외 클래스 및 다른 기능에 대한 함수가 포함될 수 있습니다.

 

📖 구현 내용

  • 적 처치, 아이템 습득에 대한 퀘스트를 구현합니다.
  • 플레이어는 퀘스트를 받고 퀘스트를 완료할 수 있습니다.
  • 퀘스트 상태를 확인하여 상태별로 이벤트를 처리할 수 있습니다.
  • 스크립터블 오브젝트로 퀘스트의 데이터를 관리합니다.
  • 스크립터블 오브젝트를 프로젝트 폴더 내에서 관리하고, 이 데이터를 자동으로 불러옵니다.
  • ID를 이용하여 고유한 퀘스트의 중복 여부를 검사할 수 있습니다.
  • 하나의 퀘스트에서 여러 개의 목표 중 하나만 수행해도 퀘스트를 완료하는지에 대한 여부를 설정할 수 있습니다.
  • 퀘스트 콘텐츠를 보여주고 현재 진행 시점, 완료한 퀘스트들의 정보를 볼 수 있습니다.

 

⚒️ 구현

  • 이번 글에서는 퀘스트 콘텐츠 매니저를 사용할 때 풀 사이즈 UI창을 구현하고 구성요소 중 하나인 QuestFullContent 프리팹에대해 다루겠습니다.

· CompactContent

더보기
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using System.Text;

namespace Quest
{
    public class QuestCompactContent : MonoBehaviour
    {
        protected QuestData mQuestData;

        [Header("타이틀 텍스트 라벨")]
        [SerializeField] public TextMeshProUGUI TitleTextLabel;

        [Header("콘텐츠 텍스트 라벨")]
        [SerializeField] public TextMeshProUGUI ContentTextLabel;

        public void Init(QuestData questData)
        {
            this.mQuestData = questData;
        }

        public virtual void UpdateCompactQuestContents(QuestContentData contentData)
        {
            //타이틀 제목 변경
            {
                string compactTitleText = ((QuestManager.Instance.CheckQuestState(mQuestData.questId) == QuestState.CLEAR || QuestManager.Instance.CheckQuestState(mQuestData.questId) ==  QuestState.CLEARED_PAST) ? "<color=green>(완료)</color>" : null) + contentData.title;
                TitleTextLabel.text = compactTitleText;
            }

            //콘텐츠 내용 변경을 위한 스트링빌더
            StringBuilder contentBuilder = new StringBuilder();

            for (int i = 0; i < contentData.compactContent.Length; ++i)
            {
                if (contentData.compactContent[i] == '{') // String Format 시작점을 찾음
                {
                    int formatFrom = i;

                    for (; i < contentData.compactContent.Length; ++i) // String Format 시작점부터 '}'을 찾음
                    {
                        if (contentData.compactContent[i] == '}')
                        {
                            //포맷 인덱스 획득
                            int formatIndex = int.Parse(contentData.compactContent.Substring(formatFrom + 1, i - formatFrom - 1));

                            //포맷 인덱스에 맞는 문자열 가져오기
                            contentBuilder.Append(GetFormatIndex(mQuestData, formatIndex));
                            break;
                        }
                    }
                }
                else
                {
                    contentBuilder.Append(contentData.compactContent[i]);
                }
            }

            ContentTextLabel.text = contentBuilder.ToString();
        }    


        private string GetFormatIndex(QuestData questData, int formatId)
        {
            for(int i = 0; i < questData.allQuests.Length; ++i)
            {
                if(questData.allQuests[i].formatId == formatId)
                    return questData.allQuests[i].GetFormatText();
            }

            Debug.LogErrorFormat("{0}에서 {1}번의 퀘스트 포맷 인덱스가 없음!", questData.name, formatId);

            return null;
        }

    }
}

 

protected QuestData mQuestData;
  • 현재 이 클래스가 담당하는 퀘스트 데이터입니다.
  • 퀘스트 데이터 내부의 변수를 읽어 적절하게 텍스트 콘텐츠를 표시합니다.

 

[Header("타이틀 텍스트 라벨")]
[SerializeField] public TextMeshProUGUI TitleTextLabel;
  • 타이틀 텍스트 라벨입니다.
  • 퀘스트의 이름을 표시합니다.

 

[Header("콘텐츠 텍스트 라벨")]
[SerializeField] public TextMeshProUGUI ContentTextLabel;
  • 콘텐츠 텍스트 라벨입니다.
  • 현재 퀘스트의 진행 상태를 요약하여 보여줍니다.

 

public void Init(QuestData questData)
{
    this.mQuestData = questData;
}
  • 이 클래스가 생성될때 호출되며 퀘스트 데이터를 이 클래스에 레퍼런스해줍니다.

 

public virtual void UpdateCompactQuestContents(QuestContentData contentData)
{
    //타이틀 제목 변경
    {
        string compactTitleText = ((QuestManager.Instance.CheckQuestState(mQuestData.questId) == QuestState.CLEAR || QuestManager.Instance.CheckQuestState(mQuestData.questId) ==  QuestState.CLEARED_PAST) ? "<color=green>(완료)</color>" : null) + contentData.title;
        TitleTextLabel.text = compactTitleText;
    }

    //콘텐츠 내용 변경을 위한 스트링빌더
    StringBuilder contentBuilder = new StringBuilder();

    for (int i = 0; i < contentData.compactContent.Length; ++i)
    {
        if (contentData.compactContent[i] == '{') // String Format 시작점을 찾음
        {
            int formatFrom = i;

            for (; i < contentData.compactContent.Length; ++i) // String Format 시작점부터 '}'을 찾음
            {
                if (contentData.compactContent[i] == '}')
                {
                    //포맷 인덱스 획득
                    int formatIndex = int.Parse(contentData.compactContent.Substring(formatFrom + 1, i - formatFrom - 1));

                    //포맷 인덱스에 맞는 문자열 가져오기
                    contentBuilder.Append(GetFormatIndex(mQuestData, formatIndex));
                    break;
                }
            }
        }
        else
        {
            contentBuilder.Append(contentData.compactContent[i]);
        }
    }

    ContentTextLabel.text = contentBuilder.ToString();
}
  • 현재 퀘스트 상태를 고려하여 적절하게 텍스트를 설정합니다.
  • GetFormatIndex 함수를 호출하여 현재 퀘스트 데이터의 포맷 아이디에 맞는 적절한 텍스트를 가져옵니다.
  • String.Format에 영감을 받아 디자인하였습니다.
  • 이 함수를 이용하여 현재 진행 값을 보여줄 수 있습니다.
  • 예시로 아이템이 3개가 남았다면, "[1/4]개 획득하기"처럼 보여지게 할 수 있습니다.

 

private string GetFormatIndex(QuestData questData, int formatId)
{
    for(int i = 0; i < questData.allQuests.Length; ++i)
    {
        if(questData.allQuests[i].formatId == formatId)
            return questData.allQuests[i].GetFormatText();
    }

    Debug.LogErrorFormat("{0}에서 {1}번의 퀘스트 포맷 인덱스가 없음!", questData.name, formatId);

    return null;
}
  • formatId에 맞는 적절한 텍스트를 가져오고 리턴해줍니다.

 

· UI 설정 

  • 전체 구성은 다음과 같습니다.

✅ 사용

bonnate