Unityでもgrpc-dotnetを使ったgRPCがしたい

こんにちは、エンジニアの渡辺(@mochi_neko_7)です。

今回は grpc-dotnet のライブラリを使って Unity を gRPC のクライアントとして利用する話を紹介します。

Unity で gRPC を使うにはいくつか選択肢がありますが、今回はサポートの切れている Grpc.Core ではなく 新しい方のライブラリの grpc-dotnet を有料アセットの Best HTTP/2 を組み合わせて使用する方法をメインでお話します。

なお本記事では Unity を使った gRPC のクライアントの実装をメインでお話するため、 gRPC サーバーの実装に関しては解説しないことをご承知おきください。

要約

  • Unity で gRPC を使うには Grpc.Core と grpc-dotnet を使う二通りの選択肢があります
    • Grpc.Core は実績がありますが既にサポートが切れています
    • grpc-dotnet は新しい実装ですが、そのままでは Unity では使用できません
  • grpc-dotnet と Best HTTP/2 という有料アセットを組み合わせて使用する grpc-dotnet-unity があります
    • 元の Repository ではなく fork して UPM 対応したものを利用するのがおすすめです
    • 本記事ではこちらをメインに紹介します
  • Unity で grpc-dotnet-unity を使って gRPC を導入するまでの一連の流れをざっと解説します

環境

今回使用している環境は下記になります。

  • Unity 2022.3.0f1
    • .NET Standard 2.1
  • grpc-dotnet 2.51.0
  • grpc-dotnet-unity 1.2.0
  • Windows 11

なぜ gRPC を使用するのか

gRPC とは何ぞや、という話は調べれば説明が出てきますのでここでは詳しく説明しません。

ざっくり言えば

  • Protocol Buffers による API 仕様の共通化・対応各言語のソースコード自動生成
  • HTTP/2 ベースの双方向も可能な柔軟な RPC 実行

が特徴として挙げられるかと思います。

Protocol Buffers による API 仕様の定義は OpenAPI のドキュメントよりシンプルで分かりやすいですし、 HTTP/1.1 ベースのREST API や WebSocketの組み合わせよりシンプルに双方向の API が実装できます。

gRPC は Client-Server モデルではありますが、Unity × gRPC で最も有名な MagicOnion のように リアルタイム通信・ネットワーク同期の用途に使うこともできます。

Unity をよく触っている方ならむしろ RPC の概念自体は Photon などのリアルタイム通信系のライブラリで慣れ親しんでいるかもしれません。

WebAPI の文脈でも同様に Client-Server 間で RPC をする形になります。

Unityで gRPC を使う場合の選択肢

そんなに便利ならどんどん使えばいいじゃないかという話になりますが、Unity での gRPC の導入には少しハードルがあるのが現状です。

Unity で gRPC を使えるようにする手段は自分の調べた限りでは大きく2つ存在します。

参考:.NET Standard 2.0 での gRPC クライアントの使用 | Microsoft Learn

1. (サポートの切れている) Grpc.Core を利用する

github.com

古くからある .NET 対応を含む gRPC 実装です。

Unity の使用している .NET のバージョンでは標準では HTTP/2 をサポートしていないのですが、 このライブラリはその実装もおそらく内部に含まれており、Unity でも問題なく動くというロジックのようです。

先ほど紹介した MagicOnion はこちらを使用していますので動作実績があります。

ただ注意が必要なのが「2023年5月で C# の対応は非推奨になってサポートが切れています」

grpc.io

そのためこれから新規で gRPC を使いたい場合には少し躊躇するかもしれません。

MagicOnion 以外の事例も少ないこともあり、情報もあまり多くないです。 *1

また、こちらは C ベースのライブラリ、つまりネイティブプラグインであることによる触りづらさもあるそうです。 *2

ちょっとネガティブな面を強調しているように見えるかもしれませんが、 サポートが切れていることさえ気にしなけば実績もあり十分選択肢になるかと思います。

2. grpc-dotnet を利用する

サポートの切れた Grpc.Core に替わる grpc-dotnet という C# ベースの gRPC 実装が用意されています。

github.com

新しい .NET のバージョンでは HTTP/2 のサポートがされていて、それを利用する形で C# で実装されているようです。

注意が必要なのは、現在の Unity がサポートしている .NET のバージョンでは HTTP/2 の標準実装が利用できず *3、 デフォルトでは HTTP/1.1 で動作する、もしくは動かないかもしれないという点です。

github.com

ところが GitHub でいろいろ調べていたところ、Best HTTP/2 という有料アセットの HTTP/2 実装を利用して grpc-dotnet を動かすという Repository を見つけました。

github.com

原理としては設定で grpc-dotnet の HttpHandler を変更することができ、そこに HTTP/2 対応した自作の HttpHandler を利用すればよいという話のようです。

有料の安くはない価格のアセットが必要になる導入ハードルはありますが、 C# で実装されていて将来性のある grpc-dotnet を Unity で動かすことができるのは面白い選択肢なのでは?と思い、 今回はこちらの方法で検証を進めました。*4

※ 2023/07/28 追記

Cysharp さんから Rust ベースの HTTP/2 Handler のライブラリ YetAnotherHttpHandler が公開され、 Best HTTP/2 の有料アセットを使用せずとも gRPC が利用できるようになりました。

github.com

neue.cc

HttpHandler の差し替えをするだけで同様に導入できるはずです。

これで Unity での gRPC 導入の敷居がかなり下がりましたね。

grpc-dotnet-unity を fork して利用する

救世主のような grpc-dotnet-unity ですが、もったいないことに UPM(Unity Package Manager)や Assembly Definition の対応がされていません。

そのためこれをそのまま使用するのではなく、fork して自分で手を加えて使用することにします。

github.com

変更した箇所は下記になります。

  • UPM 対応( package.json の作成)
  • Assembly Defintion の作成
  • DLL の Auto Reference の設定を false に変更*5
  • .proto ファイルからのソースコード生成ができる Editor 拡張の追加

gRPC のワークフローとして Protocol Buffers におけるプロトコルの定義ファイル .proto から、 protoc(Grpc.Tools)を使って C# のソースコードの自動生成をする必要がありますが、 コマンドがちょっと長いこともあり Editor 拡張で叩けるようなものを一応用意しました。

grpc-dotnet-unity を利用する際の注意点

grpc-dotnet-unity を使う上での注意点も挙げておきます。

  1. Best HTTP/2 という有料アセットが必要
    • 前述しましたがこちらの有料アセットが別で必要になります
    • これがないとコンパイルエラーになります
    • 有料アセットは GitHub に上げてしまうと再配布をしていることになってしまうため、.gitignore で管理外にするなど対処が必要です
  2. Server はサポートしていない
    • 現在は gRPC の Client のみサポートしています
  3. HTTP(http://)はサポートしていない
    • HTTPS(https://)、つまり TLS 対応がサーバー側で必要になります
  4. 全てのプラットフォームでの動作確認はまだされていない
    • Windowsでは自分も確認していますが、それ以外はまだ確認していません
    • C# 実装のため原理的には問題なく動作するはずです

grpc-dotnet-unity を利用した gRPC クライアントの導入手順

必要ものは用意ができたので、実際に導入するまでの手順を確認しておきます。

1. .proto ファイルの用意

.proto ファイルで自分が利用したい API(RPC) を定義します。

protobuf.dev

2. gRPC サーバーの作成

作成した .proto ファイルをベースに gRPC サーバーを作成します。

サーバー単体でも gRPCurl などを利用すれば Server Reflection の設定は必要ですが動作確認ができます。

また、Best HTTP/2 の都合で TLS の設定が必要になります。

様々な言語、フレームワークで作成できますが、本記事の趣旨からは外れるためここでは割愛します。

3. Unity プロジェクトの用意

gRPC クライアントとしての Unity プロジェクトを用意します。

必要なものは大きく2つあります。

  1. Best HTTP/2
  2. grpc-dotnet-unity (fork の方)

後者は下記のページに従って、UPM の設定でパッケージをインポートしてください。

github.com

4. Grpc.Tools の用意

.proto ファイルから C# のソースコードを自動生成するのに必要な Grpc.Tools を用意します。

www.nuget.org

NuGet の「Download package」からファイルを直接ダウンロードし、拡張子を無理やり .zip に書き換えて ZIP 展開します。

展開した tools フォルダに入っている下記の2つの実行ファイルを使用します。

  • protoc.exe
  • grpc_csharp_plugin.exe

これを適当な場所に置きます。

後で Editor 拡張でこれらの Path を指定しますが、もしコマンドを自分で叩く場合には Path を通しておきます。

5. .proto から C# のソースコードの自動生成

3-2 でインポートしたパッケージには、自分が作成した Editor 拡張が含まれています。

これを使うと Unity Editor 上で.proto から C# のソースコードの自動生成ができます。

設定は二か所に分かれています。

  1. Menu > Preferences > gRPC.NET で Grpc.Tools に入っていた protoc.exegrpc_csharp_plugin.exe の Path を指定する
  2. Menu > Window > Mochineko > gRPC.NET > Protobuf Source Generator で 出力先のフォルダと対象の .proto ファイルを指定する

これらを入力したのち、「Generate source code from .proto file...」のボタンを押すと、ソースコードの生成のコマンドが実行されます。

もし不具合や改善案などありましたら Issue まで報告していただけると嬉しいです。

Editor 拡張を使用せずに自分でコマンドを叩いて生成する場合は下記のメモを参考にしてください。

zenn.dev

6. gRPC クライアントの RPC 呼び出しの実装

自動生成した C# のソースコードを使用して、gRPC クライアントの RPC 呼び出しを実装します。

具体例は後で貼りますが、ここでは流れだけ書いておきます。

  1. gRPC サーバーのアドレス(https://xxx:yyy)と GRPCBestHttpHandler を使用して、GrpcChannel を取得する
  2. GrpcChannel のインスタンスから、API の Client クラスを new で生成する
  3. API の Client クラスから使用したい RPC を呼び出す
  4. gRPC クライアントのロジックを実装する

Unary の RPC の場合には 4 は不要で、シンプルに呼び出すだけでよいです。

使用例

Unity 側の実装のイメージがもう少し分かりやすくなるよう、使用例を紹介します。

例として、サーバー側で ChatGPT の API を使って会話ができる WebAPI(RPC)を考えてみます。

.proto ファイル

.proto ファイルで API 仕様を定義します。

今回はシンプルな REST API に近い形の Unary な RPC と、REST API の Server-Sent Streaming に近い形の Server Streaming の RPC の二つを考えます。

syntax = "proto3";
package chat;

service Chat {
    rpc CompleteChat (ChatRequest) returns (ChatResponse);
    rpc CompleteChatStreaming (ChatRequest) returns (stream ChatStreamingResponse);
}

message ChatRequest {
    string message = 1;
}

message ChatResponse {
    string response = 1;
}

message ChatStreamingResponse {
    string delta = 1;
}

gRPC サーバーの実装

サーバーは何で実装しても良いのですが、既に Rust / tonic を使用したものを作成していたのでこれをそのまま利用します。

こちらは作成途中だったり余計なものが入っていたりするので今回は解説はしません。

github.com

gRPC クライアントの実装

前節で説明した流れを見ながら実装例をご覧ください。

  1. gRPC サーバーのアドレス(https://xxx)と GRPCBestHttpHandler を使用して、GrpcChannel を取得する
  2. GrpcChannel のインスタンスから、API の Client クラスを new で生成する
  3. API の Client クラスから使用したい RPC を呼び出す
  4. gRPC クライアントのロジックを実装する
#nullable enable
using System;
using System.Threading;
using System.Threading.Tasks;
using GRPC.NET;
using UnityEngine;
using Grpc.Net.Client;

namespace Chat.Client
{
    internal sealed class ChatClient : MonoBehaviour
    {
        private readonly GRPCBestHttpHandler handler = new();
        private GrpcChannel? channel;
        private readonly CancellationTokenSource cancellationTokenSource = new();

        private void Start()
        {
            // 1: 
            channel = GrpcChannel.ForAddress("https://127.0.0.1:8000", new GrpcChannelOptions()
            {
                HttpHandler = handler,
            });
        }

        private void OnDestroy()
        {
            cancellationTokenSource.Dispose();
            handler.Dispose();
            channel?.Dispose();
        }

        [ContextMenu(nameof(CompleteChat))]
        public void CompleteChat()
        {
            CompleteChatAsync(cancellationTokenSource.Token);
        }

        private async Task CompleteChatAsync(CancellationToken cancellationToken)
        {
            try
            {
                // 2:
                var client = new Chat.ChatClient(channel);
                // 3:
                var response = await client.CompleteChatAsync(
                    new ChatRequest
                    {
                        Message = "あなたのことを教えてください。"
                    },
                    cancellationToken: cancellationToken);

                // Responseの利用
                Debug.Log(response.Response);
            }
            catch (RpcExceptione)
            {
                Debug.LogError(e);
            }
        }
        
        [ContextMenu(nameof(CompleteChatStreaming))]
        public void CompleteChatStreaming()
        {
            CompleteChatStreamingAsync(cancellationTokenSource.Token);
        }

        private async Task CompleteChatStreamingAsync(CancellationToken cancellationToken)
        {
            try
            {
                // 2:
                var client = new Chat.ChatClient(channel);
                // 3:
                var response = client.CompleteChatStreaming(new ChatRequest
                {
                    Message = "あなたのことを教えてください。"
                });
                // 4:
                var headers = await response.ResponseHeadersAsync;
                // Headerの利用

                while (await response.ResponseStream.MoveNext(cancellationToken))
                {
                    // ResponseStreamの利用
                    Debug.Log(response.ResponseStream.Current.Delta);
                }
            }
            catch (RpcExceptione)
            {
                Debug.LogError(e);
            }
        }
    }
}

あとはサーバーを立ち上げた状態で実行してみて動作するか確認してみましょう。

使い方はシンプルなので流れが分かればあまり迷わないかと思います。

Stream の受け取り部分 while(...) の部分は実際には UniTask や UniRx などで丁寧に処理したほうが良いでしょう。

var client = new Chat.ChatClient(channel); の部分以降は実際の .proto によって自動生成されたコードに依存しますので、 ご自身で作っているものに置き換えてください。

細かいところだと CancellationToken にもしっかり対応していますし、 エラーも基本的には RpcException で返ってきてその中に gRPC の Status Code も入っていますのでハンドリングには困らなさそうです。

RPC のタイプとしては他に Client Streaming、Bidirectional Streaming もあり双方向にインタラクティブな API を作ることもできます。 *6

まとめ

今回の記事のまとめになります。

  • Unity で gRPC を使うには Grpc.Core と grpc-dotnet を使う二通りの選択肢があります
    • Grpc.Core は実績がありますが既にサポートが切れています
    • grpc-dotnet は新しい実装ですが、そのままでは Unity では使用できません
  • grpc-dotnet と Best HTTP/2 という有料アセットを組み合わせて使用する grpc-dotnet-unity があります
    • 元の Repository ではなく fork して UPM 対応したものを利用するのがおすすめです
    • 本記事ではこちらを利用しました
  • Unity で grpc-dotnet-unity を使って gRPC を導入するまでの一連の流れをざっと解説しました

ツールとしては、

  • grpc-dotnet-unity(の fork の UPM)
  • Best HTTP/2 の有料アセット
  • Grpc.Tools

の三つを利用して Unity にも比較的お手軽に gRPC の導入ができるようになります。

github.com

おわりに

少し長くなりましたが、今日 Unity に gRPC を導入する際の選択肢と、その一例として grpc-dotnet を利用する方法、gRPC 導入の一連の流れとサンプルを紹介しました。

gRPC の導入の是非の技術選定の参考や、実際の導入手順のガイドになっていれば嬉しいです。

とはいえ自身もまだ gRPC 入門したてのため、もし間違っている記述がありましたらご指摘ください。

個人的には REST API より Protobuf でシンプルに API 仕様を定義でき、Bidirectional Streaming で柔軟に対応できるのがとても好みで使いどころがあれば使っていきたいと考えています。

直近だと LLM backend な AI Agent は会話や Function の実行をインタラクティブに実装したくなる都合で gRPC が相性がいいのではと考えていて試しに実装してみているところです。

*1:MagicOnion は少し特殊な gRPC の使い方をしていて、通常の .proto を使用する運用の参考にはならないかもしれません

*2:https://twitter.com/toRisouP/status/1252881783634268170?s=20

*3:Windowsのみ限定的に利用できるようです -> https://learn.microsoft.com/ja-jp/aspnet/core/grpc/netstandard?view=aspnetcore-7.0#net-framework

*4:もちろん Unity が .NET 6/7 をサポートしてくれるのがベストなのですが、いつになることか...

*5:DLLのコンフリクトを予防するため

*6:Bidirectional の実装例:https://github.com/mochi-neko/llm-agent-prototype