💾

GodotでMessagePack for C# を使ってみる

2024/08/26に公開

MessagePackはJsonやprotobufよりも速いらしいのでマルチプレイのゲームを作る場合やゲームデータのセーブなどに使えます。
GodotEngineでMessagePackを行うにはMessagePack for C#を使うのが良いかと思います。
gdscriptでmesagepackができるプラグインが今のところ見つからなかったので、ここはC#を使います。
ただ、Godot名前空間にあるVector2,Vector3などの型は対応していないのでCustomFormmatterを使ってシリアライズ/デシリアライズできるようにする必要があります。

CustomFormatterを定義

試しにVector2型のCustomFormatterを作ってみました

Formatters.cs
public sealed class Vector2Formatter : global::MessagePack.Formatters.IMessagePackFormatter<global::Godot.Vector2>
{
    public global::Godot.Vector2 Deserialize(ref MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options)
    {
        if (reader.IsNil)
        {
            throw new InvalidOperationException("typecode is null, struct not supported");
        }
        var length = reader.ReadArrayHeader();
        var x = default(float);
        var y = default(float);
        var z = default(float);
        for (int i = 0; i < length; i++)
        {
            var key = i;
            switch (key)
            {
                case 0:
                    x = reader.ReadSingle();
                    break;
                case 1:
                    y = reader.ReadSingle();
                    break;
                default:
                    reader.Skip();
                    break;
            }
        }
        var result = new global::Godot.Vector2(x, y);
        return result;
    }

    public void Serialize(ref MessagePackWriter writer, global::Godot.Vector2 value, global::MessagePack.MessagePackSerializerOptions options)
    {
        writer.WriteArrayHeader(2);
        writer.Write(value.X);
        writer.Write(value.Y);
    }
}

このCustomFormatterを使ってCustomResolverを作ります。

using MessagePack.Formatters;
using MessagePack.Resolvers;

public sealed class GodotResolver : IFormatterResolver
{
    public static readonly IFormatterResolver Instance = new GodotResolver();
    private static readonly IMessagePackFormatter[] Formatters = new IMessagePackFormatter[]
    {
        // ここにCustomFromatterを登録する
        new Vector2Formatter(),
        new Vector3Formatter(),
    };
    private static readonly IFormatterResolver[] Resolvers = new IFormatterResolver[]
    {
        StandardResolver.Instance,
    };
    private GodotResolver(){}
    public IMessagePackFormatter<T> GetFormatter<T>()
    {
        foreach (var formatter in Formatters)
        {
            if (formatter is IMessagePackFormatter<T> typedFormatter)
            {
                return typedFormatter;
            }
        }

        foreach (var resolver in Resolvers)
        {
            var formatter = resolver.GetFormatter<T>();
            if (formatter != null)
            {
                return formatter;
            }
        }

        return null;
    }
}

シリアライズオプションにこのResolverを登録します

public override void _Ready()
{
    var resolver = MessagePack.Resolvers.CompositeResolver.Create(GodotResolver.Instance);
    var options = MessagePackSerializerOptions.Standard.WithResolver(resolver);
    MessagePackSerializer.DefaultOptions = options;
}

これでgodotでMessagePackを使う準備はできました。試しにクラスを定義してシリアライズして見ます。

using Godot;
using MessagePack;

[MessagePackObject]
public readonly struct PlayerData
{
    [Key(0)]
    public readonly string name;
    [Key(1)]
    public readonly int age;
    [Key(2)]
    public readonly Vector2 position;

    public PlayerData(string name, int age, Vector2 position)
    {
        this.name = name;
        this.age = age;
        this.position = position;
    }
}
    
public override void _Ready()
{
    var resolver = MessagePack.Resolvers.CompositeResolver.Create(GodotResolver.Instance);
    var options = MessagePackSerializerOptions.Standard.WithResolver(resolver);
    MessagePackSerializer.DefaultOptions = options;

     var data = new PlayerData(
         name: "alice",
         age: 23,
        position: new Vector2(1.0f,2.0f));

     var bytes = Serialize(data);
}
public byte[] Serialize(PlayerData data)
{
	var bin = MessagePackSerializer.Serialize(data);
	var json = MessagePackSerializer.ConvertToJson(bin);
	GD.Print(json); //json形式でlogに出力してみる
	return bin;
}

stringintに加えてVector2型もしっかりシリアライズできています。

まとめ

CustomFormatterを自前で用意するのは面倒ですが、かなり簡単だったので誰でも実装できると思います。ゲームを作る上でセーブシステムなどは必要ですし、C#が使えるGodotならC#のライブラリが利用できるので、いろいろと便利ですね。

ちなみにVisualStudioを使っていてコメントなどに日本語が含まれているとバグるので、気を付けましょう。https://qiita.com/hyahoitaro/items/802f58595f4f971510f7

Discussion