LoginSignup
15
13

More than 5 years have passed since last update.

[Unity] ZeroFormatterのデータ振り分けテスト

Last updated at Posted at 2016-11-08

内容

ローカルにセーブデータを保存する処理として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

実装内容

MonsterDataBase.cs
public abstract class MonsterDataBase
{
}
MonsterDataV1.cs
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; }
}
MonsterDataV2.cs
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; }
}
DataRoot.cs
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;
    }
}
GameController.cs
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をコード上に宣言する方が
やはり直感的ではないでしょうか?
(明示的な方が逆にわかりやすい)

データの振り分けをする手段はあるということはわかりました。
あとは各自かっこいい実装をすればいいんですね!

直感的っていいもんだ!:blush:

15
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
13