게임에서 퀘스트는 방향성을 제공해 주고 게임의 목적성과 재미를 높여주며, 진행 상황을 추적하고 보상을 제공하여 게임을 보다 흥미롭게 만들어줍니다. 퀘스트 시스템을 구현하고 정리하였습니다.
💬 서론
- 본 글은 게임을 모두 만든 후에 작성한 글로 핵심 주제 외 클래스 및 다른 기능에 대한 함수가 포함될 수 있습니다.
📖 구현 내용
- 적 처치, 아이템 습득에 대한 퀘스트를 구현합니다.
- 플레이어는 퀘스트를 받고 퀘스트를 완료할 수 있습니다.
- 퀘스트 상태를 확인하여 상태별로 이벤트를 처리할 수 있습니다.
- 스크립터블 오브젝트로 퀘스트의 데이터를 관리합니다.
- 스크립터블 오브젝트를 프로젝트 폴더 내에서 관리하고, 이 데이터를 자동으로 불러옵니다.
- ID를 이용하여 고유한 퀘스트의 중복 여부를 검사할 수 있습니다.
- 하나의 퀘스트에서 여러 개의 목표 중 하나만 수행해도 퀘스트를 완료하는지에 대한 여부를 설정할 수 있습니다.
- 퀘스트 콘텐츠를 보여주고 현재 진행 시점, 완료한 퀘스트들의 정보를 볼 수 있습니다.
⚒️ 구현
- 이번 글에서는 퀘스트 데이터를 관리하기 위한 디자인을 다룹니다.
· QuestBase
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
namespace Quest
{
#region 퀘스트 종류
[System.Serializable]
public abstract class QuestBase
{
[Header("이 값을 표현할 포맷 id번호")] public int formatId = -1;
/// <summary>
/// 퀘스트 데이터의 파트 중 하나인 이 퀘스트를 클리어했는가?
/// </summary>
[HideInInspector] public bool isPartClear = false;
abstract public string GetFormatText();
}
/// <summary>
/// 적을 처치해야하는 퀘스트
/// </summary>
[System.Serializable]
public class Quest_KillTargets : QuestBase
{
/// <summary>
/// 처치해야할 대상의 코드
/// </summary>
[Header("처치해야할 대상의 코드")][SerializeField] public EnemyCode enemyCode;
/// <summary>
/// 처치해야 할 수
/// </summary>
[Space(20)][Header("처치해야 할 수")][SerializeField] public int killCount;
[HideInInspector] public int currentKillCount = 0; //현재 처치한 적 수
public override string GetFormatText()
{
return $"{Mathf.Clamp(currentKillCount, 0, killCount)}/{killCount}";
}
}
/// <summary>
/// 적을 처치해야하는 퀘스트
/// </summary>
[System.Serializable]
public class Quest_GetItems : QuestBase
{
/// <summary>
/// 획득해야 할 아이템의 코드
/// </summary>
[Header("획득해야 할 아이템의 코드")][SerializeField] public EntityCode itemCode;
/// <summary>
/// 획득해야 하는 수
/// </summary>
[Space(20)][Header("획득해야 하는 수")][SerializeField] public int itemCount;
[HideInInspector] public int currentItemCount = 0; //현재 획득한 아이템 수
public override string GetFormatText()
{
return $"{Mathf.Clamp(currentItemCount, 0, itemCount)}/{itemCount}";
}
}
#endregion
}
/// <summary>
/// 퀘스트 상태
/// </summary>
[System.Serializable] public enum QuestState
{
/// <summary>
/// 단 한번도 퀘스트를 받은적이 없음
/// </summary>
NEVER_RECEIVED,
/// <summary>
/// 현재 퀘스트를 받아 진행중
/// </summary>
ONGOING,
/// <summary>
/// 현재 퀘스트를 받은 상태 및 목표를 모두 완수한 상태
/// </summary>
CLEAR,
/// <summary>
/// 이전에 해당 퀘스트를 완료
/// </summary>
CLEARED_PAST
}
[System.Serializable]
public abstract class QuestBase
{
[Header("이 값을 표현할 포맷 id번호")] public int formatId = -1;
/// <summary>
/// 퀘스트 데이터의 파트 중 하나인 이 퀘스트를 클리어했는가?
/// </summary>
[HideInInspector] public bool isPartClear = false;
abstract public string GetFormatText();
}
- 기본적인 퀘스트 데이터셋입니다.
- 퀘스트의 종류에 따라 다른 속성을 가지는 경우를 고려한 상위 부모 클래스입니다.
- 함수 오버라이딩을 이용하여 편리하게 이용하기 위해 사용합니다.
/// <summary>
/// 적을 처치해야하는 퀘스트
/// </summary>
[System.Serializable]
public class Quest_KillTargets : QuestBase
{
/// <summary>
/// 처치해야할 대상의 코드
/// </summary>
[Header("처치해야할 대상의 코드")][SerializeField] public EnemyCode enemyCode;
/// <summary>
/// 처치해야 할 수
/// </summary>
[Space(20)][Header("처치해야 할 수")][SerializeField] public int killCount;
[HideInInspector] public int currentKillCount = 0; //현재 처치한 적 수
public override string GetFormatText()
{
return $"{Mathf.Clamp(currentKillCount, 0, killCount)}/{killCount}";
}
}
- 적을 처치하는 타입의 퀘스트입니다.
- QuestBase를 상속받고 새로운 속성을 가집니다.
- GetFromText() 함수를 재정의하여 성질에 맞는 역할을 수행합니다.
- EnemyCode는 적 객체를 구분하는 enum입니다.
/// <summary>
/// 적을 처치해야하는 퀘스트
/// </summary>
[System.Serializable]
public class Quest_GetItems : QuestBase
{
/// <summary>
/// 획득해야 할 아이템의 코드
/// </summary>
[Header("획득해야 할 아이템의 코드")][SerializeField] public EntityCode itemCode;
/// <summary>
/// 획득해야 하는 수
/// </summary>
[Space(20)][Header("획득해야 하는 수")][SerializeField] public int itemCount;
[HideInInspector] public int currentItemCount = 0; //현재 획득한 아이템 수
public override string GetFormatText()
{
return $"{Mathf.Clamp(currentItemCount, 0, itemCount)}/{itemCount}";
}
}
- 아이템을 획득하는 퀘스트입니다.
- 적을 처치하는 퀘스트와 동일하게 QuestBase를 상속받고 새로운 속성을 가집니다.
- GetFromText() 함수를 재정의하여 성질에 맞는 역할을 수행합니다.
- EntityCode는 item을 포함한 여러 객체를 구분하는 enum입니다.
· QuestData
- 퀘스트 데이터로 스크립터블 오브젝트를 사용하여 위에서 다룬 클래스들을 멤버변수로 지닙니다.
- 이 데이터를 이용하여 퀘스트 정보를 읽고 여러 연산을 수행합니다.
[System.Serializable] [CreateAssetMenu(fileName = "Quest", menuName = "Add Quest/Quest")]
public class QuestData : ScriptableObject
{
[HideInInspector] public QuestState questState = QuestState.NEVER_RECEIVED;
[Header("고유한 퀘스트의 ID")]
[SerializeField] public int questId;
[Space(50)]
[Header("퀘스트 목표")]
[SerializeField] public Quest_KillTargets[] killTargetsQuests;//적을 처치하는 목표
[SerializeField] public Quest_GetItems[] getItemsQuests;//아이템을 획득하는 목표
[Space(50)]
[Header("퀘스트의 목표 중 하나만 수행해도 되는 타입인가?")]
[SerializeField] public bool isOptionalQuestType;//퀘스트의 목표 중 하나만 수행해도 되는 타입인가?
[Space(10)]
[Header("퀘스트를 완료 시 지급 경험치")]
[SerializeField] public float expAmount;// 경험치 양
[Space(10)]
[Header("퀘스트를 진행하는 씬 이름")] public string questScene;
[Header("퀘스트를 의뢰한 대상의 위치")] public Vector3 sourcePos;
[Header("퀘스트를 수행하러 가야 할 목적지 위치")] public Vector3 destinationPos;
/// <summary>
/// 해당 퀘스트가 가지는 모든 부분적 퀘스트
/// </summary>
public QuestBase[] allQuests;
}
[HideInInspector] public QuestState questState = QuestState.NEVER_RECEIVED;
- 현재 이 퀘스트의 상태를 나타냅니다.
- 퀘스트를 한 번도 받지 않았는지, 수행 중인지, 완료한 상태인지 등을 저장합니다.
[Header("고유한 퀘스트의 ID")]
[SerializeField] public int questId;
- 퀘스트의 ID입니다.
- 이 값은 고유하며, 다른 값들과 중복되어서는 안 됩니다.
- 인스펙터에서 추가 기능으로 ID값이 중복되는지 검사할 수 있습니다.
[Header("퀘스트 목표")]
[SerializeField] public Quest_KillTargets[] killTargetsQuests;//적을 처치하는 목표
[SerializeField] public Quest_GetItems[] getItemsQuests;//아이템을 획득하는 목표
- 퀘스트의 목표를 지정합니다.
- 위에서 다뤘던 두 개의 퀘스트들을 미리 지정하여 퀘스트 데이터를 구성할 수 있습니다.
[Header("퀘스트의 목표 중 하나만 수행해도 되는 타입인가?")]
[SerializeField] public bool isOptionalQuestType;//퀘스트의 목표 중 하나만 수행해도 되는 타입인가?
- 이 퀘스트가 목표들 중 하나만 수행해도 되는지에 대한 여부를 설정합니다.
- 이 값이 참이라면, 퀘스트 목표들 중 하나만 완료를 하면 퀘스트 자체를 완료했다고 설정됩니다.
[Header("퀘스트를 완료 시 지급 경험치")]
[SerializeField] public float expAmount;// 경험치 양
- 이 퀘스트를 수행하면 플레이어에게 지급하는 경험치 양을 설정합니다.
[Header("퀘스트를 진행하는 씬 이름")] public string questScene;
[Header("퀘스트를 의뢰한 대상의 위치")] public Vector3 sourcePos;
[Header("퀘스트를 수행하러 가야 할 목적지 위치")] public Vector3 destinationPos;
- 내비게이션 기능을 이용하기 위해 사용하는 변수입니다.
- questScene은 퀘스트를 수행하는 씬 이름으로, 해당 씬이 아니면 내비게이션이 작동하지 않습니다.
- 또한 sourcePos, destinationPos는 내비게이션이 향할 위치를 설정합니다.
/// <summary>
/// 해당 퀘스트가 가지는 모든 부분적 퀘스트
/// </summary>
public QuestBase[] allQuests;
- 각 목표들을 상위 클래스인 QuestBase로 저장한 배열입니다.
- 이 배열을 이용하여 다른 함수들에서 서로 다른 클래스의 퀘스트들을 한 번에 접근할 수 있습니다.
/// <summary>
/// 퀘스트 상태
/// </summary>
[System.Serializable] public enum QuestState
{
/// <summary>
/// 단 한번도 퀘스트를 받은적이 없음
/// </summary>
NEVER_RECEIVED,
/// <summary>
/// 현재 퀘스트를 받아 진행중
/// </summary>
ONGOING,
/// <summary>
/// 현재 퀘스트를 받은 상태 및 목표를 모두 완수한 상태
/// </summary>
CLEAR,
/// <summary>
/// 이전에 해당 퀘스트를 완료
/// </summary>
CLEARED_PAST
}
- 퀘스트의 상태를 나타냅니다.
✅ 사용
- 스크립터블 오브젝트로 에셋을 만든 후 퀘스트를 설정합니다.
- 처치해야 할 목표 또는 획득해야 할 아이템 목표를 넣고 설정할 수 있습니다.
- 본 이미지는 늑대를 처치하는 퀘스트를 설정한 결과입니다.
'unity game modules' 카테고리의 다른 글
[유니티] 퀘스트 시스템(3) - 콘텐츠 매니저 (0) | 2023.04.11 |
---|---|
[유니티] 퀘스트 시스템(2) - 퀘스트 매니저 (0) | 2023.04.11 |
[유니티] 오브젝트의 중심을 기준으로 회전 (1) | 2023.04.08 |
[유니티] 데미지 표시 (Damage Indicator) (0) | 2023.04.03 |
[유니티] 로딩 화면 구현 (0) | 2023.04.02 |