직렬화

프로그램의 객체, 구조체, 클래스 등을 일정한 형식으로 나열(serialize) 하여 파일이나 문자열 등으로 저장할 수 있게 만드는 과정
  • 메모리에 흩어져 있는 데이터들을 정리하여 순차적으로 나열
  • 나중에 역직렬화(Deserialize) 를 통해 다시 원래 구조로 복원 가능
  • 주로 데이터 저장, 전송, 복원 등에 사용됨

직렬화 종류

1. PlayerPrefs

2. CSV(Comma Separated Values)

3. XML(eXtensible Markup Language)

4. JSON(JavaScript Object Notation)

5. ScriptableObject


PlayerPrefs

Unity에서 제공하는 간단한 데이터 저장 시스템으로,
키-값(Key-Value) 형태로 데이터를 저장할 수 있다.

 

더보기

[예시 코드]

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

public class Player : MonoBehaviour
{
    public string nickname;
    public int level;
    public float hp;

    private void Start()
    {
        Save(); // 또는 Load();
    }

    public void Save()
    {
        string SaveData = nickname + ',' + level + ',' + hp; // 일렬로 나열되므로 ',', 한번에저장
        PlayerPrefs.SetString("PlayerData", SaveData);
        Debug.Log("저장완료");
    }

    public void Load()
    {
        string loadData = PlayerPrefs.GetString("PlayerData"); // 컴마로 구분된게 불러와짐

        string[] datas = loadData.Split(',');
        this.nickname = datas[0];
        this.level = int.Parse(datas[1]);
        this.hp = float.Parse(datas[2]);
    }
}

 

[코드 분석]

정보를 한번에 저장하기 위하여 저장할 변수를 전부 더해주고 구분을 위해 ','(콤마)로 나눠 표현

Playerprefs.Setstring을 통해 벨류값에 해당하는 SaveData를 저장해준다

불러올때는 Playerprefs.Getstring으로 키 값("PlayerData")을 불러오고

이 값은 콤마로 불러와졌으므로 Split을 통해 분리 후 

저장하고 싶은 정보에 각 배열을 할당해준다.

 

주로 간단한 데이터(설정 값) 등에 사용되며

 

보안이 취약하므로 중요한 데이터에 사용하지 않는다. 


CSV(Comma Separated Values)

데이터를 쉼표로 구분해 저장하는 텍스트 형식

더보기

[예시 코드]

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

public class CSV : MonoBehaviour
{
    private Dictionary<string, string> chatData = new Dictionary<string, string>();

    private void Start()
    {
        TextAsset csvData = Resources.Load<TextAsset>("CSVData"); // 데이터 읽어오기
        var data = csvData.text.TrimEnd(); // 맨끝에 있는 공백제거(예외처리)
        
        Deserialization(data);
        
        Debug.Log(Show(3, 3));
    }

    public void Deserialization(string originData)
    {
        var rowData = originData.Split('\n'); // 행을 엔터 기준으로 분리

        for (int i = 1; i < rowData.Length; i++) // 첫 줄은 헤더이므로 생략
        {
            var data= rowData[i].Split(',');

            chatData[data[0]] = data[1]; // 딕셔너리에 저장
        }
    }
    
    public string Show(int chapter, int phase)
    {
        string t = $"{chapter}_{phase}";
        return chatData[t];
    }
}

 

[코드 분석]

행 단위(엔터 단위, '\n')로 분리하고(Split) 쪼개진 데이터를 다시 쉼표 단위로 분리(',')

장점

엑셀처럼 행과 열 구조를 갖고 있어 직관적이고 사람이 읽기 쉽다.

 

단점

데이터 타입 구분이 없다(전부 string)

또한, 데이터가 많아질수록 읽기 어려워진다.(계층 구조 표현이 어려움)


읽고 쓰기 좋은 직렬화 방식

XML, JSON, YAML


XML(eXtensible Markup Language)

데이터를 태그 기반으로 표현하는 형식

더보기

[예시 코드]

public class XML : MonoBehaviour
{
    public Player player;

    private void Start()
    {
        //CreateXML(player);
        LoadXML();
    }

    void CreateXML(Player p)
    {
        XmlDocument xmlDoc = new XmlDocument(); // C#에 내장되어 있는 XmlDocument

	// 헤더 부분, AppendChild 특정노드의 자식으로 추가
        // CreateXmlDeclaration(string version, string encoding, string standalone)
        xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration("1.0", "utf-8", "yes")); 
        
        // 루트 노드
        XmlNode root = xmlDoc.CreateNode(XmlNodeType.Element, "GameData", string.Empty);
        xmlDoc.AppendChild(root);
        
        // 자식 노드
        XmlNode child = xmlDoc.CreateNode(XmlNodeType.Element, "PlayerData", string.Empty);
        root.AppendChild(child);
        
        // 엘리먼트
        XmlElement nickname = xmlDoc.CreateElement("nickname");
        nickname.InnerText = p.nickname; // 실제 값 적용
        child.AppendChild(nickname);
        
        XmlElement lv = xmlDoc.CreateElement("lv");
        lv.InnerText = p.lv.ToString(); // 실제 값 적용(int → string)
        child.AppendChild(lv);
        
        XmlElement hp = xmlDoc.CreateElement("hp");
        hp.InnerText = p.hp.ToString();
        child.AppendChild(hp);
        
        xmlDoc.Save("./Assets/Resources/GameDatas.xml"); // 경로

    }

    void LoadXML()
    {
        var t = Resources.Load<TextAsset>("GameDatas");
        
        XmlDocument xmlDoc = new XmlDocument(); xml을 읽어올때는 객체를 생성해야함
        xmlDoc.LoadXml(t.text);
        
        // 노드들로 이루어져 있기 때문에 특정해서 꺼내야 함
        XmlNodeList nodes = xmlDoc.SelectNodes("GameData/PlayerData");
        
        XmlNode playerData = nodes[0];
        
        player.nickname = playerData.SelectSingleNode("nickname").InnerText;
        player.lv = int.Parse(playerData.SelectSingleNode("lv").InnerText);
        player.hp = float.Parse(playerData.SelectSingleNode("nickname").InnerText);
    }
}

장점

 데이터 구조를 계층적으로 표현할 수 있어서 직관적이다.

 

단점

  가독성은 높지만, 사람이 직접 수정하기엔 구조가 복잡, 규칙이 엄격함


JSON(JavaScript Object Notation)

텍스트 기반의 데이터 교환 형식으로, 키-값 쌍으로 구성됨.

Unity에서는 주로 JsonUtility 또는 Newtonsoft.Json (Json.NET)을 사용해서

JSON을 직렬화(Serialize) 또는 역직렬화(Deserialize)함.

 

JsonUtility

더보기

[예시 코드]

public class JSONUtility : MonoBehaviour
{
    public UserData userData;
    void Start()
    {
        Load();
    }

    public void Save()
    {
        var saveData = JsonUtility.ToJson(userData); // 직렬화
        // 문자열 저장 [저장 경로, 어떤걸 저장할건지]
        File.WriteAllText(Application.persistentDataPath + "/userData.txt", saveData); 
    }

    public void Load()
    {
        var loadData = File.ReadAllText("/userData.txt");
        userData = JsonUtility.FromJson<UserData>(loadData); // 역직렬화
    }

    // 직렬화/역직렬화 할 요소를 별도의 구조로 만듬(구조체 또는 클래스)
    [System.Serializable]
    public class UserData
    {
        public int id;
        public string name;
        public int level;
        public int exp;
    }
}

 

[다양한 자료형에 대응할 수 있게 제너릭T로 리펙토링]

public void SaveData<T>(T data)
    {
        string saveData = JsonUtility.ToJson(data);
        File.WriteAllText(Application.persistentDataPath + $"/{typeof(T).ToString()}.txt", saveData);
    }

    public T LoadData<T>()
    {
        string loadData = File.ReadAllText(Application.persistentDataPath + $"/{typeof(T).ToString()}.txt");
        return JsonUtility.FromJson<T>(loadData);
    }

 

[사용]

public PlayerData data;

// 데이터 저장
DataManager.instance.SaveData(data);

// 데이터 불러오기
data = DataManager.instance.LoadData<PlayerData>();

장점

데이터 저장/불러오기 용이 (PlayerPrefs보다 구조화된 저장 가능)

사람이 읽기 쉬운 형식 (텍스트 기반)

 

단점

형 변환 실수 시 런타임 에러 발생 가능

보안에 약할 수 있음 (암호화 시 별도 처리 필요, AES를 통한 암호화)

 

JSON 파싱하는 방법(링크)


ScriptableObject

ScriptableObject는 Unity의 데이터 컨테이너

Unity 프로젝트 내에서 독립적인 자산(asset)으로 저장하고 공유할 수 있는 구조

더보기
[CreateAssetMenu(fileName = "SOData", menuName = "SO/Data", order = 1)]
public class SOData :ScriptableObject
{
    // Monster(절대 변하지 않는 값)
    public string nickname;
    public float maxHp;
    public float maxMoney;

    // ScriptableObject에서는 Rigidbody, Collider 사용가능
    public Rigidbody rb;
    public Collider co;
}

장점

메모리 절약(여러 오브젝트가 같은 ScriptableObject를 공유하면 중복 데이터를 줄이고 메모리 효율이 높아짐)

재사용성(게임 내 다양한 오브젝트에서 하나의 ScriptableObject 인스턴스를 참조하여 일관성 유지)

디자이너 친화적(프로그래머가 정의한 데이터 구조를 디자이너가 인스펙터에서 직접 조작 가능)

의존성 줄이기(코드와 데이터를 분리하여 유지보수성 향상, 특히 상태머신(FSM)이나 이벤트 시스템 구현 시 유리)

 

단점

빌드 시 세팅값이 유지되므로 절대 변동값을 넣으면 안된다(초기화 됨)

+ Recent posts