데이터의 저장 (PlayerPrefs, CSV, JSON, SO, BF)

이준호·2024년 1월 12일
0

📌 데이터의 저장

데이터의 저장은 운영되고 있는 게임의 서비스 수명에 직접 영향을 주는 중요한 주제이다.

➔ 직렬화

우리가 프로그래밍한 복잡한 객체를 바이트의 배열로 만들어버리는 작업을 직렬화라고 하며, 모든 클래스를 직렬화할 수 있는 것은 아니다.

유니티에서는 아래와 같은 필드들에 대해 직렬화할 수 있다고 정의하고 있다.

직렬화 가능 타입

⭐ 정리 : Serializable로 전체를 구성하고, 클래스 자체도 Serializable 어트리뷰트를 붙이면 직렬화 가능타입이 된다 ⭐

[Serializable]
public class MyClass{
	public int a;
	public int b;
}

[Serializable]
public class MyBigClass{
	public MyClass my;
}

public class myCharacterData{
	// Transform 직접 넣는거 안됨
	public Vector3 position;
	public Quaternion rotation;
}

왜 데이터를 파일에 저장하면 좋을까?

📌 PlayerPrefs

PlayerPrefs는 게임 세션 간에 플레이어 환경설정을 저장하는 클래스이다.
이 클래스는 문자열, 실수, 정수 값을 사용자의 플랫폼 레지스트리에 저장할 수 있다.

유니티는 암호화하지 않고 로컬 레지스트리에 PlayerPrefs를 저장한다. 민감한 데이터를 저장하는 데에는 적합하지 않다.

유니티는 애플리케이션이 실행되는 운영 체제에 따라 PlayerPrefs데이터를 다르게 저장한다.
이 페이지에 제공된 파일 경로에서 예제 회사 이름과 예제 제품 이름은 Unity의 플레이어 설정에서 설정한 이름이다.

예제 1

string userId = "user1234";
string password = "password123";
string name = "홍길동";
int balance = 10000;

string combinedInfo = password + "," + name + "," + balance.ToString();
PlayerPrefs.SetString(userId, combinedInfo);
PlayerPrefs.Save();
if(PlayerPrefs.HasKey(userId){ // ID 있음
	string savedInfo = PlayerPrefs.GetString(userId);
	string[] infoParts = savedInfo.Split(',');
	
	string loadedPassword = infoParts[0];
	string loadedName = infoParts[1];
	int loadedBalance = int.Parse(infoParts[2]);
	
	Debug.Log("비밀번호: " + loadedPassword);
	Debug.Log("이름: " + loadedName);
	Debug.Log("잔액: " + loadedBalance);
}
else {
		// ID 없음
}

예제 1+

[System.Serializable]
public class UserData
{
    public string userId;
    public string password;
    public string name;
    public int balance;

    public UserData(string userId, string password, string name, int balance)
    {
        this.userId = userId;
        this.password = password;
        this.name = name;
        this.balance = balance;
    }

    // 객체의 데이터를 하나의 문자열로 변환
    public string Serialize()
    {
        return userId + "," + password + "," + name + "," + balance.ToString();
    }

    // 문자열에서 객체의 데이터를 복원
    public static UserData Deserialize(string data)
    {
        string[] parts = data.Split(',');
        return new UserData(parts[0], parts[1], parts[2], int.Parse(parts[3]));
    }
}

UserData userData = new UserData("user1234", "password123", "홍길동", 10000);
string serializedData = userData.Serialize();
PlayerPrefs.SetString(userData.userId, serializedData);
PlayerPrefs.Save();

string serializedData = PlayerPrefs.GetString("user1234");
UserData loadedUserData = UserData.Deserialize(serializedData);

Debug.Log("비밀번호: " + loadedUserData.password);
Debug.Log("이름: " + loadedUserData.name);
Debug.Log("잔액: " + loadedUserData.balance);

예제 2

public void changeScore(int value)
{
    this.nowScore += value;
    
		// 최고점수 갱신
    if (nowScore > highScore)
    {
        highScore = nowScore;
        PlayerPrefs.SetInt("HighScore", highScore);
    }
    // 점수 UI에 반영
		updateScore();
}

PlayerPrefs.Save() ?

📌 CSV

SCV(Comma-Separated Values)는 데이터를 저장하고 교환하는 데 사용되는 간단한 파일 형식이다. 이 형식은 각 데이터 항목이 쉼표로 구분되며, 각 줄이 하나의 데이터 레코드를 나타낸다. CSV파일은 엑셀이나 다른 표 계산 소프트웨어에서 쉽게 읽고 쓸 수 있으며, 프로그래밍에서도 널리 사용된다.

예제

[System.Serializable]
public class Item
{
    public int id;
    public string name;
    public int price;
    public string type;

    public Item(int id, string name, int price, string type)
    {
        this.id = id;
        this.name = name;
        this.price = price;
        this.type = type;
    }
}
using System.Collections.Generic;
using UnityEngine;

public class CSVReader : MonoBehaviour
{
    public TextAsset csvFile; // Unity Editor에서 할당

    void Start()
    {
				csvFile = Resources.Load<TextAsset>("/File/myFile");

        List<Item> items = ReadCsv(csvFile.text);
        // 읽은 데이터 사용 예
        foreach (Item item in items)
        {
            Debug.Log($"아이템: {item.name}, 가격: {item.price}, 타입: {item.type}");
        }
    }

    List<Item> ReadCsv(string csvData)
    {
        List<Item> items = new List<Item>();

        string[] lines = csvData.Split('\n');
        for (int i = 1; i < lines.Length; i++) // 헤더줄 제외!
        {
            if (lines[i].Trim() == "") continue;

            string[] fields = lines[i].Split(',');
            int id = int.Parse(fields[0]);
            string name = fields[1];
            int price = int.Parse(fields[2]);
            string type = fields[3];

            items.Add(new Item(id, name, price, type));
        }

        return items;
    }
}

📌 JSON

JSON(JavaScript Object Notation)은 경량 데이터 교환 형식이다. 유니티에서는 JSON을 사용하여 데이터를 저장하고 읽어올 수 있다. JSON은 사람과 기계 모두에게 모두에게 읽기 쉽고 쓰기 쉬운 형식이며, 다양한 프로그래밍 언어에서 지원한다,

예제 1

using UnityEngine;
using System.IO;

public class JsonDataManager : MonoBehaviour
{
    private string filePath;

    private void Awake()
    {
        filePath = Application.persistentDataPath + "/data.json";
    }

    public void SaveData(PlayerData data)
    {
        string jsonData = JsonUtility.ToJson(data, true);
        File.WriteAllText(filePath, jsonData);
    }

    public PlayerData LoadData()
    {
        if (File.Exists(filePath))
        {
            string jsonData = File.ReadAllText(filePath);
            return JsonUtility.FromJson<PlayerData>(jsonData);
        }
        else
        {
            Debug.LogError("Save file not found.");
            return null;
        }
    }
}

위 예제에서는 JsonDataManager 클래스를 사용하여 데이터를 JSON 형식으로 저장하고 로드하는 방법을 보여준다. SaveData 함수는 PlayerData 객체를 JSON 문자열로 변환하여 파일에 저장하고, LoadData 함수는 파일에서 JSON 문자열을 읽어와 Player 객체로 반환한다.

예제 2

using UnityEngine;

[System.Serializable]
public class PlayerData
{
    public string playerName;
    public int playerLevel;
    public float playerHealth;
}

public class Player : MonoBehaviour
{
    public PlayerData playerData;

    private void Start()
    {
        playerData = new PlayerData();
        playerData.playerName = "John";
        playerData.playerLevel = 10;
        playerData.playerHealth = 100.0f;

        SavePlayerData();
        LoadPlayerData();
    }

    private void SavePlayerData()
    {
        string jsonData = JsonUtility.ToJson(playerData);
        PlayerPrefs.SetString("PlayerData", jsonData);
        PlayerPrefs.Save();
    }

    private void LoadPlayerData()
    {
        if (PlayerPrefs.HasKey("PlayerData"))
        {
            string jsonData = PlayerPrefs.GetString("PlayerData");
            playerData = JsonUtility.FromJson<PlayerData>(jsonData);

            Debug.Log("Player Name: " + playerData.playerName);
            Debug.Log("Player Level: " + playerData.playerLevel);
            Debug.Log("Player Health: " + playerData.playerHealth);
        }
    }
}

위 예제에서는 Player 클래스를 사용하여 PlayerData 객체를 JSON 형식으로 저장하고 로드하는 방법을 보여준다. Start 함수에서는 PlayerData 객체를 생성하고 초기값을 설정한 후, SavePlayerData 함수를 호출하여 데이터를 저장하고 LoadPlayerData 함수를 호출하여 데이터를 로드한다. 로드한 데이터는 PlayerData 변수에 저장되며, 필요에 따라 사용할 수 있다.

JSON을 사용하여 유니티에서 데이터를 저장하고 로드하는 것은 간편하고 효율적인 방법이다.
JSON 형식을 다양한 데이터 유형을 지원하며, 데이터를 읽고 쓰기 쉽게 처리할 수 있다. 이를 통해 게임 내에서 플레이어 정보, 게임 설정, 진행 상태 등을 관리할 수 있다.

📌 Scriptable Object

Scriptable Object은 유니티 엔진에서 사용되는 데이터 컨테이너이다. 이를 통해 게임 오브젝트나 씬에 종속되지 않고 데이터를 저장하고 공유할 수 있다. Scriptable Object는 주로 게임의 리소스, 설정, 상태 등을 관리하는 데 사용된다.

사용법 - Scriptable Object 상속

예제 1

using UnityEngine;

[CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/SpawnManagerScriptableObject", order = 1)]
public class SpawnManagerScriptableObject : ScriptableObject
{
    public string prefabName;

    public int numberOfPrefabsToCreate;
    public Vector3[] spawnPoints;
}
using UnityEngine;

public class Spawner : MonoBehaviour
{
    // The GameObject to instantiate.
    public GameObject entityToSpawn;

    // An instance of the ScriptableObject defined above.
    public SpawnManagerScriptableObject spawnManagerValues;

    // This will be appended to the name of the created entities and increment when each is created.
    int instanceNumber = 1;

    void Start()
    {
        SpawnEntities();
    }

    void SpawnEntities()
    {
        int currentSpawnPointIndex = 0;

        for (int i = 0; i < spawnManagerValues.numberOfPrefabsToCreate; i++)
        {
            // Creates an instance of the prefab at the current spawn point.
            GameObject currentEntity = Instantiate(entityToSpawn, spawnManagerValues.spawnPoints[currentSpawnPointIndex], Quaternion.identity);

            // Sets the name of the instantiated entity to be the string defined in the ScriptableObject and then appends it with a unique number. 
            currentEntity.name = spawnManagerValues.prefabName + instanceNumber;

            // Moves to the next spawn point index. If it goes out of range, it wraps back to the start.
            currentSpawnPointIndex = (currentSpawnPointIndex + 1) % spawnManagerValues.spawnPoints.Length;

            instanceNumber++;
        }
    }
}

예제 2

using UnityEngine;

[CreateAssetMenu(fileName = "CharacterBaseStatsData", menuName = "ScriptableObjects/CharacterBaseStatsData", order = 1)]
public class CharacterBaseStatsData : ScriptableObject
{
    public int Hp;
    public int Defence;
    public int Damage;
    public int AttackSpeed;
    public int MoveSpeed;
}

📌 Binary Frmatter

Binary Formatter는 유나티에서 사용되는 직렬화 도구이다. 이를 통해 객체를 이진 형식으로 직렬화하고, 이진 형식을 다시 객체로 역직렬화할 수 있다. Binary Formatter는 주로 파일 저장 및 로드에서 사용되며,데이터를 보다 효율적으로 관리하고 공유할 수 있다.

예를 들어, 게임에서 플레이어의 상태를 저장하고 로드해야할 때, Binary Formatter를 사용할 수 있다. 플레이어의 정보를 객체로 표현하고, 이를 이진 형식으로 직렬화하여 파일에 저장한다. 필요할 때마다 파일에서 이진 형식을 읽어와 역직렬화하여 플레이어의 정보를 복원한다.

Binary Formatter를 사용하기 위해서는 직렬화할 클래스에 [Serializable] 속성을 추가해야 한다. 또한, FileStream 과 같은 파일 처리 클래스를 사용하여 생성하고 열어서 Binaty Formatter를 이용해 데이터를 읽고 쓸 수 있다.

예제

using UnityEngine;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

[System.Serializable]
public class PlayerData
{
    public string playerName;
    public int playerLevel;
    public float playerHealth;
}

public class SaveLoadManager : MonoBehaviour
{
    public PlayerData playerData;

    private string saveFilePath;

    private void Awake()
    {
        saveFilePath = Application.persistentDataPath + "/playerData.dat";
    }

    public void SavePlayerData()
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        FileStream fileStream = File.Create(saveFilePath);

        binaryFormatter.Serialize(fileStream, playerData);
        fileStream.Close();
    }

    public void LoadPlayerData()
    {
        if (File.Exists(saveFilePath))
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            FileStream fileStream = File.Open(saveFilePath, FileMode.Open);

            playerData = (PlayerData)binaryFormatter.Deserialize(fileStream);
            fileStream.Close();
        }
        else
        {
            Debug.Log("Save file not found.");
        }
    }
}

위 코드에서 PlayerData 클래스는 직렬화를 위해 [Serializable] 속성이 추가되어있다. SaveLoadManager 클래스는 PlayerData 를 이진 형식으로 직렬화하여 파일에 저장허고, 필요할 때, 파일에서 읽어와 역직렬화하여 PlayerData 를 복원한다.

Binary Formatter를 사용하면 유니티에소 데이터를 파일에 저장하고 로드하는 기능을 구현할 수 있다. 이를 통해 게임의 진행 상태, 플레이어 정보, 게임 설정 등을 보다 효율적으로 관리할 수 있다.

📌 정리

파일을 서버가 아닌 로컬(디바이스)에 저장하는 것은 항상 보안이 문제.

📌 데이터 저장에 많이 쓰는 메소드/프로퍼티

Application.persistentDataPath
File.Exists
File.ReadAllText
File.WriteAllText

profile
No Easy Day

0개의 댓글