1. 설계
2. 트리구조와 무슨 연관?
3. 퀘스트 수락 -> 퀘스트 완료 -> 연계 퀘스트 추가 플로우
4. 구현 코드
5. 마무리
📝 1. 퀘스트 시스템
🔖 설계
□ QuestState
■ 퀘스트의 상태를 나타내기 위해 만든 enum
■ 받기전, 받은 후, 보상 수령 가능한 상태, 수령받은 후 끝난 상태
□ QuestClass 클래스
■ abstract 클래스
■ '퀘스트 완료 조건 메서드'를 저장하는 Func<bool> completeQuest 를 필드로 가지고 있음
■ TodoMission() 메서드
□ 하위클래스에서 '퀘스트 완료 조건' 구현
□ 조건이 만족하면 => true 반환
□ 조건이 불만족하면 => false 반환
□ MonsterKillQuest 클래스 / EquiptQuest 클래스
■ TodoMission () 메서드를 오버라이딩
□ QuestManager 클래스
■ List<Quest> performedQuest
□ 수행가능한 퀘스트 클래스들을 담을 컨테이너
📝 2. 트리랑 무슨 연관?
: 연계퀘스트를 어떻게 구성할지 다이어그램을 작성하다가 이 구조가 트리와 굉장히 닮았다고 생각했다.
: 퀘스트에서 트리구조를 사용하면 몇 가지 장점이 있다.
1. 부모퀘스트는 자식에 해당하는 퀘스트들을 관리할 수 있다.
2. 자식에 해당하는 퀘스트는 부모퀘스트 정보를 알 수 있다.
3. 자식에 해당하는 퀘스트는 부모퀘스트를 트리에서 탐색(search) 할 수 있다.
🔖 그냥 리스트 사용하면 안 되나?
그럼 해당 퀘스트가 어떤 퀘스트로부터 파생되었는지 알기 힘들다.
예를 들어서, 수행하지 못하는 퀘스트도 화면에 표시한다고 생각하자
------------------
[퀘스트]
1. Armor 장비 한 개 착용 [완료가능]
2. Weapon 장비 한개 착용 [수락가능]
3. 인벤토리에 15개 이상 장비 착용 [수행불가]
------------------
이미지상 '인벤토리에 15개 이상 장비 착용'퀘스트는 '무기 장비 1개 착용'퀘스트 후에 수행가능한 연계퀘스트이다.
즉 , '무기 장비 1개 착용'을 완료해야 퀘스트 수락이 가능하다.
이때, ' 인벤토리에 15개 이상 장비 착용' 퀘스트가 어느 퀘스트로부터 파생되었는지 알 수 있는 방법이 없다.
그래서 트리 구조로 [부모퀘스트]와 [자식퀘스트]를 연결시키는 것이다.
📝 3. 수락 → 완료 → 연계퀘스트 추가 플로우
: 주의 깊게 봐야 할 부분은 빨간색 박스 부분이다.
: 퀘스트를 완료했다면
→ 완료한 퀘스트의 연관퀘스트를 '수행가능한 퀘스트 리스트'에 담아야 한다.
📝 4. 구현 코드
tree를 사용한 연계퀘스트에 중점을 맞췄기 때문에 다른 코드는 생략하였다.
- Quest 클래스
public abstract class Quest
{
// 필드
protected string questName; // 퀘스트이름
protected string questStory; // 퀘스트 스토리 (ex) 미니언들이 너무 많아졋다고 생각~
protected int rewardGold; // 리워드
protected string questPerform; // 수행내역
protected QuestState questState; // 수행 스탯
// 컨테이너
protected Dictionary<Item, int> rewardItemByCount; // 보상 아이템별 count
protected Func<bool> completeQuest; // 성공여부 Func
// 트리구조
protected Quest parentQuest;
protected List<Quest> childQuest;
// 프로퍼티
public Quest ParentQuest { get => parentQuest; set { parentQuest = value; } }
public List<Quest> ChildQuest => childQuest;
public Quest(string name, string tooltip, string questPerfom , int rewardGold)
{
this.questName = name;
this.questStory = tooltip;
this.questPerform = questPerfom;
this.questState = QuestState.beforeReceive; // 생성할 때, 받기전으로 설정
this.rewardGold = rewardGold;
if (rewardItemByCount == null)
rewardItemByCount = new Dictionary<Item, int>();
if(childQuest == null)
childQuest = new List<Quest>();
}
public void AddToItem(Item item, int count)
{
// 코드생략
}
public void CheckState()
{
// 받기전이면 return
if (questState == QuestState.beforeReceive)
return;
// 조건을 만족하면
if (completeQuest.Invoke())
{
// 완료 state로 변환
this.questState = QuestState.complete;
}
}
// child 자식 대입
public void AddChild(Quest child)
{
child.parentQuest = this;
childQuest.Add(child);
}
// 하위에서 작성해야할 퀘스트 성공 조건
public abstract bool TodoMission();
// 퀘스트 진행내역
public abstract string QuestProgress();
}
□ Quest 클래스
■ 자세한 설명은 상단의 클래스다이어그램 참고
■ ✨ AddChild() 메서드 ✨
□ Quest클래스를 매개변수로 가진다
□ 매개변수로 들어온 자식 퀘스트의 부모퀘스트를 this, 즉 내 클래스로 지정한다
□ 현재 퀘스트의 자식클래스의 리스트에 매개변수로 들어온 자식 클래스를 add 한다
■ ✨ CheckState() 메서드 ✨
□ Func<bool> completeQuest 를 Invoke()
■ true를 return → complete로 상태 변환
■ flase를 return → 상태 변환 x
- MonsterKillQuest 클래스
public class MonsterKillQuest : Quest
{
// 처치해야할 몬스터
// Monster 타입이면 더 좋을듯
private List<string> monsterNameList;
private List<int> killCountList;
// 프로퍼티
public List<string> MonsterNameList => monsterNameList;
public List<int> KillCountList => killCountList;
public MonsterKillQuest(string name, string tooltip,string perForm , int rewardGold) :
base(name, tooltip , perForm , rewardGold)
{
// Func에 연결
completeQuest += TodoMission;
}
public void AddtoKillMonsterList(string name , int cnt )
{
// 코드생략
}
public override bool TodoMission()
{
bool flag = true;
// 지금까지 처치한 몬스터 수
for (int i = 0; i < monsterNameList.Count; i++)
{
string nowMonster = monsterNameList[i];
int countToKill = killCountList[i];
// 잡은횟수
int killCnt = DungeonManager.Instance.monsterCatches[nowMonster];
// 처치한 몬스터가 kill count보다 낮으면 -> 실패
if (killCnt < countToKill)
{
flag = false;
break;
}
}
// for문안의 조건문에 걸리지않으면 -> killcount대로 다 잡은것
return flag;
}
public override string QuestProgress()
{
// 코드생략
}
}
□ MonsterKillQuest 클래스
■ 자세한 설명은 상단의 클래스다이어그램 참고
■ ✨ TodoMission() 메서드 오버라이딩 ✨
□ 퀘스트 완료 조건 구현
□ '현재 잡은 몬스터 수'가 '잡아야 하는 몬스터 수'보다 작으면 false를 return, 완료하면 true를 return
■ ✨ 생성자 ✨
□ Func<bool> completeQuest 에 구현한 TodoMission() 메서드를 추가
- QuestManger 클래스
- 필드
private Quest currQuest; // 현재 퀘스트 저장
private List<Quest> performableQuests; // 수행가능 퀘스트
□ currQuest
■ 현재 퀘스트 저장
□ List<Quest> performableQuests
■ 수행가능한 퀘스트 컨테이너
■ 화면에 출력 할 때 해당리스트에 접근해서 퀘스트 이름을 출력
- Enter() 메서드
public void Enter()
{
Console.Clear();
// 수행가능한 퀘스트 이름만 빼서 배열로 저장 (LINQ)
// 코드 생략
// 목록(리스트) 출력
// 코드 생략
// player input
// 코드 생략
// 1. 현재 퀘스트
currQuest = performableQuests[input - 1];
// 2. 완료여부에 따라 state변화
currQuest.CheckState();
// 퀘스트 print
// 코드 생략
// 성공여부에 따라 print 다름
PrintRewardBystate();
}
1. 플레이어에 입력에 맞게 Quest 타입의 currQuest를 저장한다.
2. currQuest의 상태변환 메서드를 실행한다
■ 자세한 설명은 Quest 클래스의 CheskState() 메서드 참고
- PrintRewardBystate() 메서드
private void PrintRewardBystate()
{
switch (currQuest.QuestState)
{
case QuestState.beforeReceive:
// 코드생략
break;
case QuestState.afterReceive:
// 코드생략
break;
case QuestState.complete:
RemoveListAndAddToChild();
break;
}
}
1. 현재 퀘스트의 상태에 따라 다른 메서드를 실행한다.
2. 퀘스트가 complete 상태이면 (완료했으면) RemoveListAndAddToChild() 메서드를 실행한다.
- RemoveAndAddToChild() 메서드
private void RemoveListAndAddToChild()
{
// 현재 퀘스트의 state를 done으로
currQuest.ChangeState(QuestState.done);
// currQuest와 같은 quest 반환
var temp = performableQuests.Find(quest => quest.Equals(currQuest));
// 수행가능 리스트에서 삭제
if (temp != null)
{
performableQuests.Remove(temp);
}
// 현재 퀘스트의 child리스트에 접근해서 가능한 퀘스트리스트에 넣어야 함
for (int i = 0; i < currQuest.ChildQuest.Count; i++)
{
performableQuests.Add(currQuest.ChildQuest[i]);
}
}
1. 현재 퀘스트의 상태를 QuestState.done으로 변경한다.
2. 수행가능한 퀘스트를 담아놓는 리스트에서 현재 퀘스트를 remove 한다.
3. 현재 퀘스트의 List<Quest>에 접근해서 수행가능한 퀘스트 리스트(perforambleQuest)에 추가한다.
📝 동작 영상
📝 마무리
: 현재는 수락불가능한 퀘스트가 출력되고 있지는 않다.
유니티 개인 프로젝트를 할 때 수락 불가능한 퀘스트도 출력해서 퀘스트의 부모 퀘스트 추적 기능까지 추가해 보겠다.
📝 부모퀘스트 추적
(추가예정입니다)
TextRpg 팀 프로젝트 깃허브 주소 :
https://github.com/kimYouChae/Window11_TextRPG
GitHub - kimYouChae/Window11_TextRPG
Contribute to kimYouChae/Window11_TextRPG development by creating an account on GitHub.
github.com
'c#' 카테고리의 다른 글
[c#] LINQ 이해하기 (0) | 2025.02.11 |
---|---|
[c#]delegate 이해하기 (1) | 2025.02.10 |
[c#][TextRPG] 3. c#의 System.IO를 이용한 파일 시스템 작업 (0) | 2025.02.07 |
[c#][TextRPG] 2. NewtonJson으로 직렬화&역직렬화 (1) | 2025.02.06 |
[c#][TextRPG] 1. Interface를 활용한 Scene관리 / 싱글톤 (0) | 2025.02.05 |