유니티 부트캠프 8기/Ch03. Mini Project2(250206 - 250213)

Quest 구현하기

Imperor 2025. 2. 13. 21:13

우선 퀘스트는 3개를 만들었다

 

        // json에 퀘스트 내용 저장하기
        private void SaveDefaultQuests(Npc npc)
        {
            List<object> defaultQuests = new List<object>()
            {
                /// 추가할 퀘스트가 있다면 여기에 기록
                (object) new QuestData { Name = "고블린 5마리 잡기", QuestType = eQuestType.KILL, State = eQuestState.NOTSTARTED, Description = "던전의 고블린을 5마리 처치하세요.", Target = "고블린", Count = 0, IsCleared = false, Ext = 50, Gold = 100, rewardEquipment = null, requiredQuest = "" },
                (object) new QuestData { Name = "소량의 체력 포션 3개 모으기", QuestType = eQuestType.COLLECT, State = eQuestState.NOTSTARTED, Description = "약초상에게 줄 포션 3개를 모아 오세요.", Target = "소량의 체력 포션", Count = 0, IsCleared = false, Ext = 30, Gold = 50, rewardEquipment = null, requiredQuest = "" },
                (object) new QuestData {Name = "드래곤 잡기", QuestType = eQuestType.KILL, State = eQuestState.NOTSTARTED, Description = "마을을 위협하는 드래곤을 처치하세요.", Target = "드래곤", Count = 0, IsCleared = false, Ext = 500, Gold = 1000, rewardEquipment = null, requiredQuest = "고블린 5마리 잡기"}
            };
            string jsonStr = JsonConvert.SerializeObject(defaultQuests, Formatting.Indented, new JsonSerializerSettings
            {
                // 혹시 모를 null 대비
                NullValueHandling = NullValueHandling.Include,  // null값도 불러온다
                MissingMemberHandling = MissingMemberHandling.Ignore,

                TypeNameHandling = TypeNameHandling.All  // 다형성 정보 포함하여 직렬화
            });
            // quest.json파일에 저장
            File.WriteAllText(questFilePath, jsonStr);
            Console.WriteLine("기본 퀘스트 데이터 저장 완료!");
            Thread.Sleep(1000);
        }

 

기본 파일을 json으로 저장한다음 읽을건데

언제 저장하느냐가 중요하다

기본파일의 퀘스트 정보(아무것도 클리어하지 않음)와

게임 도중 저장한 플레이어의 퀘스트 정보는 다르기 때문이다

 

저장된 quest.json파일이 없으면 SaveDefault를 호출해서 quest.json 파일을 만들고

바로 LoadQuestData 메서드를 호출해서 

npc의 questList에 추가한다(모든 퀘스트는 npc가  가지고 있어야 하기 때문)

        // 퀘스트 내용 불러와서 npc에 대입한다
        // 저장파일에서 불러온 퀘스트 진행상황, 완료한 퀘스트 처리는 DataManager에서 한다
        private void LoadQuestData(Npc npc)
        {
            // 호출하는 곳에서 파일경로 체크를 했으니 여기서는 하지 않는다
            try
            {
                string jsonStr = File.ReadAllText(questFilePath);
                List<object>? loadedObjs;
                if (string.IsNullOrEmpty(jsonStr))
                {
                    // 빈 파일 처리
                    loadedObjs = new List<object>();
                    return;
                }
                loadedObjs = JsonConvert.DeserializeObject<List<object>>(jsonStr, new JsonSerializerSettings
                {
                    // 혹시 모를 null 대비
                    NullValueHandling = NullValueHandling.Include,  // null값도 불러온다
                    MissingMemberHandling = MissingMemberHandling.Ignore,

                    TypeNameHandling = TypeNameHandling.All  // 다형성 처리
                });
                if (loadedObjs != null)
                {
                    foreach (var obj in loadedObjs)
                    {
                        if (obj is QuestData questData)
                        {
                            npc.questList[questData.Name] = new Quest(questData);
                        }
                    }
                }
                Console.WriteLine($"기본 퀘스트 데이터 {loadedObjs.Count}개 불러오기 완료!");
                Thread.Sleep(1000);
                //
                Console.Clear();
                Console.SetCursorPosition(0, 0); /// 커서를 왼쪽 맨 위로 이동
            //
            }
            catch (Exception ex)
            {
                // 예외 처리 로직
                Console.WriteLine($"Error loading quest data: {ex.Message}");
            }
        }

 

 

 

기본파일은 플레이어가 생성되기 전 EntryScene의 시작부분에 저장하기로 했다

저장위치는 Json폴더의 quest,json파일

    public class EntryScene : BaseScene
    {
        Player player;
        PlayerData data;

        #region 새로운 생성자 만들기 금지
        // 생성자에서는 현재 씬의 이름만 설정한다. 씬에 있는 멤버들의 초기화는 Awake나 Start에서 한다
        public EntryScene(string name) : base(name) { }
        #endregion
        //
        public override void Awake()
        {
            /// 퀘스트정보를 여기서 초기화하는 이유
            /// 1. Town에서 Entry로 올 때 기존의 퀘스트 진행도가 남아있는 문제가 있었다
            /// GameProcess의 Awake는 두번다시 실행하지 않기 때문에
            /// 새로운 퀘스트데이터를 여기서 만들어야 한다
            /// 
            /// 2. entryScene은 saveLoadScene보다 먼저 진입하기 때문이다
            /// 데이터를 불러오기전에 이미 npc가 생성이 되어 있어야한다
            /// 그래야 데이터의 퀘스트정보를 npc의 퀘스트정보에 대입할 수 있다
            QuestManager.Instance.Init();
        }
...

 

 

모든 퀘스트는 

    public class Npc
    {
        // Npc가 가지고 있는 고유한 퀘스트를 저장
        public Dictionary<string, Quest> questList;

퀘스트의 questList가 가지고 있다

언제 저장하냐면

 

그러다 플레이어는 저장을 하고

SaveFiles의 saveFile_저장슬롯의번호.json 으로 저장된다

파일의 위치는 겹치지 않으니 괜찮디

 

플레이어가 저장할 때 호출되어 저장되니까 문제되지 않는다

 

저장할때 DataManager의 SaveData 메서드에서 해결

        public void SaveData(int fileIdx)
        {
            // 파일, 슬롯의 인덱스를 받고, 저장한다
            /// 아이템 매니저의 데이터 복사
            GameData tmpData = new GameData(fileIdx, player, ItemManager.Instance.ownedList, ItemManager.Instance.armedList, SkillManager.Instance.playerSkillList,
                               QuestManager.Instance.completedQuestDictionary, QuestManager.Instance.ongoingQuestDictionary);

            string jsonStr = JsonConvert.SerializeObject(tmpData, new JsonSerializerSettings
            {
                NullValueHandling = NullValueHandling.Include,      // null값도 json에 저장
                MissingMemberHandling = MissingMemberHandling.Ignore,  // JSON 데이터에 C# 객체에 정의되지 않은 속성이 있을 때, 이를 무시하고 계속 진행할 수 있습니다 

                TypeNameHandling = TypeNameHandling.All // 추상클래스인경우를 대비하여 실제 자료형을 기록한다
            });            // SaveFiles에 세이브파일생성
            File.WriteAllText(GetFilePath(fileIdx), jsonStr);
        }

 

 

그 파일을 불러오는 것도

SaveLoad씬에서 불러오기를 선택하면 

DataManager의 LoadData 메서드를 사용하여 읽어온다

        // 특정 슬롯에 연결된 세이브파일을 불러온다
        public bool LoadData(int fileIdx)
        {
            /// LoadData는 saveLoadScene에서 호출이 되는 메서드이므로
            /// entryScene을 이미 지났기 때문에 npc는 생성되어 있는 상태다
            /// 즉, 여기서 불러온 퀘스트 완료정보를 npc의 퀘스트 완료정보에 대입할 수 있다
            /// (참조오류가 발생하여 직접 대입해서 반영해줘야한다)

            ClearCollections();

            player = new Player();
            GameData gameData = new GameData();
            string path = GetFilePath(fileIdx);
            if (File.Exists(path))
            {
                string jsonData = File.ReadAllText(path);
                var settings = new JsonSerializerSettings
                {
                    NullValueHandling = NullValueHandling.Include,  // null값도 불러온다
                    MissingMemberHandling = MissingMemberHandling.Ignore, // JSON 데이터에 C# 객체에 정의되지 않은 속성이 있을 때, 이를 무시하고 계속 진행할 수 있습니다 

                    TypeNameHandling = TypeNameHandling.All
                };
                gameData = JsonConvert.DeserializeObject<GameData>(jsonData, settings);
                // 데이터매니저의 플레이어에 데이터 저장
                player.data = gameData.player.data;
                player.weapon = gameData.player.weapon;
                player.armor = gameData.player.armor;

                /// 아이템들이 object형식이므로 다시 원래의 자료형으로 바꿔준 다음, 아이템매니저의 리스트에 복사
                /// 나중에 LINQ 공부하고 나서 수정해보자
                foreach (var item in gameData.ownedList)
                {
                    if (item is Armor armor)
                    {
                        ItemManager.Instance.ownedList.Add(armor);
                    }
                    else if (item is Weapon weapon)
                    {
                        ItemManager.Instance.ownedList.Add(weapon);
                    }
                }
                foreach (var item in gameData.armedList)
                {
                    if (item is Armor armor)
                    {
                        ItemManager.Instance.armedList.Add(armor);
                    }
                    else if (item is Weapon weapon)
                    {
                        ItemManager.Instance.armedList.Add(weapon);
                    }
                }
                player.weapon = gameData.player.weapon;
                player.armor = gameData.player.armor;

                // 퀘스트를 Dictionary에 추가
                foreach (var pair in gameData.completedQuestDictionary)
                {
                    try
                    {
                        // 명시적으로 Quest 형변환
                        Quest qst = (Quest)pair.Value;
                        QuestManager.Instance.completedQuestDictionary.Add(pair.Key, qst);

                        /// npc의 해당 퀘스트의 클리어정보를 바꿔야한다
                        if (QuestManager.Instance.npc.questList.ContainsKey(pair.Key))
                        {
                            QuestManager.Instance.npc.questList[pair.Key].questData.IsCleared = true;
                        }
                    }
                    catch
                    {
                        Console.WriteLine($"{pair.Value}를 Quest로 형변환하지 못했습니다");
                    }
                }
                foreach (var pair in gameData.ongoingQuestDictionary)
                {
                    try
                    {
                        // 명시적으로 Quest 형변환
                        Quest qst = (Quest)pair.Value;
                        QuestManager.Instance.ongoingQuestDictionary.Add(pair.Key, qst);
                    }
                    catch
                    {
                        Console.WriteLine($"{pair.Value}를 Quest로 형변환하지 못했습니다");
                    }
                }
                return true;
            }
            return false;
        }

 

저장할때 여기에 스킬정보도 포함이 될 것이다