Unity에서의 Data처리
유니티에서 가장 흔하게 사용되는 data 저장 방식에 대해 알아본다.
유니티에서 사용되는 data 저장 방식의 장단점을 알아보고 추천활용법과 예제를 정리하였다.
맨 아래로 내리면 링크가 나오니 참고할 것.
PlayerPrefs
- 장점
Key와 Value 값으로 이루어져있어(Hash, Dictionary와 비슷) 사용하기 편리하다
유니티에 내장되어있다 - 단점
제한적인 자료형만 가능하다(int, float, string) → 하지만 함수로 만들어 가공하여 사용하면 된다
한 개의 파일에만 저장된다(WebPlayer는 1MB의 용량 제한이 있다)
보안에 가장 취약하다. 보안처리를 해주어도 값이 노출된다
레지스트리값으로 저장되기 때문에 기기에서 데이터를 삭제하면 초기화된다 - 추천 활용법
값을 변경하더라도 게임 시스템에 크게 지장없는 데이터
플레이어 설정값 (음악 볼륨값, 사용 언어, 그래픽 등) - 암호화 관련 참고 링크
유니티 암호화 1편, PlayerPrefs 암호화 - 예제
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerPrefsExample : MonoBehaviour
{
private string key = "Example";
private int val = 0;
public int defaultValue = 5;
// 호출 시 갱신
public int Val
{
get
{
LoadData();
return val;
}
set
{
SaveData(value);
}
}
// 시작 시에 로드
private void Start()
{
PlayerPrefs.DeleteAll();
if (PlayerPrefs.HasKey(key)) // 키가 있다면 로드
{
LoadData();
}
else // 키가 없다면 생성
{
Debug.Log("┌ Create new PlayerPrefs ┐");
SaveData(defaultValue);
Debug.Log("└───────────────┘");
}
}
// 세이브
private void SaveData(int data)
{
PlayerPrefs.SetInt(key, data);
val = PlayerPrefs.GetInt(key, data);
Debug.Log("Set: " + val);
}
// 로드
private void LoadData()
{
val = PlayerPrefs.GetInt(key, val);
Debug.Log("Get: " + val);
}
}
CSV
- 장점
엑셀 등의 툴을 사용해 외부에서 작업할 수 있어서 기획과의 협업에 편리하다
동일하게 대량의 데이터 편집 작업에 용이하다 - 단점
표로 만들 수 있는 구조만 가능하기에 Json보다 구조상의 한계가 있다
특정 키를 찾거나 수정하는 것도 헷갈리고 오류도 발견하기 까다로워서 신뢰성이 떨어진다
콤마(,)와 엔터(/n)는 따로 큰따옴표(““)로 묶어 후처리 해주어야한다 - 추천 활용법
외부에서 타인이 수정을 많이 하는 대량의, 정형화된 간단한 구조의 데이터
로컬라이징 데이터, item table - 예제
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 데이터 가져올떄 파라메터로 사용할 Enum 값
/// </summary>
public enum PartDataType
{
/// <summary>
/// 오브젝트 이름
/// </summary>
NAME_OBJ,
/// <summary>
/// 해당오브젝트 설명
/// </summary>
DESCRIPTION
}
public class CsvExample : MonoBehaviour
{
[SerializeField]
/// <summary>
/// 데이터 CSV 파일
/// </summary>
TextAsset csvFile;
// csv를 나누는 기준
char lineSeperator = '\n';
char fieldSeperator = ',';
/// <summary>
/// 데이터 class
/// </summary>
class CsvData
{
/// <summary>
/// 모델오브젝트 이름
/// </summary>
public string nameObj;
/// <summary>
/// 해당 오브젝트 설명
/// </summary>
public string desc;
}
/// <summary>
/// 데이터가 저장될 dictionary
/// </summary>
Dictionary<string, CsvData> dataDic = new Dictionary<string, CsvData>();
private void Start()
{
Csv2Dic();
}
void Csv2Dic()
{
// 행별로 나눠서 저장
string[] records = csvFile.text.Split(lineSeperator);
int lineCount = 0;
int skipLine = 1; // 스킵하고 싶은 라인Count
// 열별로 나눠서 저장
foreach (string record in records)
{
lineCount++;
if (lineCount <= skipLine) // csv에서 라인 스킵
continue;
// value 내 comma 처리 추가
string[] fields = FieldSplitter(record);
if (string.IsNullOrEmpty(fields[0]))
break;
// csv 변경시 같이 변경해주어야함-----------
CsvData objData = new CsvData
{
nameObj = fields[0],
desc = fields[1]
};
// 정리된 objData를 데이터dic에 최종 Add
if (dataDic.ContainsKey(fields[0]) == false)
{
dataDic.Add(fields[0], objData);
Debug.Log("Added: " + objData.nameObj);
}
else
{
Debug.Log("duplicated object name exists");
}
}
}
/// <summary>
/// 메인데이터 딕셔너리로부터 string 데이터 가져오기
/// </summary>
/// <param name="objName">선택한 오브젝트 이름</param>
/// <param name="dataName">가져올 데이터종류</param>
/// <returns></returns>
public string GetObjData(string objName, PartDataType dataName)
{
string data = "";
if (dataDic.ContainsKey(objName) == false)
{
data = "None";
return data;
}
switch (dataName)
{
case PartDataType.NAME_OBJ:
data = dataDic[objName].nameObj;
break;
case PartDataType.DESCRIPTION:
data = dataDic[objName].desc;
break;
}
return data;
}
/// <summary>
/// value내 comma와 일반 comma의 구분을 위한 ""판별, 반환
/// </summary>
/// <param name="line">레코드 입력</param>
/// <returns></returns>
internal string[] FieldSplitter(string line)
{
List<string> fieldsList = new List<string>();
int fieldStart = 0;
bool metDoubleQuotes = false;
for (int i = 0; i < line.Length; i++)
{
if (line[i].Equals('"'))
{
for (i++; line[i].Equals('"') == false; i++) { }
metDoubleQuotes = true;
}
if (line[i] == fieldSeperator)
{
if (metDoubleQuotes)
{
fieldsList.Add(line.Substring(fieldStart + 1, i - fieldStart - 2));
fieldStart = i + 1;
metDoubleQuotes = false;
}
else
{
fieldsList.Add(line.Substring(fieldStart, i - fieldStart));
fieldStart = i + 1;
}
}
if (i == line.Length - 1)
{
fieldsList.Add(line.Substring(fieldStart, i - fieldStart + 1));
}
}
return fieldsList.ToArray();
}
}
- 예제 csv
skipObject0,Description0 Object1,Description1 dupleObject2,Description2 dupleObject2,Description2-1 Object3,Description3 "comma,Object4",Description4
Json
- 장점
복잡한 구조를 구현할 수 있다
유니티에서 파서를 지원한다
Key와 Value 값으로 이루어져있어(Hash, Dictionary와 비슷) 사용하기 편리하다 - 단점
CSV와 마찬가지로 오류발견이 까다롭다
대량의 데이터 편집이 힘들다
Array 처리를 지원하지 않아 후처리가 필요하다 - 추천 활용법
많지 않은, 복잡한 구조의 데이터
게임 콘텐츠 list - 예제
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;
/// <summary>
/// 데이터 저장과 로드 함수
/// </summary>
public class JsonExample : MonoBehaviour
{
[Tooltip("저장하길 원하는 파일 이름(.json 제외)")]
public string fileName = "JsonSample";
public enum DataType
{
Single,
Array
}
[Tooltip("작성한 데이터의 타입 지정")]
public DataType type = DataType.Single;
/// <summary>
/// 받고 싶은 데이터
/// </summary>
[System.Serializable]
public class Data
{
public int id;
public string name;
public string[] texts;
public Vector3 position;
}
public Data data;
public Data[] arrayData;
/// <summary>
/// 데이터 save 및 load 경로
/// </summary>
string DataPath
{
get
{ return Path.Combine(Application.dataPath, "Json", fileName + ".json"); }
}
// test
void Start()
{
Debug.Log(data.name);
foreach (var data in arrayData)
{
Debug.Log(data.name);
}
}
/// <summary>
/// 데이터를 Json으로 저장
/// </summary>
[ContextMenu("Save Data To Json")]
void SaveDataToJson()
{
string jsonData = "";
if (type == DataType.Single)
jsonData = JsonUtility.ToJson(data, true);
else if (type == DataType.Array)
jsonData = JsonArrayHelper.ToJson(arrayData, true);
File.WriteAllText(DataPath, jsonData);
}
/// <summary>
/// Json을 데이터로 불러오기
/// </summary>
[ContextMenu("Load Data From Json")]
void LoadDataFromJson()
{
string jsonData = "";
jsonData = File.ReadAllText(DataPath);
if (type == DataType.Single)
data = JsonUtility.FromJson<Data>(jsonData);
else if (type == DataType.Array)
arrayData = JsonArrayHelper.FromJson<Data>(jsonData);
}
}
/// <summary>
/// Json을 Array로 사용하고 싶을 때 JsonUtility대신 사용
/// </summary>
public static class JsonArrayHelper
{
public static T[] FromJson<T>(string json)
{
Wrapper<T> wrapper = JsonUtility.FromJson<Wrapper<T>>(json);
return wrapper.array;
}
public static string ToJson<T>(T[] array, bool prettyPrint = false)
{
Wrapper<T> wrapper = new Wrapper<T>();
wrapper.array = array;
return JsonUtility.ToJson(wrapper, prettyPrint);
}
[System.Serializable]
private class Wrapper<T>
{
public T[] array;
}
}
Scriptable Object
- 장점
유니티에 내장되어있다
대량의 데이터 처리에 용이하다
다양하고 복잡한 구조의 데이터도 처리가 가능하다 - 단점
빌드 후의 저장이 불가능하다
유니티 외부에서의 수정이 불가능하다 - 추천 활용법
외부에서, 빌드 후 수정할 일이 없는 내부 자체 데이터 - 예제
- ScriptableObjectExample.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu]
public class ScriptableObjectExample : ScriptableObject
{
/// <summary>
/// 받고 싶은 데이터
/// </summary>
[System.Serializable]
public class Data
{
public int id;
public string name;
public string[] texts;
public Vector3 position;
}
public Data[] datas;
}
- ScriptableObjectTester.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScriptableObjectTester : MonoBehaviour
{
[SerializeField]
private ScriptableObjectExample example;
private void Start()
{
foreach (var data in example.datas)
{
Debug.Log(data.name);
}
}
}