본문 바로가기

Unity/3D Project

3D RPG - 데이터 저장(Json)

데이터 저장을 위해서 저는 Json 파일을 사용했습니다.

Json 은 JavaScript Object Notation의 약어로 모든 값이 직렬화가 가능한 Key-Value 값의 쌍으로 이루어진 데이터를 텍스트를 사용하는 개방형 표준 포맷입니다.

Json 파일을 사용하기 전 직렬화에 대해서 알아야 합니다.

 

우선 저장할 데이터들의 클래스가 필요합니다. 먼저 만든 BaseStatus와 PlayerStatus에서는 각각의 스탯들이 분리되어 있기때문에 따로 따로 저장하는 것은 안 좋습니다.  왜냐하면, 파일 읽고 쓰기에서 파일 여는 것 행위 자체가 많은 성능을 차지하기 때문입니다. 그래서 하나의 클래스에 저장할 스탯들을 저장합니다.

 

DataManager.cs

...
[System.Serializable]
public class PlayerInfo
{
    public string Name;
    public float WalkSpeed;
    public float RunSpeed;
    public float CurrentHp;
    public float MaxHp;
    public float CurrentMp;
    public float MaxMp;
    public float CurrentStamina;
    public float AttackDamage;
    public float CriticalDamage;
    public float CriticalChance;
    public float Defense;
    public float CurrentExp;
    public float RequiredExp;
    public uint Level;
    public uint Money;
    public string CurrentScene;
}
...

직렬화에서 말했듯이 Serializable을 이용해서 직렬화를 해줍니다.

 

public static class JsonHelper
{
    public static T[] FromJson<T>(string json)
    {
        Wrapper<T> wrapper = JsonUtility.FromJson<Wrapper<T>>(json);
        return wrapper.items;
    }

    public static string ToJson<T>(T[] array)
    {
        Wrapper<T> wrapper = new Wrapper<T>();
        wrapper.items = array;
        return JsonUtility.ToJson(wrapper);
    }

    public static string ToJson<T>(T[] array, bool prettyPrint)
    {
        Wrapper<T> wrapper = new Wrapper<T>();
        wrapper.items = array;
        return JsonUtility.ToJson(wrapper, prettyPrint);
    }

    [System.Serializable]
    private class Wrapper<T>
    {
        public T[] items;
    }
}

유니티 5.3버전부터 JSON 포맷의 데이터를 파싱할 수 있는 API가 추가되었습니다. 하지만, 유니티 JsonUtility의 FromJson 함수만으로는 서버에서 넘어온 Json Array의 데이터를 Parsing하기 어려운 점이 가장 큰 단점인데, StackOverflow에서 얻어온 JsonHelper클래스를 이용하면 해결할 수 있었습니다.

 

PlayerInfo는 배열로 구성된 변수가 없기 때문에 유니티에 내장되어 있는 JsonUtility를 사용했습니다.

JsonUtility 는 유니티 오브젝트를 Json으로 포맷하거나 Json 형태를 유니티 오브젝트 형태로 바꿀 수 있습니다. 유니티에서 내장된 클래스이기 때문에 최소한의 기능들만 제공합니다. 다양한 기능들을 사용하고 싶으면 다른 라이브러리를 추가로 받아서 사용해야 합니다.

 

DataManager.cs

using System.IO;

public class DataManager : Singleton<DataManager>
{
    public PlayerInfo PlayerData;
    
    ...
    public void CreatePlayerStatusFile(string name)
    {
        PlayerStatus.Name = name;

        PlayerData.Name = PlayerStatus.Name;
        PlayerData.Level = PlayerStatus.Level;
        PlayerData.WalkSpeed = PlayerStatus.WalkSpeed;
        PlayerData.RunSpeed = PlayerStatus.RunSpeed;
        PlayerData.CurrentHp = PlayerStatus.MaxHp;
        PlayerData.MaxHp = PlayerStatus.MaxHp;
        PlayerData.CurrentMp = PlayerStatus.MaxMp;
        PlayerData.MaxMp = PlayerStatus.MaxMp;
        PlayerData.CurrentStamina = PlayerStatus.CurrentStamina;
        PlayerData.AttackDamage = PlayerStatus.AttackDamage;
        PlayerData.CriticalDamage = PlayerStatus.CriticalDamage;
        PlayerData.CriticalChance = PlayerStatus.CriticalChance;
        PlayerData.Defense = PlayerStatus.Defense;
        PlayerData.CurrentExp = PlayerStatus.CurrentExp;
        PlayerData.RequiredExp = PlayerStatus.RequiredExp;
        PlayerData.Money = PlayerStatus.Money;
        PlayerData.CurrentScene = EnumTypes.SceneName.Village.ToString();

        SaveStausData();
    }
    
    public void SaveStausData()
    {
        if (PlayerStatus.Name.Length != 0)
        {
            string jsonData = JsonUtility.ToJson(PlayerData, true);
            File.WriteAllText(_statusFilePath, jsonData);
        }
    }
    
    ...
}

우선 파일 입출력 기능이 필요하므로 파일과 디렉터리 관련 네임스페이스인 "using System.IO;" 를 사용합니다.

게임이 시작되면(일단 저장 예시입니다. 다른 방식으로도 사용할 수 있습니다. 다른 예시- 캐릭터를 생성했을 때) 데이터들을 처음 만든 class PlayerData에 저장합니다.

 

JsonUtility.ToJson

public static string ToJson(object obj, bool prettyPrint)

obj는 object형식이기 때문에 직렬화 된 클래스라면 다 읽을 수 있는 것을 알 수 있다.

2번째 매개변수는 prettyPrint로 좀더 읽기 쉽게 로드 해줍니다.

ToJson은 말 그대로 유니티 오브젝트를 Json 파일로 만드는 메서드입니다. Json 포맷으로 변환하기 이전에 직렬화(Serialization)가 된 클래스만 변환이 가능합니다.

 

DataManager.cs

...
private readonly string _statusFilePath = Application.dataPath + "/Resources/PlayerData.json";
...

    private void Start()
    {
        #region Status Save
        PlayerData = new PlayerInfo();

        if (File.Exists(_statusFilePath))
        {
            string saveFile = File.ReadAllText(_statusFilePath);
            PlayerData = JsonUtility.FromJson<PlayerInfo>(saveFile);

            PlayerStatus.Name = PlayerData.Name;
            PlayerStatus.Level = PlayerData.Level;
            PlayerStatus.MaxHp = PlayerData.MaxHp;
            PlayerStatus.RunSpeed = PlayerData.RunSpeed;
            PlayerStatus.CurrentHp = PlayerData.CurrentHp;
            PlayerStatus.MaxMp = PlayerData.MaxMp;
            PlayerStatus.CurrentMp = PlayerData.CurrentMp;
            PlayerStatus.CurrentStamina = PlayerData.CurrentStamina;
            PlayerStatus.AttackDamage = PlayerData.AttackDamage;
            PlayerStatus.CriticalDamage = PlayerData.CriticalDamage;
            PlayerStatus.CriticalChance = PlayerData.CriticalChance;
            PlayerStatus.Defense = PlayerData.Defense;
            PlayerStatus.RequiredExp = PlayerData.RequiredExp;
            PlayerStatus.CurrentExp = PlayerData.CurrentExp;
            PlayerStatus.Money = PlayerData.Money;
            PlayerStatus.CurrentScene = PlayerData.CurrentScene;
        }
        #endregion
        
        ...
    }

게임을 다시 시작했을 때 _statusFilePath 경로에 파일이 존재하는지 확인 후 만약 존재한다면, 파일을 읽고 파일 데이터를 Json를 역직렬화해서 클래스에 저장합니다.

 

public static object FromJson(string json, System.Type type);

json : 문자열로 된 Json 형식의 포맷 파일이 필요합니다.

public static T FromJson<T>(string json) (T) JsonUtility.FromJson(json, typeof(T));

제네릭을 사용하기 때문에 무명함수를 통해 type을 정의 합니다.

 

전체 코드

https://github.com/HerFS/Unity-RPG/blob/main/Assets/_Scripts/Manager/DataManager.cs

 

PlayerStatus.cs

    protected override void Awake()
    {
        onStaminaChanged += (playerStatus, currentStamina, prevStamina) =>
        {
            UIManager.Instance.PlayerInfoPanel.StaminaSlider.value = currentStamina;
            DataManager.Instance.PlayerData.CurrentStamina = currentStamina;
            DataManager.Instance.SaveStausData();
        };
        ...
    }

PlayerStatus 스크립트에서 구현한 스탯이 변경될 때마다 발동하는 이벤트에 DataManager에 있는 데이터 저장 메서드도 같이 실행하도록 추가해서 데이터가 변동될 때마다 저장이 되도록 설계했습니다.

 

처음 데이터들을 저장 하면 유니티 엔진 Resources 폴더에 PlayerData.json 라는 파일이 생성됩니다. 그 후로는 PlayerData.json 파일이 존재한다면 파일 내용들을 수정합니다.

 

전체 코드

https://github.com/HerFS/Unity-RPG/blob/main/Assets/_Scripts/Status/Player/PlayerStatus.cs