#内容
ローカルにセーブデータを保存する処理としてZeroFormatterが要件に合うかテストする。
- 保存する値をいつでも変えることができる
- 将来性を考慮してフォーマット変更時も同じファイルからロードできるようにする
FlatBuffersのテストのとき同様なフォーマットを目指す。
(http://qiita.com/akerusoft/items/8c10f8a40fee722e6d1b)
ZeroFormatter
ZeroFormatter - C#の最速かつ無限大高速な .NET, .NET Core, Unity用シリアライザー
https://github.com/neuecc/ZeroFormatter
#環境
Unity 5.4.1p4
Windows10
ZeroFormatter v1.0
(nugetからUnity用のものをゲットした)
ポイントはデータ振り分けのことさえ確認できれば良いということで
速度面の考慮はしていないし、実装に無駄が多いかもしれませんが、、、
#結果
データにバージョン情報を埋め込んで適時振り分けることができた。
テスト実装内容
https://github.com/akerusoft/UnityTestZeroFormatter
#実装内容
public abstract class MonsterDataBase
{
}
using ZeroFormatter;
[ZeroFormattable]
public class MonsterDataV1 : MonsterDataBase
{
[Index(0)]
public virtual string Name { get; set; }
[Index(1)]
public virtual uint HitPoint { get; set; }
[Index(2)]
public virtual float HitRate { get; set; }
[Index(3)]
public virtual uint Speed { get; set; }
[Index(4)]
public virtual uint Luck { get; set; }
}
using ZeroFormatter;
[ZeroFormattable]
public class MonsterDataV2 : MonsterDataBase
{
[Index(0)]
public virtual string Name { get; set; }
[Index(1)]
public virtual uint HitPoint { get; set; }
[Index(2)]
public virtual float HitRate { get; set; }
[Index(3)]
public virtual uint Speed { get; set; }
[Index(4)]
public virtual uint Luck { get; set; }
[Index(5)]
public virtual uint Defense { get; set; }
}
using ZeroFormatter;
using ZeroFormatter.Segments;
[ZeroFormattable]
public class DataRoot
{
public enum DataTypeVersion
{
MonsterDataV1,
MonsterDataV2,
}
[Index(0)]
public virtual DataTypeVersion DataType { get; set; }
[Index(1)]
public virtual byte[] Data { get; set; }
public bool SetMonsterData(MonsterDataBase data)
{
bool ret = false;
if(data is MonsterDataV1)
{
this.DataType = DataTypeVersion.MonsterDataV1;
this.Data = ZeroFormatterSerializer.Serialize((MonsterDataV1)data);
ret = true;
}
else if(data is MonsterDataV2)
{
this.DataType = DataTypeVersion.MonsterDataV2;
this.Data = ZeroFormatterSerializer.Serialize((MonsterDataV2)data);
ret = true;
}
return ret;
}
public MonsterDataBase LoadMonsterData()
{
switch(DataType)
{
case DataTypeVersion.MonsterDataV1:
return ZeroFormatterSerializer.Deserialize<MonsterDataV1>(Data);
case DataTypeVersion.MonsterDataV2:
return ZeroFormatterSerializer.Deserialize<MonsterDataV2>(Data);
default:
return null;
}
}
}
public class DataRootFormatter : ZeroFormatter.Formatters.Formatter<DataRoot>
{
public override int? GetLength()
{
return null;
}
public override DataRoot Deserialize(ref byte[] bytes, int offset, DirtyTracker tracker, out int byteSize)
{
DataRoot dataRoot = new DataRoot();
dataRoot.DataType = ZeroFormatter.Formatters.Formatter<DataRoot.DataTypeVersion>.Default.Deserialize(ref bytes, offset, tracker, out byteSize);
dataRoot.Data = ZeroFormatter.Formatters.Formatter<byte[]>.Default.Deserialize(ref bytes, offset, tracker, out byteSize);
return dataRoot;
}
public override int Serialize(ref byte[] bytes, int offset, DataRoot value)
{
int startOffset = offset;
offset += ZeroFormatter.Formatters.Formatter<DataRoot.DataTypeVersion>.Default.Serialize(ref bytes, offset, value.DataType);
offset += ZeroFormatter.Formatters.Formatter<byte[]>.Default.Serialize(ref bytes, offset, value.Data);
return offset - startOffset;
}
}
using UnityEngine;
using UnityEngine.Assertions;
using System.Collections;
using ZeroFormatter;
public class GameController : MonoBehaviour
{
void Start()
{
MonsterDataV1 dataV1 = new MonsterDataV1()
{
Name = "スライム",
HitPoint = 10,
HitRate = 80f,
Speed = 2,
Luck = 3,
};
{
byte[] bytes = ZeroFormatterSerializer.Serialize(dataV1);
MonsterDataV1 loadData = ZeroFormatterSerializer.Deserialize<MonsterDataV1>(bytes);
Debug.Log("Name:" + loadData.Name);
Debug.Log("HitPoint:" + loadData.HitPoint);
Debug.Log("HitRate:" + loadData.HitRate);
Debug.Log("Speed:" + loadData.Speed);
Debug.Log("Luck:" + loadData.Luck);
}
Debug.Log("------------------------------------");
MonsterDataV2 dataV2 = new MonsterDataV2()
{
Name = "スライムベッキー",
HitPoint = 100,
HitRate = 95f,
Speed = 200,
Luck = 300,
Defense = 400,
};
{
byte[] bytes = ZeroFormatterSerializer.Serialize(dataV2);
MonsterDataV2 loadData = ZeroFormatterSerializer.Deserialize<MonsterDataV2>(bytes);
Debug.Log("Name:" + loadData.Name);
Debug.Log("HitPoint:" + loadData.HitPoint);
Debug.Log("HitRate:" + loadData.HitRate);
Debug.Log("Speed:" + loadData.Speed);
Debug.Log("Luck:" + loadData.Luck);
Debug.Log("Defense:" + loadData.Defense);
}
Debug.Log("------------------------------------");
{
DataRoot dataRoot = new DataRoot();
dataRoot.SetMonsterData(dataV1);
{
byte[] bytes = ZeroFormatterSerializer.Serialize(dataRoot);
DataRoot loadData = ZeroFormatterSerializer.Deserialize<DataRoot>(bytes);
MonsterDataBase dataBase = loadData.LoadMonsterData();
if (dataBase is MonsterDataV1)
{
MonsterDataV1 data = dataBase as MonsterDataV1;
Debug.Log("Name:" + data.Name);
Debug.Log("HitPoint:" + data.HitPoint);
Debug.Log("HitRate:" + data.HitRate);
Debug.Log("Speed:" + data.Speed);
Debug.Log("Luck:" + data.Luck);
}
else
{
Debug.LogError("Failed load data version1.");
}
}
}
Debug.Log("------------------------------------");
{
DataRoot dataRoot = new DataRoot();
dataRoot.SetMonsterData(dataV2);
{
byte[] bytes = ZeroFormatterSerializer.Serialize(dataRoot);
DataRoot loadData = ZeroFormatterSerializer.Deserialize<DataRoot>(bytes);
MonsterDataBase dataBase = loadData.LoadMonsterData();
if (dataBase is MonsterDataV2)
{
MonsterDataV2 data = dataBase as MonsterDataV2;
Debug.Log("Name:" + data.Name);
Debug.Log("HitPoint:" + data.HitPoint);
Debug.Log("HitRate:" + data.HitRate);
Debug.Log("Speed:" + data.Speed);
Debug.Log("Luck:" + data.Luck);
Debug.Log("Defense:" + data.Defense);
}
else
{
Debug.LogError("Failed load data version2.");
}
}
}
}
}
zfc.exe -i (プロジェクトファイル名).csproj -o (UnityのAssetフォルダ配下)/(ファイル名).cs
#実行結果
Name:スライム
HitPoint:10
HitRate:80
Speed:2
Luck:3
------------------------------------
Name:スライムベッキー
HitPoint:100
HitRate:95
Speed:200
Luck:300
Defense:400
------------------------------------
Name:スライム
HitPoint:10
HitRate:80
Speed:2
Luck:3
------------------------------------
Name:スライムベッキー
HitPoint:100
HitRate:95
Speed:200
Luck:300
Defense:400
#感想
FlatBuffersのAPIは少し複雑で直感的ではありませんでした。
(書き込む順番や文字列の書き込み等)
IDLに宣言すれば自動でフィールドにIDをつけてくれるということはありがたいことですが
その手間が増えるとは言え、ZeroFormatterのように明示的にIndexをコード上に宣言する方が
やはり直感的ではないでしょうか?
(明示的な方が逆にわかりやすい)
データの振り分けをする手段はあるということはわかりました。
あとは各自かっこいい実装をすればいいんですね!
直感的っていいもんだ!