Grani Engineering Blog

株式会社グラニはC#を中心として、ASP.NET、Unity、VR開発を行っています。

黒騎士と白の魔王におけるMessagePack-CSharpのUnionの活用事例

こんにちは、アプリケーション部の松田(@kimika127)です。

今回はMessagePack-CSharpのUnionを活用した事例をご紹介します。 MessagePack-CSharpそのものについては

neue cc - C#(.NET, .NET Core, Unity, Xamarin)用の新しい高速なMessagePack実装

こちらに詳しくありますのでご覧ください。

Unionについて

MessagePack-CSharpのUnionを噛み砕いていうと「インターフェイスでシリアライズできる機能」になります。 とだけ聞くと便利そうに思えますが、実際にプロジェクトを進めるうえでこの機能を有効的に活用できる場面はそう多くはないでしょう。

今回、黒騎士と白の魔王(以下、黒騎士)では、トーク機能でこのUnionを使うことにしました。

メッセージの種類

トークではたくさんのメッセージを流します。 黒騎士ではメッセージの種類も多岐にわたり、例えば

  • 単純な文字列
  • スタンプ

のほかに、トーク機能を持ったRPGゲーム特有の事例として

  • トークからクエストへみんなで出発する時の募集文

なども要件として出てきます。

メッセージで送る情報

単純にメッセージを送るといっても、その中には

  • 誰が送信したか
  • どこに送信したか(具体的にはトークルームと呼ばれる集団)
  • いつ送信したか
  • どのような内容か

など、これら以外にも多数の情報を持っています。

これらのうち、発言者やルーム、時刻などはメッセージの種類に関係なく、同じものを使うことができます。 しかし内容に関しては、文字列、スタンプ、ロビーへの招待など全く違う情報をクライアントへ送信する必要性が出てきます。 ここでUnionの出番になります。

具体的な実装例

// インターフェイスを用意
// 各種具象型をUnion属性で指定
[Union(0, typeof(TextMessageBody))]
[Union(1, typeof(StampMessageBody))]
[Union(2, typeof(QuestMessageBody))]
public interface IMessageBody { }

// 文字列を送る
[MessagePackObject]
public class TextMessageBody : IMessageBody 
{
    [Key(0)]
    public string Text { get; set; }
}

// スタンプを送る
[MessagePackObject]
public class StampMessageBody : IMessageBody 
{
    [Key(0)]
    public int StampId { get; set; }
}

// クエストへの募集を送る
[MessagePackObject]
public class QuestMessageBody : IMessageBody 
{
    [Key(0)]
    public int QuestId { get; set; }
    [Key(1)]
    public string Text { get; set; } // ユーザが入力できる募集文言とか…
}

具体的な利用例

サーバ側

var textMessage = new TextMessageBody { Text = "グラニのエンジニアブログへようこそ!" };

// シリアライズして送信
var bin = MessagePackSerializer.Serialize<IMessageBody>(textMessage);

クライアント側

// 受信してデシリアライズ
var body = MessagePackSerializer.Deserialize<IMessageBody>(bin);

// C# 7だと型switchが使えるので便利
// が、Unityだとasかインターフェイスに識別子を持たせて分岐
switch (body)
{
    // 文字列が来た
    case TextMessageBody x:
        Debug.Log(x.Text);
        break;
    // スタンプが来た
    case StampMessageBody x:
        Debug.Log(x.StampId);
        break;
    // クエストへの募集が来た
    case QuestMessageBody x:
        Debug.Log(x.QuestId + ":" + x.Text);
        break;
    default:
        break;
}

まとめ

実はMessagePack-CSharpを導入する以前は、本文の全ての要素をCSVにし、string型でクライアントに送ってからパースをしていました。

しかし、iOS/Androidアプリの運用を続けていくにあたって障害の一つとなるのが、「更新されていないかもしれないユーザのクライアント」と「更新しなければいけないサーバ」間の通信の整合性を保つことです。 そこでMessagePackを使えば、要素の追加に強く、既存のクライアントに影響を出しにくく、保守もしやすい設計にすることができます。

今回は黒騎士の中でもUnionという機能を有効に使えた例をご紹介させていただきました。