일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 백준
- anonymous page
- 네트워크
- 추상클래스와인터페이스
- 알고리즘수업-너비우선탐색2
- 크래프톤 정글 4기
- 크래프톤 정글
- 크래프톤정글
- Unity
- 연결리스트
- 티스토리챌린지
- 파이썬
- 유니티
- 다익스트라
- 이벤트 함수 실행 순서
- 알고리즘
- project3
- c#
- KRAFTON JUNGLE
- 오블완
- 4기
- C
- kraftonjungle
- pintos
- User Stack
- BFS
- 크래프톤정글4기
- 전쟁-전투
- 핀토스
- TiL
- Today
- Total
말감로그
[Unity] Valley - 식물 데이터 저장/로드 (새로하기 & 이어하기) 본문
어제 인벤토리 저장/로드를 구현했기 때문에 비슷한 방식으로 식물 데이터도 저장/로드하면 되겠다고 생각했다.
인벤토리의 경우에는 Inventory 클래스 내부에 정의된 Slot 중첩 클래스에 데이터들이 ItemData에 담긴 데이터와 똑같기 때문에 저장된 데이터를 인벤토리에 추가해주기만 하면 됐었다.
그러나 식물의 경우, PlantData엔 식물의 이름, 프리팹, 성장 단계에 맞는 타일 배열, 성장 시간 배열 등 정말 그 식물 자체의 데이터들만 담겨있다. 그래서 씨를 뿌린 식물의 위치, 성장 단계, 성장 시간, 타일의 상태 등 개별 인스턴스 정보를 저장하기 위해서 PlantSaveData라는 클래스를 따로 만들어 식물 데이터(PlantData) 뿐만 아니라 내가 씨를 뿌린 그 식물에 현재 상태도 저장하도록 했다.
public class PlantSaveData
{
public PlantData plantData;
public Vector3Int position;
public int growthStage;
public int growthDay;
public string currentState;
public PlantSaveData(PlantData plantData, Vector3Int position, int growthStage, int growthDay, string currentState)
{
this.plantData = plantData;
this.position = position;
this.growthStage = growthStage;
this.growthDay = growthDay;
this.currentState = currentState;
}
}
SaveData 스크립트에선 인벤토리와 마찬가지로, 모든 PlantData를 담을 딕셔너리를 생성해 PlantData 객체들을 불러와서 해시 값으로 저장하도록 했다.
public class SaveData : MonoBehaviour
{
public static SaveData instance;
public Inventory inventoryToSave = null; // 저장할 인벤토리
private static Dictionary<int, ItemData> allItem = new Dictionary<int, ItemData>();
private static int HashItem(ItemData item) => Animator.StringToHash(item.itemName); // 아이템의 이름을 해시 값으로 변환해 고유한 키로 사용
private const char SPLIT_CHAR = '_';
private static Dictionary<int, PlantData> allPlant = new Dictionary<int, PlantData>();
private static int HashItem(PlantData plant) => Animator.StringToHash(plant.plantName);
void Awake()
{
if (!instance)
{
instance = this;
}
CreateItemDictionary();
CreatePlantDataDictionary();
}
// Resources 폴더에서 ItemData 객체들을 불러와서 allItem 딕셔너리에 해시 값으로 저장
private void CreateItemDictionary()
{
ItemData[] allItems = Resources.FindObjectsOfTypeAll<ItemData>();
foreach (ItemData i in allItems)
{
int key = HashItem(i);
if (!allItem.ContainsKey(key))
allItem.Add(key, i);
}
}
private void CreatePlantDataDictionary()
{
PlantData[] allPlants = Resources.FindObjectsOfTypeAll<PlantData>();
foreach (PlantData i in allPlants)
{
int key = HashItem(i);
if (!allPlant.ContainsKey(key))
allPlant.Add(key, i);
}
}
저장하는 함수도 인벤토리와 똑같이 만들어줬다.
plantSaveDataList를 인자로 받아 데이터들을 파일에 써주도록 한다.
/* ---- Plant Save & Load ----*/
public void SavePlants(List<PlantSaveData> plantSaveDataList)
{
string filePath = Application.persistentDataPath + $"/plants.txt";
using (StreamWriter sw = new StreamWriter(filePath, false))
{
sw.WriteLine($"-- PlantData --");
foreach (var plant in plantSaveDataList)
{
sw.WriteLine($"{plant.plantData.plantName}{SPLIT_CHAR}{plant.position.x}{SPLIT_CHAR}{plant.position.y}{SPLIT_CHAR}{plant.position.z}{SPLIT_CHAR}
{plant.growthStage}{SPLIT_CHAR}{plant.growthDay}{SPLIT_CHAR}{plant.currentState}");
}
}
Debug.Log($"PlantData saved!");
}
인벤토리의 경우 해당 인벤토리의 데이터를 로드해와서 Add() 함수를 사용하여 기존 인스턴스에 데이터를 채우기만 하면 되었지만, Plant의 경우에는 로드한 데이터를 새롭게 리스트로 반환하여 PlantGrowthManager에서 사용하도록 한다.
public List<PlantSaveData> LoadPlants()
{
string filePath = Application.persistentDataPath + $"/plants.txt";
List<PlantSaveData> plantSaveDataList = new List<PlantSaveData>();
if (File.Exists(filePath))
{
using (StreamReader sr = new StreamReader(filePath))
{
string line;
while ((line = sr.ReadLine()) != null)
{
string[] data = line.Split(SPLIT_CHAR);
if (data.Length == 7)
{
string plantName = data[0];
Vector3Int position = new Vector3Int(int.Parse(data[1]), int.Parse(data[2]), int.Parse(data[3]));
int growthStage = int.Parse(data[4]);
int growthDay = int.Parse(data[5]);
string currentState = data[6];
int key = Animator.StringToHash(plantName);
PlantData plantData;
if (allPlant.TryGetValue(key, out plantData))
{
PlantSaveData saveData = new PlantSaveData(plantData, position, growthStage, growthDay, currentState);
plantSaveDataList.Add(saveData);
}
}
}
}
}
else
{
Debug.LogWarning("No saved plant data found.");
}
return plantSaveDataList;
}
이제 PlantGrowthManager 스크립트로 가서 씨앗을 심을 때마다 해당 위치와 그 식물의 plantData를 저장하는 plantDataDict 딕셔너리를 순회하여, 위치별로 식물의 정보를 가져온다.
그리고 가져온 위치를 PlantSaveDataList에 추가하여, 개별 식물의 위치와 현재 상태 정보를 저장한다.
그리고 그 리스트를 위에서 만들었던 SaveData.cs의 SavePlants에 인자로 전달한다.
//PlantGrowthManager.cs
public void SavePlantDataList()
{
List<PlantSaveData> plantSaveDataList = new List<PlantSaveData>();
foreach (var position in plantDataDict.Keys)
{
PlantData plantData = plantDataDict[position];
int growthStage = currentGrowthStages[position];
int growhDay = plantGrowthDays[position];
string currentState = GameManager.instance.tileManager.GetTileState(position);
plantSaveDataList.Add(new PlantSaveData(plantData, position, growthStage, growhDay, currentState));
}
SaveData.instance.SavePlants(plantSaveDataList);
}
그러면 Save 함수가 실행되면서 plants.txt 파일이 생성되고 그 안에 PlantData가 저장된다.
이제 저장을 했으니 로드를 할 차례이다.
파일에 저장된 데이터를 List<PlantSaveData>로 반환하여 그 리스트를 받는다. 하지만 받는걸로 그치면 안된다.
왜냐하면 Tile에 리스트에 저장된 식물의 타일을 그려줘야 하기 때문이다.
그래서 받아온 리스트를 SetTile 해주는 함수를 만들어줬다.
public void LoadPlantsData()
{
List<PlantSaveData> plantSaveDataList = SaveData.instance.LoadPlants();
SetTilePlantSaveData(plantSaveDataList);
}
public void SetTilePlantSaveData(List<PlantSaveData> plantSaveDataList)
{
foreach (var saveData in plantSaveDataList)
{
Vector3Int position = saveData.position;
PlantData plantData = saveData.plantData;
int currentGrowthStage = saveData.growthStage;
int currentGrowthDay = saveData.growthDay;
string currentState = saveData.currentState;
if (GameManager.instance.tileManager == null)
Debug.Log("no tileManager ");
if (GameManager.instance.tileManager.seedMap == null)
Debug.Log(" no seedMap: ");
GameManager.instance.tileManager.SetTileState(position, currentState);
GameManager.instance.tileManager.interactableMap.SetTile(position, GameManager.instance.tileManager.interactedTile);
GameManager.instance.tileManager.seedMap.SetTile(position, plantData.growthStagesTiles[currentGrowthStage]);
plantDataDict[position] = plantData;
plantGrowthDays[position] = currentGrowthDay;
currentGrowthStages[position] = currentGrowthStage;
}
}
각 리스트 별 데이터를 받아서 타일의 상태를 설정해주고, interactableMap 타일을 다시 설정하고, seedMap에 해당 식물의 성장 단계에 맞는 타일을 세팅해주었다.
'TIL' 카테고리의 다른 글
[Unity] Valley - Animated Tile (0) | 2024.11.15 |
---|---|
[Unity] Valley - 식물 로드 버그 수정 (0) | 2024.11.14 |
[Unity] Valley - 인벤토리 저장/로드 (새로하기 &이어하기) (3) | 2024.11.13 |
[Unity] Valley - 인벤토리 저장 & 로드 시스템 (2) | 2024.11.12 |
[Unity] Valley - 판매 가능한 아이템 설정 (0) | 2024.10.30 |