오늘은 게임 저장 기능을 구현하였다.
개인 과제 때 게임 저장 기능을 구현한 경험이 있어 참고하여 구현했다.
C# 데이터 저장
C# 게임 저장- 앱을 껏다 켜도 사용되던 정보 유지- 파일 저장 & 파일 불러오기 Newtonsoft.Json을 사용하여 구현하였다. Newtonsoft.Json 설치 방법프로젝트 -> NuGet 패키지 관리 -> 찾아보기 -> Newtonsoft.Json
jeongeunji1127.tistory.com
위의 링크에 정리된 것과 동일하게 SaveData(), LoadData(), ResetData() 함수를 만들어 데이터를 저장했다.
SaveData()
public void SaveData()
{
string userData = JsonConvert.SerializeObject(Manager.Instance.gameManager.user);
File.WriteAllText(path + "\\UserData.json", userData);
string inventoryData = JsonConvert.SerializeObject(Manager.Instance.inventoryManager.items);
File.WriteAllText(path + "\\UserInventoryData.json", inventoryData);
string storeData = JsonConvert.SerializeObject(Manager.Instance.shopManager.products);
File.WriteAllText(path + "\\StoreItemData.json", storeData);
string questData = JsonConvert.SerializeObject(Manager.Instance.questManager.quests);
File.WriteAllText(path + "\\QuestData.json", questData);
}
LoadData()
public void LoadData()
{
// 유저 데이터가 없을 때 -> 세이브 데이터가 없을 때
if (!File.Exists(path + "\\UserData.json"))
{
Manager.Instance.userDataManager.SetName();
Manager.Instance.gameManager.Init();
SaveData();
Manager.Instance.gameManager.village.ShowVillage();
return;
}
else
{
// 유저 데이터 Load
string userLData = File.ReadAllText(path + "\\UserData.json");
User userLoadData = JsonConvert.DeserializeObject<User>(userLData);
Manager.Instance.gameManager.user = userLoadData;
// 인벤토리 데이터 Load
string inventoryLData = File.ReadAllText(path + "\\UserInventoryData.json");
List<Item> inventoryLoadData = JsonConvert.DeserializeObject<List<Item>>(inventoryLData);
if (Manager.Instance.inventoryManager.items != null)
{
Manager.Instance.inventoryManager.ClearInventory();
}
foreach (Item item in inventoryLoadData)
{
if (item != null)
{
Manager.Instance.inventoryManager.items.Add(item);
}
}
//상점 데이터 Load
string storeLData = File.ReadAllText(path + "\\StoreItemData.json");
List<ShopProduct> storeLoadData = JsonConvert.DeserializeObject<List<ShopProduct>>(storeLData);
if (Manager.Instance.shopManager.products != null)
{
Manager.Instance.shopManager.ClearShop();
}
foreach (ShopProduct shopitem in storeLoadData)
{
if (shopitem != null)
{
Manager.Instance.shopManager.AddProduct(shopitem);
}
}
//퀘스트 데이터 Load
string questLData = File.ReadAllText(path + "\\QuestData.json");
List<Quest> questLoadData = JsonConvert.DeserializeObject<List<Quest>>(questLData);
if (Manager.Instance.questManager.quests != null)
{
Manager.Instance.questManager.ResetQuest();
}
foreach (Quest quest in questLoadData)
{
Manager.Instance.questManager.AddQuest(quest);
}
}
}
발생한 문제점
1. 상점 데이터 LoadData() 도중 NullReference 오류 발생 이슈
- 상점 데이터 저장 구현 도중 Json파일을 Load하지 못하는 이슈가 발생
- 초기 ShopProduct 클래스는 아래와 같았다.
public class ShopProduct : Item
{
public int Price;
public bool IsBuy;
public ShopProduct(Item item,int price,bool isbuy = false) : base(item.ItemName, item.ItemStat, item.Itemtype, item.ItemDescription)
{
Price = price;
IsBuy = isbuy;
}
}
string storeData = JsonConvert.SerializeObject(Manager.Instance.shopManager.products);
File.WriteAllText(path + "\\StoreItemData.json", storeData);
ShopProduct 객체를 위의 코드를 통해 Json 파일로 변환하면

이런 형태로 저장된다.이 Json파일을 ShopProduct 형식으로 그냥 역직렬화시키면 오류가 발생한다.
=> Json에 있는 ItemName와 같은 변수가 ShopProduct 생성자에 없기 때문에 발생한 것.
해결하기 위해 foreach문을 돌며 new Item(shopitem.ItemName, shopitem.ItemDescription ...)과 같이 아이템 객체를 만들어 ShopProduct 형태를 구현해 리스트에 추가해보았으나 이 방법으로는 해결되지 않았다.
foreach (ShopProduct i in storeLoadData)
{
// 이런식으로 적었던 것 같다
ShopProduct shopitem = new ShopProduct(new Item(i.ItemName,i.ItemDescription...),i.price, i.isbuy);
Manager.Instance.shopManager.AddProduct(shopitem);
}
결국 Item 클래스 상속을 풀고 매개변수를 일일히 받아 해결하였다.
클래스 상속으로 구현된 내용이 없으므로 빠르게 수정이 가능했다.
2. SaveData() 함수 위치로 인해 상점 데이터가 계속 리셋되는 이슈
- 데이터를 LoadData()하기 전에 SaveData() 함수가 먼저 실행되어 데이터가 초기화 되는 이슈
- 상점 데이터 로드 코드 윗 부분의 인벤토리 데이터 로드 코드에서 아래 로직이 실행된다.
Manager.Instance.shopManager.AddProduct(shopitem);
상점 데이터가 들어올 때마다 데이터를 저장해야한다 생각해
AddProduct() 함수에 SaveData()함수를 넣어 두었던 것이 이슈의 원인!
모든 상점 아이템이 다 들어온 후,
AddProduct() 함수 내부가 아닌 외부 로직에서 SaveData()를 실행하여도 같은 결과 값이 나오므로
외부 로직에 SaveData()함수를 옮기는 것으로 수정하여 해결하였다.
- 튜터님께 도움을 받아 문제가 발생한 부분을 찾았다.
- 디버그를 찍으며 버그를 수정하는 경험이 중요하므로 연습이 많이 필요하다고 조언해주셨다.
3. 퀘스트 데이터 저장 과정에서 데이터를 JsonConvert.DeserializeObject 해오지 못하는 이슈
- 가장 많은 시간을 썼던 오류였다..
- 위의 유저 정보, 인벤토리, 상점 데이터 저장 구현을 완료한 후라
데이터 저장 부분은 마스터 했다고 생각하고 구현을 시작했다 ㅋ 그런데..
데이터가 정상적으로 저장은 되는데 저장된 값이 아닌 초기 값만을 불러오기 시작했다.
오류도 발생하지 않았다.
2번 이슈를 해결했던 경험을 토대로 차근차근 디버그를 찍어보며 값을 확인해보니
문제가 된 부분을 찾을 수 있었다.
string questLData = File.ReadAllText(path + "\\QuestData.json");
List<Quest> questLoadData = JsonConvert.DeserializeObject<List<Quest>>(questLData);
questLData 변수에는 저장된 데이터 값이 잘 불러와지는데
이 변수가 questLoadData List<Quest>형식에 역직렬화 되지 않고 있었다.
나의 접근
1. 팀원분이 구현하신 코드를 보고 가장 먼저 string 배열로 선언되어 있는 Rewards 변수를 의심했다.
'1번 이슈 같이 배열도 데이터를 역직렬화 하는 과정에서 별도의 처리가 필요한가???'
-> 아니었다
2. Quest의 생성자 매개변수들과 Json 데이터 변수들의 이름이 달라 데이터를 읽어오지 못하는 오류?
-> 아니었다
3. 이외에도 여러 방법을 시도해보았으나 모두 답이 아니었다.
원인은 Quest.cs 자체에 있었다.
팀원분이 구현하신 코드를 천천히 살펴보니 bool 변수들에 문제가 있었다.
생성자의 매개변수와 클래스의 필드 이름이 동일했던 것이다.
생성자에서 매개변수들이 클래스의 필드에 할당되지 않고
생성자 내에서 선언된 로컬 변수들에 할당되는 것이 문제점이었다.
아래와 같이 작성되어 있어 생성자의 필드 할당은 실제로 아무런 효과가 없게 되었던 것이다.
public class Quest
{
public string Name { get; set; }
public string Description { get; set; }
public int TargetCount { get; set; }
public int CurrentCount { get; set; }
public bool IsAccepted { get; set; }
public bool IsCompleted { get; set; }
public bool IsWaiting { get; set; }
public string[] Rewards { get; set; }
public Quest(string name, string description, int targetCount, bool IsAccepted, bool IsCompleted, bool IsWaiting, string[] rewards)
{
Name = name;
Description = description;
TargetCount = targetCount;
CurrentCount = 0;
IsCompleted = false;
IsAccepted = false;
IsWaiting = false;
Rewards = rewards;
}
}
아래와 같이 수정하니
Json파일에서 bool 값들을 아주 아주 잘 불러왔다 흑
public class Quest
{
public string Name { get; set; }
public string Description { get; set; }
public int TargetCount { get; set; }
public int CurrentCount { get; set; }
public bool IsAccepted { get; set; }
public bool IsCompleted { get; set; }
public bool IsWaiting { get; set; }
public List<string> Rewards { get; set; }
public Quest(string name, string description, int targetCount, bool isAccepted, bool isCompleted, bool isWaiting, List<string> rewards)
{
Name = name;
Description = description;
TargetCount = targetCount;
CurrentCount = 0;
IsCompleted = isCompleted;
IsAccepted = isAccepted;
IsWaiting = isWaiting;
Rewards = rewards;
}
}
구현 완료..
'게임 개발' 카테고리의 다른 글
[Unity] BrickOutGame - 오브젝트 풀링 (1) (0) | 2024.05.16 |
---|---|
Text Dungeon - 세나몬 잡기 (5) (0) | 2024.05.07 |
Text Dungeon - 세나몬 잡기 (3) (0) | 2024.05.02 |
Text Dungeon - 세나몬 잡기 (2) (0) | 2024.05.01 |
Text Dungeon - 세나몬 잡기 (0) | 2024.04.30 |