코드 및 공부/데이터 관리

데이터 관리 - JSON을 이용한 데이터 관리

ekrxjvpvj0110 2024. 9. 16. 03:03

유니티 버전 - 2022.3.44f1

 

 

 

 

 

목차


  • JSON이란?
    • JsonUtility와 System.IO
  • DataManager의 코드 및 설명
    • 전체 코드
    • 설명
  • JsonUtility의 장점과 단점, 코드 개선점
    • 장점
    • 단점
    • 개선점
  • 코드 개선하기
    • 개선점
  • 완성된 코드 및 설명
    • 완성코드
    • 설명
    • 실행결과
  • 마치며
    • 간단 정리

 

 

 

 

 

JSON이란?


JsonUtility는 유니티의 데이터 구조를 Json 형식으로 변환하거나 Json 형식의 데이터를 유니티 데이터 구조로 변환하는 데 사용됩니다

 

System.IO는 파일 읽기/쓰기, 디렉토리 관리, 파일 경로 조작 등의 작업을 수행 할 수 있게 해줍니다

 

 

 

 

 

DataManager의 코드 및 설명


초기 형태의 DataManager 코드입니다, 하나씩 나누어서 설명 하겠습니다

초기 형태의 DataManager 코드

 

 

 

1. using 지시문

using System.Collections.Generic;
using UnityEngine;
using System.IO;

파일 읽기,쓰기 및 JsonUtility를 사용하기 위해 System.IO를 선언합니다

 

 

 

2. 커스텀 클래스 Datas

[System.Serializable]
public class Datas
{
    public Vector2 playerPositon;
    public string playername;
    public int level;
    public int hp;
    public float stamina;
    public List<string> items = new List<string>();
}

[System.Serializable] : 데이터를 저장할 수 있는 형식으로 변환하기 위한 직렬화 과정

 

커스텀 클래스 Datas : 저장하고자 하는 정보 선언 및 FromJson의 형식 참조(LoadData에서 설명)

 

 

 

3. 선언부

public class DataManager : ConvertToSingleton<DataManager>
{
    public Vector2 playerPositon; // 여기부터
    public string playername;
    public int level;
    public int hp;
    public float stamina;
    public List<string> items = new List<string>(); // 여기까지 저장하고자 하는 데이터

    private Datas theDatas = new Datas();

    private string saveDataDirectory;
    private string saveFileName = "playerData.txt";

private Datas theDatas = new Datas();

데이터를 저장하기 위한 일시적인 그릇 역할, new 키워드를 이용해 새로운 객체를 생성하여 내부 변수를 이용

 

private string saveFileName = "playerData.txt";

데이터가 저장되어 파일로 존재할때 사용할 이름

 

 

 

Awake()

    protected override void Awake() // 싱글톤화 및 오브젝트 유지
    {
        base.Awake();
        DontDestroyOnLoad(gameObject);
    }

싱글톤화는 아래를 참조 바랍니다

 

 

 

Start()

    private void Start()
    {
        saveDataDirectory = Application.dataPath + "/Saves/";

        if (!Directory.Exists(saveDataDirectory)) // 디렉토리 존재 여부 확인
        {
            Directory.CreateDirectory(saveDataDirectory); // 디렉토리가 존재하지 않을때 생성
        }

        LoadData();
    }

saveDataDirectory = Application.dataPath + "/Saves/";

 

읽기 전용 경로로, 데이터 파일이 저장된 경로를 고정 값으로 반환합니다, 이 뒤에 /Saves/ 를 붙여 파일을 저장하고자하는 디렉토리의 경로를 saveDataDirectory에 저장합니다

 

 

 

SaveData()

public void SaveData()
{
    theDatas.playerPositon = playerPositon;
    theDatas.playername = playername;
    theDatas.level = level;
    theDatas.hp = hp;
    theDatas.stamina = stamina;
    theDatas.items = items;

    string json = JsonUtility.ToJson(theDatas);

    File.WriteAllText(saveDataDirectory + saveFileName, json);
}

 

theDatas.XXX = XXX

 

theDatas 클래스에 현재 상태의 정보를 저장하는 역할을 합니다


string json = JsonUtility.ToJson(theDatas);

 

정보를 저장한 후 theDatas를 JSON 형식의 문자열로 반환합니다


File.WriteAllText(saveDataDirectory + saveFileName, json);

 

json 문자열을 아래의 경로에 작성합니다(saveDataDirectory + saveFileName), 이미 파일이 존재할 경우 내용을 덮어씁니다

 

 

 

LoadData()

    public void LoadData()
    {
        if (File.Exists(saveDataDirectory + saveFileName)) // 경로에 파일이 존재하는지 확인
        {
            string loadJson = File.ReadAllText(saveDataDirectory + saveFileName);
            // 경로에 존재하는 파일에서 JSON 형식의 문자열을 읽어옴

            theDatas = JsonUtility.FromJson<Datas>(loadJson);

            playerPositon = theDatas.playerPositon;
            playername = theDatas.playername;
            level = theDatas.level;
            hp = theDatas.hp;
            stamina = theDatas.stamina;
            items = theDatas.items;
        }
    }
}

theDatas = JsonUtility.FromJson<Datas>(loadJson);

 

읽어온 JSON 문자열(loadJson)을 theDatas의 각 속성에 매핑합니다(저장된 내용을 불러와서 theDatas클래스에 넣어줌)

FromJson<Datas> 여기서 Datas는 청사진같은 역할입니다, loadJson의 안에 있는 정보들은 그저 일렬로 나열되어있기때문에 청사진(Datas라는 구조도)을 제공하여 알맞게 데이터가 들어갈 수 있도록 합니다


playerPositon = theDatas.playerPositon; 및 나머지 5개

 

현재의 상태에 불러온 데이터를 적용시킵니다

 

 

 

 

 

JsonUtility의 장점과 단점, 코드 개선점


장점

  • Unity에 내장되어 있기때문에 별도의 라이브러리 설치 없이 쉽게 사용 할 수 있고 과정이 간단합니다
  • 다른 형식에 비해 상대적으로 빠르며, 데이터 크기도 작아 효율적입니다

단점

  • 다차원 배열, 딕셔너리 등 지원하지 않는 데이터 구조가 존재합니다
  • JSON형식은 사용자가 직접 열어 데이터를 수정할 수 있기에 보안에 취약합니다

코드 개선점

  • 저장해야할 데이터의 증가에 따른 클래스의 복잡도 증가
  • Application.dataPath의 문제점 
  • 경로 하드코딩에 대한 휴먼 에러️
  • 각종 오류에 대한 처리 부족

 

 

 

 

 

완성된 코드 및 설명


먼저 저장해야할 데이터의 증가에 따른 클래스 복잡도를 해결하기위해 데이터를 실질적으로 활용하는 클래스에서 관리하도록하고 중앙 클래스인 DataManager에서는 클래스들의 전체적인 데이터 저장, 로드 등의 관리를 하도록 하였습니다 

DataManager 클래스

 

 

 

클래스를 구분 후 근본적인 복잡도를 해결하기 위해 필드 선언 없이 Datas클래스만 선언 후 이용하려 하였으나 코드의 복잡성과 참조의 어려움의 문제가 생겨 그대로 사용하기로 하였습니다

 

 

Application.dataPath의 문제점

saveDataDirectory = Path.Combine(Application.persistentDataPath + "/Saves/");

 

플랫폼간 호환성 및 안전한 데이터 저장을 위해 Application.dataPath 대신 Application.persistentDataPath을 사용하였습니다

 

 

 

경로 하드코딩에 대한 휴먼 에러️

saveDataDirectory = Path.Combine(Application.persistentDataPath, "Saves");

 

사용자가 직접 / 를 이용하여 경로를 내려가거나 + 를 이용하여 연산하는 것을 Path.Combine을 이용하여 해결하였습니다

경로는 , (콤마)를 이용하여 나열하고 / 를 이용하여 경로를 내려갈 필요없이 시스템이 자동적으로 처리하도록 하였습니다 

 

 

 

각종 오류에 대한 처리 부족

1. SaveData() 및 LoadData() 에서 try-catch를 이용하여 예외 처리를 하여 대응할 수 있도록 하였습니다

 

2. 널 병합 연산자를 이용하여 items 리스트가 null일 경우 새로운 리스트를 할당하여 예외 처리 해주었습니다

items = theDatas.items ?? new List<string>();

 

 

 

 

 

실행결과


SaveData()실행시 파일이 제대로 생성되고 내용이 저장되었음을 확인할 수 있었습니다

Debug.Log(Path.Combine(DataManager.Instance.SaveDataDirectory, saveFileName));
// 디버그를 통해 파일 경로를 확인하고 들어가서 확인해보시기 바랍니다
// 유니티 Project 창에서 파일을 확인할 수 없습니다

 

LoadData()실행시 파일의 내용이 제대로 불러와져서 오브젝트에 반영되었음을 확인할 수 있었습니다

 

 

 

 

 

 

마치며


간단한 데이터 저장 및 로드 방법을 알아보았습니다, 프로젝트의 규모에 맞추어서 알맞은 데이터 관리법을 선택해야 할때 JSON은 좋은 방법 중 하나가 될 수 있겠습니다

 

이상입니다.