일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- TiL
- 알고리즘수업-너비우선탐색2
- 파이썬
- User Stack
- 크래프톤 정글 4기
- 이벤트 함수 실행 순서
- KRAFTON JUNGLE
- kraftonjungle
- 네트워크
- pintos
- 오블완
- C
- c#
- 핀토스
- Unity
- 티스토리챌린지
- 유니티
- 연결리스트
- BFS
- 전쟁-전투
- project3
- 백준
- 크래프톤 정글
- 다익스트라
- 크래프톤정글4기
- 알고리즘
- 추상클래스와인터페이스
- 4기
- 크래프톤정글
- anonymous page
- Today
- Total
말감로그
[Unity] Valley - 전체적인 코드 수정 & 회고 본문
그동안 작성했던 코드들에 대해서 버그나 기능을 추가하거나 리팩토링하면서 전체적인 코드를 수정했다.
1. 저장 후 로드 시 인벤토리에 있는 씨앗을 심을 때 성장이 안되는 문제
인벤토리에 아이템을 추가하고 저장 후 로드할 때 다른 아이템들은 잘 사용이 되지만, 씨앗 아이템들은 사용을 해도 성장이 되지 않는 문제가 있었다.
그 이유를 찾아보니 식물 성장 시스템을 위해선 해당 아이템의 PlantData가 필요했지만, 저장 할 때 아이템의 데이터(ItemData)만 저장을 하고, PlantData는 저장하지 않아서 텅 비어있는 데이터로 인해 식물 성장 시스템이 작동하지 않았던 것이었다.
그래서 InventorySlotData에 plantName을 저장해서 해당 plantName으로 해싱하여 PlantData를 찾아서 로드할 때 추가해주기로 했다.
[System.Serializable]
public class InventorySlotData
{
public string itemName;
public int currentCount;
public string plantName;
}
//SaveInventory 메서드
foreach (var slot in inventoryToSave.GetSlots)
{
if (!slot.isEmpty)
{
inventorySaveData.slots.Add(new InventorySlotData
{
itemName = slot.itemName,
currentCount = slot.currentCount
currentCount = slot.currentCount,
plantName = slot.item?.plantData?.plantName
});
}
}
//LoadInventory 메서드
if (allItem.TryGetValue(key, out var itemData))
{
PlantData plantData = null;
// 해당 item의 plantData 가져오기
if (!string.IsNullOrEmpty(slotData.plantName))
{
int plantKey = Animator.StringToHash(slotData.plantName);
allPlant.TryGetValue(plantKey, out plantData);
};
for (int i = 0; i < slotData.currentCount; i++)
{
Item item = new GameObject("Item").AddComponent<Item>();
item.itemData = itemData;
item.plantData = plantData;
currentInventory.Add(item);
}
}
2. 플레이어가 나무 뒤로 가면 나무의 알파값 조정
플레이어가 나무 뒤로 가면 나무에 가려져 보이지 않았지만, 나무 뒤에 콜라이더를 두어 해당 콜라이더에 trigger 할 시 나무의 알파 값을 조정해주도록 했다.
//Tree.cs
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
SetTreeAlpha(fadedAlpha);
}
}
void OnTriggerExit2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
SetTreeAlpha(originalColor.a);
}
}
private void SetTreeAlpha(float alpha)
{
Color color = spriteRenderer.color;
color.a = alpha;
spriteRenderer.color = color;
}
3. 텍스트 기반 저장 방식 , PlayerPrefs -> json 기반 저장 방식으로 변경
지금까지 식물, 인벤토리는 텍스트 기반, 플레이어의 정보는 PlayerPrefs로 저장/로드하고 있었다.
너무 막무가내이고, json으로 충분히 통일 시킬 수 있을 것 같아 전체 저장/로드 방식을 json으로 변경하였다.
json은 string, int, float, bool 형의 변수들을 관리할 수 있으므로 기존 PlantSaveData의 클래스에 있던 PlantData ScritableObject를 string형 plantName으로 변경하여 plantName을 해싱하여 해당 PlantData를 찾아주는 걸로 변경했다.
또한 식물 ,인벤토리, 플레이어의 정보를 한 곳에서 관리하기 위해 GameSaveData라는 클래스를 만들고,
각 식물과 인벤토리 데이터를 그룹화하여 하나의 객체로 생성하기 위해 해당 클래스를 감싸는 Wrapper 클래스를 생성해주었다.
[System.Serializable]
public class GameSaveData
{
public PlayerData playerData;
public InventoryData backpackData;
public InventoryData toolbarData;
public PlantDataWrapper plantData;
}
[System.Serializable]
public class PlayerData
{
public int money;
public int currentDay;
public int currentDayIndex;
public int sellingPrice;
public PlayerData(int money, int currentDay, int currentDayIndex, int sellingPrice)
{
this.money = money;
this.currentDay = currentDay;
this.currentDayIndex = currentDayIndex;
this.sellingPrice = sellingPrice;
}
}
[System.Serializable]
public class InventorySlotData
{
public string itemName;
public int currentCount;
public string plantName;
}
[System.Serializable]
public class InventoryData
{
public List<InventorySlotData> slots = new List<InventorySlotData>();
}
[System.Serializable]
public class PlantSaveData
{
public PlantData plantData;
public string plantName;
public Vector3Int position;
public int growthStage;
public int growthDay;
public string currentState;
public bool isWatered;
public PlantSaveData(string plantName, Vector3Int position, int growthStage, int growthDay, string currentState, bool isWatered)
{
this.plantName = plantName;
this.position = position;
this.growthStage = growthStage;
this.growthDay = growthDay;
this.currentState = currentState;
this.isWatered = isWatered;
}
}
[System.Serializable]
public class PlantDataWrapper
{
public List<PlantSaveData> plants = new List<PlantSaveData>();
}
SaveGameData 메서드의 매개변수로, backpack, toolbar, 플레이어 정보, plant 데이터를 받고,
GameData.json 파일에 JsonUtility.ToJson()을 통해 json으로 직렬화하여 WriteAllText()로 저장한다. .
public void SaveGameData(
Inventory backpack, Inventory toolbar,
int currentMoney, int currentDay, int currentDayIndex, int sellingPrice,
List<PlantSaveData> plant)
{
string filePath = Application.persistentDataPath + $"/GameData.json";
GameSaveData gameSaveData = new GameSaveData
{
playerData = new PlayerData(currentMoney, currentDay, currentDayIndex, sellingPrice),
backpackData = new InventoryData(),
toolbarData = new InventoryData(),
plantData = new PlantDataWrapper()
};
foreach (var slot in backpack.GetSlots)
{
if (!slot.isEmpty)
{
gameSaveData.backpackData.slots.Add(new InventorySlotData
{
itemName = slot.itemName,
currentCount = slot.currentCount,
plantName = slot.item?.plantData?.plantName
});
}
}
foreach (var slot in toolbar.GetSlots)
{
if (!slot.isEmpty)
{
gameSaveData.toolbarData.slots.Add(new InventorySlotData
{
itemName = slot.itemName,
currentCount = slot.currentCount,
plantName = slot.item?.plantData?.plantName
});
}
}
gameSaveData.plantData.plants = plant;
string json = JsonUtility.ToJson(gameSaveData, true);
File.WriteAllText(filePath, json);
}
이 메서드는 저장하기 버튼을 눌렀을 때 실행되어야 하기 때문에 각 값을 인자로 넘겨서 InGameUI.cs에서 불리도록 한다.
//InGameUI.cs
public void SaveAllData()
{
SaveData.instance.SaveGameData(
Player.Instance.inventoryManager.backpack,
Player.Instance.inventoryManager.toolbar,
Player.Instance.money,
GameManager.instance.timeManager.day,
GameManager.instance.timeManager.currentDayIndex,
GameManager.instance.itemBox.sellingPrice,
GameManager.instance.plantGrowthManager.SavePlantDataList()
);
ShowSaveNotification();
}
로드 메서드는 해당 filePath에 있는 파일을 읽어들여서
JsonUtility.FromJson<GameSaveData>(json) 을 통해 JSON 문자열을 지정한 타입(T)의 객체로 역직렬화한다.
public void LoadGameData()
{
string filePath = Application.persistentDataPath + "/GameData.json";
if (File.Exists(filePath))
{
string json = File.ReadAllText(filePath);
GameSaveData gameSaveData = JsonUtility.FromJson<GameSaveData>(json);
if (gameSaveData.playerData != null)
{
Player.Instance.money = gameSaveData.playerData.money;
GameManager.instance.timeManager.day = gameSaveData.playerData.currentDay;
GameManager.instance.timeManager.currentDayIndex = gameSaveData.playerData.currentDayIndex;
GameManager.instance.itemBox.sellingPrice = gameSaveData.playerData.sellingPrice;
if (gameSaveData.playerData.sellingPrice > 0)
{
GameManager.instance.itemBox.SellItems();
GameManager.instance.itemBox.ResetSellingPrice();
}
}
LoadInventoryFromData(InventoryManager.instance.backpack, gameSaveData.backpackData);
LoadInventoryFromData(InventoryManager.instance.toolbar, gameSaveData.toolbarData);
LoadPlantFromData(gameSaveData.plantData);
}
}
private void LoadPlantFromData(PlantDataWrapper plantsDataWrapper)
{
List<PlantSaveData> plantSaveDataList = new List<PlantSaveData>();
plantSaveDataList.Clear();
foreach (var plantsData in plantsDataWrapper.plants)
{
int plantKey = Animator.StringToHash(plantsData.plantName);
allPlant.TryGetValue(plantKey, out PlantData plantData);
plantsData.plantData = plantData;
}
plantSaveDataList = plantsDataWrapper.plants;
GameManager.instance.plantGrowthManager.SetTilePlantSaveData(plantSaveDataList);
}
private void LoadInventoryFromData(Inventory inventory, InventoryData inventoryData)
{
if (inventory != null)
{
inventory.Clear();
foreach (var slotsData in inventoryData.slots)
{
int key = Animator.StringToHash(slotsData.itemName);
if (allItem.TryGetValue(key, out var itemData))
{
PlantData plantData = null;
if (!string.IsNullOrEmpty(slotsData.plantName))
{
int plantKey = Animator.StringToHash(slotsData.plantName);
allPlant.TryGetValue(plantKey, out plantData);
}
for (int i = 0; i < slotsData.currentCount; i++)
{
Item item = new GameObject("Item").AddComponent<Item>();
item.itemData = itemData;
item.plantData = plantData;
inventory.Add(item);
}
}
}
}
}
4. targetPositon에 방향대로 애니메이션 방향 지정
땅을 파거나, 물을 줄 때 애니메이션의 방향과 targetPosition이 따로 놀아, 왼쪽을 클릭했지만 애니메이션의 방향은 오른쪽을 향하는 문제가 있었다.
그래서 targetPosition의 방향을 현재 플레이어의 위치를 그리드로 변환한 위치와 비교하여 방향을 계산한 후 애니메이션의 파라미터에 저장하게 했다.
//Player.cs
private void SetAnimationDirection(Vector3 targetPosition)
{
Vector3 playerPosition = transform.position;
Vector3Int gridPlayerPosition = new Vector3Int(Mathf.FloorToInt(playerPosition.x), Mathf.FloorToInt(playerPosition.y), 0);
// 현재 위치와 targetPosition을 비교하여 방향 계산
Vector3 direction = (targetPosition - gridPlayerPosition).normalized;
// 방향을 x, y 값으로 애니메이션 파라미터에 설정
anim.SetFloat("Horizontal", direction.x);
anim.SetFloat("Vertical", direction.y);
// 이전 이동 방향을 저장
lastMoveDirection = direction;
}
이로써 큰 문제 없이 잘 작동하는 게임을 만들었고, 인벤토리 시스템과 식물 성장 시스템, json에 대해서 깊게 배우게 되어 재미있었고 더 많은 기능을 추가해서 더 큰 게임으로 만들고 싶다는 욕심과 액션 이벤트를 사용하여 리팩토링하고 싶다는 생각이 든다.
대강 한달동안 만든 게임에서 많은 것을 배웠고, 아직 너무 많이 부족하다는 생각이 들면서 더 열심히 해야겠다는 생각이 든다.
'TIL' 카테고리의 다른 글
[Unity] Fooooox - Refactoring1 (모바일용 이동 조작 & 체력 UI 변경) (0) | 2024.12.13 |
---|---|
[Unity] Valley - Mouse Cursor 변경하기 (1) | 2024.11.24 |
[Unity] Valley - MonoBehaviour를 상속받지 않는 인스턴스 클래스에서 코루틴 사용하는 법 (0) | 2024.11.23 |
[Unity] Valley - Sound Manager (0) | 2024.11.22 |
[Unity] Valley - Item Shop 구현 (0) | 2024.11.21 |