しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Unity で MessagePack for C# を利用する(コード生成)

※ これは 2022/01/21 時点の Unity 2021.2.8f1、MessagePack for C# v2.3.85 の情報です

最新版では動作が異なる可能性がありますのでご注意ください

前回、MessagePack for C# を使用できるように準備をしたので、さっそく使ってみる

とりあえず、Unity で使う上でほぼ必須となる AOT コンパイラ対応用のコード生成とシリアライザの動作確認をする

まずはシリアライズするデータクラス SaveData の入れ物を用意

using MessagePack;
using System;

namespace MatatabiUx.Common
{
    /// <summary>
    /// セーブデータ
    /// </summary>
    [MessagePackObject]
    [Serializable]
    public partial class SaveData
    {
        /// <summary>
        /// ID
        /// </summary>
        /// <value></value>
        [Key(0)]
        public int Id { get; protected set; } = default;

        /// <summary>
        /// 保存日時
        /// </summary>
        /// <value></value>
        [Key(1)]
        public DateTimeOffset Timestamp { get; protected set; } = default;

        /// <summary>
        /// 空データフラグ
        /// </summary>
        [IgnoreMember]
        public bool IsEmpty => this.Id == default;

        public SaveData()
        {
        }

        [SerializationConstructor]
        public SaveData(int id, DateTimeOffset timestamp)
        {
            this.Id = id;
            this.Timestamp = timestamp;
        }
   }
}

クラスにはシリアライズ対象の目印として [MessagePackObject][Serializable] の属性を付与し、シリアライズ対象プロパティには [Key] 属性、対象外のものには [IgnoreMember] をつける

[Key] 属性はインデックス番号をつけず、デフォルトのプロパティ名をキーにしてもらうこともできるが、インデックス番号で明示して上げた方がデータが圧縮できるしいろいろ効率がいいのでインデックス番号をつけた

インデックス番号を付ける場合、インデックス番号順の引数をもつコンストラクタを用意し、[SerializationConstructor] をつける

とりあえずこれで入れ物の準備が完了

次にコード生成、これをやっておかないと Unity で作ったアプリを iOS 実機上で動かすとエラーで止まったりするのでまずい

UnityEditor のメニューから [Window] - [MessagePack] - [CodeGenerator] が表示されるので、input path に対象の .csproj ファイル、output filepath に出力するシリアライザソースファイル名を指定する

コード生成設定

パスは Assets ディレクトリから見た相対パスで指定可能で、input path には SaveData のデータクラスが参照しそうなプロジェクトを全部指定する(基本は ../Assembly-CSharp.csproj だけでよいが今回は ../MatatabiUx.Common.csproj も念のため半角空白で区切って追加)

あとは Generate ボタン押下・・・しばらく待つと

こんな感じで UnityEditor のログが出力されて生成完了

コード生成ログ

さて、試しにシリアライズを下記のテストコードで試してみる

using MatatabiUx.Common;
using MessagePack;
using UnityEngine;

public class Test : MonoBehaviour
{
    public void Awake()
    {
        // シリアライザの初期設定
        MessagePack.Resolvers.StaticCompositeResolver.Instance.Register(
            MessagePack.Resolvers.GeneratedResolver.Instance, // コード生成した型解決クラス
            MessagePack.Unity.UnityResolver.Instance,
            MessagePack.Unity.Extension.UnityBlitWithPrimitiveArrayResolver.Instance,
            MessagePack.Resolvers.StandardResolver.Instance
        );
        var option = MessagePack.MessagePackSerializerOptions.Standard
            .WithCompression(MessagePack.MessagePackCompression.Lz4BlockArray) // LZ4 圧縮利用
            .WithResolver(MessagePack.Resolvers.StaticCompositeResolver.Instance);
        MessagePack.MessagePackSerializer.DefaultOptions = option;
    }

    public void Start()
    {
        // シリアライズしたものをデシリアライズしてみる
        var data = new SaveData(1, System.DateTimeOffset.Now);
        var serialized = MessagePackSerializer.Serialize(data);
        var deserialized = MessagePackSerializer.Deserialize<SaveData>(serialized);

        UnityEngine.Debug.Log(
            $"Id={deserialized.Id}, Timestamp={deserialized.Timestamp}");
    }
}

リアライザを使えるようにするおまじないコードと、シリアライズ→デシリアライズを試すコードが入っているだけ

これを適当な GameObject にアタッチしてお試し実行

エラーなく実行できた

ちゃんとシリアライズ→デシリアライズが動いて値も正しく復元できた!