말감로그

[Unity] Valley - 전체적인 코드 수정 & 회고 본문

TIL

[Unity] Valley - 전체적인 코드 수정 & 회고

habbn 2024. 12. 12. 20:03
728x90

그동안 작성했던 코드들에 대해서 버그나 기능을 추가하거나 리팩토링하면서 전체적인 코드를 수정했다.

 

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에 대해서 깊게 배우게 되어 재미있었고 더 많은 기능을 추가해서 더 큰 게임으로 만들고 싶다는 욕심과 액션 이벤트를 사용하여 리팩토링하고 싶다는 생각이 든다.

대강 한달동안 만든 게임에서 많은 것을 배웠고, 아직 너무 많이 부족하다는 생각이 들면서 더 열심히 해야겠다는 생각이 든다.

 

728x90