Map Edit 씬에서 생성한 딕셔너리 정보를 JSON으로 저장해야했는데, 딕셔너리는 바로 직렬화가 안되서 직렬화가 가능한 다른 객체를 거쳐서 저장할 수 있게 했다.
using System;
using System.Collections.Generic;
using System.Linq;
[Serializable]
public class SerializableDictionary<TKey, TValue>
{
public List<TKey> keys;
public List<TValue> values;
public SerializableDictionary() { }
public SerializableDictionary(Dictionary<TKey, TValue> dict) => FromDictionary(dict);
public Dictionary<TKey, TValue> ToDictionary()
{
var dict = new Dictionary<TKey, TValue>();
for (int i = 0; i < keys.Count; i++)
dict.TryAdd(keys[i], values[i]);
return dict;
}
public void FromDictionary(Dictionary<TKey, TValue> dict)
{
keys = dict.Keys.ToList();
values = dict.Values.ToList();
}
}
간단하게 keys, values 리스트를 갖는 직렬화 가능한 딕셔너리 클래스를 만들었다.
리스트는 자동으로 직렬화가 가능한 자료형이기 때문에 바로 떠오른 방법이었다.
FromDictionary와 ToDictionary 메서드로 두 자료형을 왔다갔다 할 수 있게 해봤다.
using System.Collections.Generic;
using UnityEngine;
public static class Extensions
{
public static string DictionaryToJson<TKey, TValue>(this Dictionary<TKey, TValue> dict)
{
var serializableDict = new SerializableDictionary<TKey, TValue>(dict);
return JsonUtility.ToJson(serializableDict);
}
public static string DictionaryToJson<TKey, TValue>(this Dictionary<TKey, TValue> dict, bool pretty)
{
var serializableDict = new SerializableDictionary<TKey, TValue>(dict);
return JsonUtility.ToJson(serializableDict, pretty);
}
public static Dictionary<TKey, TValue> DictionaryFromJson<TKey, TValue>(this string jsonStr)
{
var serializableDict = JsonUtility.FromJson<SerializableDictionary<TKey, TValue>>(jsonStr);
return serializableDict.ToDictionary();
}
}
확장 메서드를 이용해서 두 객체를 바로바로 직렬화 할 수 있게 해봤다.
private void SaveDataFile()
{
File.WriteAllText(_dataPath, _voxelMap.DictionaryToJson());
FileInfo fileInfo = new FileInfo(_dataPath);
Debug.Log($"Save File Bytes : {fileInfo.Length} byte");
}
public IEnumerator ReadMapDataFile(TextAsset json)
{
var originData = json.text.DictionaryFromJson<Vector3Int, MapData>();
foreach (var data in originData)
{
var type = WorldData.GetType(data.Value.type)[data.Value.typeIndex];
_voxelMap.TryAdd(data.Key, (type, data.Value.forward));
}
yield return null;
}
컴포넌트 하나와 유니티 프리미티브 오브젝트인 Cube를 이용해 Voxel 맵을 제작할 수 있도록 해봤다.
씬에 MapEditDataSource
컴포넌트가 붙은 프리미티브 Cube를 배치하고, 컴포넌트에서 속성을 지정해준 다음, Play Mode로 들어가면 VoxelMapDataGernerator
컴포넌트가 MapEditDataSource들을 모두 읽어서 VoxelMap 데이터를 만든다.
using UnityEngine;
using System.Collections.Generic;
using System.IO;
public class VoxelMapDataGenerator : MonoBehaviour
{
private Dictionary<Vector3Int, MapData> _voxelMap = new();
private MapEditDataSource[] _dataSources;
[SerializeField] private TextAsset _mapData;
[SerializeField] private string _dataPath = @"Assets/04. Resources/MapData.json";
private int _blockCount = 0;
public void Start()
{
GetDataSources();
GenerateMapData();
SaveDataFile();
Debug.Log($"Progress Time : {Time.realtimeSinceStartup}");
}
private void GetDataSources()
{
_dataSources = FindObjectsOfType<MapEditDataSource>();
Debug.Log($"Get Sources Count : {_dataSources.Length}");
}
private void GenerateMapData()
{
foreach (var source in _dataSources)
{
foreach (var (pos, info) in source.GetSourceData())
{
if (_voxelMap.TryAdd(pos, info))
_blockCount++;
}
}
Debug.Log($"Create Blocks data Count : {_blockCount}");
}
private void SaveDataFile()
{
File.WriteAllText(_dataPath, _voxelMap.DictionaryToJson());
// 이 코드는 기존 내용이 지워지지 않음,,
//using (FileStream fs = new(_dataPath, FileMode.OpenOrCreate, FileAccess.Write))
//{
// using (StreamWriter sw = new StreamWriter(fs))
// {
// sw.Write(_voxelMap.DictionaryToJson());
// sw.Close();
// }
// fs.Close();
//}
FileInfo fileInfo = new FileInfo(_dataPath);
Debug.Log($"Save File Bytes : {fileInfo.Length} byte");
}
}
데이터를 생성하면, MapEditDataSource는 총 몇 개 읽어왔는지,
만들어진 Block의 갯수는 몇 개인지,
저장된 파일은 몇 byte인지,
실행 시간은 얼마나 걸렸는지 로그가 찍히도록 해봤다.