GodotでMagicOnionやってみる

こちらは [Applibot Advent Calendar 2023](https://qiita.com/advent-calendar/2023/applibot) 18日目の記事になります。前回の記事は[コチラ](https://zenn.dev/smotoshei/articles/a8b46d3f1af18d)です。


## はじめに
Cysharp 河合さんのポストを見て自分でもやってみようと思ったので手順を紹介します。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">GodotでMagicOnion動きました！特に何もせずNuGetで配ってるパッケージまんまで動作してます。そう、MagicOnionなら特定プラットフォーム依存がないので、いざという時も大丈夫ですよ……！ <a href="https://t.co/sAZvGYoILB">pic.twitter.com/sAZvGYoILB</a></p>&mdash; neuecc (@neuecc) <a href="https://twitter.com/neuecc/status/1704010833930625426?ref_src=twsrc%5Etfw">September 19, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

※初めてGodotを触ります。Godot自体の説明はしません。

## プロジェクト作成
### サーバー
基本的にはMagicOnionのREADMEにある[Quick Start](https://github.com/Cysharp/MagicOnion?tab=readme-ov-file#quick-start)通りにやっていきます。

Web Applicationテンプレートでソリューションとプロジェクトを作成します。
※画像はRiderで作成する場合のものです。

![Screenshot 2023-12-18 at 9.34.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/893255/94092837-b85b-2295-bcc6-55790224fb49.png)

### 共通
続いてサーバーとGodot側共通で使いたいもの（interfaceなど）用にプロジェクトを追加します。
先ほど作成したソリューションにClass Libraryテンプレートでプロジェクト追加します。
Godot側でも使うためにFramework部分はnet6.0を指定してください。

![Screenshot 2023-12-18 at 9.49.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/893255/718ee7aa-c826-7087-11c5-324bc8f30865.png)

サーバープロジェクトに共通プロジェクトへの参照を追加します。
```xml:TamanegiServer.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
    <ItemGroup>
      <ProjectReference Include="..\TamanegiShared\TamanegiShared.csproj" />
    </ItemGroup>
</Project>
```

### Godot
ソリューションを作成したディレクトリ直下にGodotプロジェクトを作成します。

![Screenshot 2023-12-18 at 9.55.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/893255/c7e5a5db-9a2b-40b1-cb0d-fedb240a17f9.png)
※既に作成済みのため画像では空のディレクトリを指定するように怒られています。

Godotプロジェクトで適当なC#スクリプトを作成後、Godotプロジェクト直下のソリューションをエディタで開いて先ほど作成した共通用プロジェクトをソリューションに追加、Godotプロジェクトに共通プロジェクトへの参照を追加します。
```xml:TamanegiGodot.csproj
<Project Sdk="Godot.NET.Sdk/4.2.1">
    <ItemGroup>
      <ProjectReference Include="..\TamanegiShared\TamanegiShared.csproj" />
    </ItemGroup>
</Project>
```

## Sharedプロジェクト
NuGetで`MagicOnion.Abstractions`を追加します。
```xml:TamanegiShared.csproj
<Project Sdk="Microsoft.NET.Sdk">
    <ItemGroup>
      <PackageReference Include="MagicOnion.Abstractions" Version="5.1.8" />
    </ItemGroup>
</Project>
```

READMEを参考にinterfaceを定義します。
```csharp:ITamanegiService.cs
using MagicOnion;

namespace Tamanegi.Shared;

public interface ITamanegiService : IService<ITamanegiService>
{
    UnaryResult<int> SumAsync(int x, int y);
}
```

## サーバープロジェクト
NuGetで`Grpc.AspNetCore`と`MagicOnion.Server`を追加します。
```xml:TamanegiServer.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
    <ItemGroup>
      <PackageReference Include="Grpc.AspNetCore" Version="2.59.0" />
      <PackageReference Include="MagicOnion.Server" Version="5.1.8" />
    </ItemGroup>
</Project>
```

Macの場合README通りでは通信できない場合があるので
[MacOS で ASP.NET Core gRPC アプリを起動できない](https://learn.microsoft.com/ja-jp/aspnet/core/grpc/troubleshoot?view=aspnetcore-7.0#unable-to-start-aspnet-core-grpc-app-on-macos)を参考にProgram.csのConfigureKestrel箇所を追記してください。
```csharp:Program.cs
using MagicOnion;
using MagicOnion.Server;
using Microsoft.AspNetCore.Server.Kestrel.Core;

var builder = WebApplication.CreateBuilder(args);

// macOSで開発時に必要
builder.WebHost.ConfigureKestrel(options =>
{
    // portはサーバー起動時のものを指定してください、launchSettings.jsonを参照
    options.ListenLocalhost(5001, o => o.Protocols = HttpProtocols.Http2);
});

builder.Services.AddGrpc();
builder.Services.AddMagicOnion();

var app = builder.Build();

app.MapMagicOnionService();

app.Run();
```

```csharp:TamanegiService.cs
using MagicOnion;
using MagicOnion.Server;
using Tamanegi.Shared;

namespace Tamanegi.Server;

public class TamanegiService : ServiceBase<ITamanegiService>, ITamanegiService
{
    public async UnaryResult<int> SumAsync(int x, int y)
    {
        Console.WriteLine($"Received:{x}, {y}");
        return x + y;
    }
}
```

## Godotプロジェクト
NuGetで`MagicOnion.Client`を追加します。
```xml:TamanegiGodot.csproj
<Project Sdk="Godot.NET.Sdk/4.2.1">
    <ItemGroup>
        <PackageReference Include="MagicOnion.Client" Version="5.1.8" />
    </ItemGroup>
</Project>
```

以下のC#スクリプトを適当なNodeにアタッチしてください。
```csharp:TamanegiClient.cs
using Godot;
using Grpc.Net.Client;
using MagicOnion.Client;
using Tamanegi.Shared;

public partial class TamanegiClient : Node
{
	public override async void _Ready()
	{
		var channel = GrpcChannel.ForAddress("http://localhost:5001");

		var client = MagicOnionClient.Create<ITamanegiService>(channel);

		var result = await client.SumAsync(123, 456);
		GD.Print($"Result: {result}");
	}
}
```

サーバーを起動後にGodot側を実行すると無事サーバーと通信できました！

![Screenshot 2023-12-18 at 10.53.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/893255/ec72f324-6704-117c-0e10-6d7e7784af10.png)


## おわりに
GodotでMagicOnionを動作させることができました！

Unityの場合はIL2CPPのためにmoc（MagicOnion用）、mpc（MessagePack用）のツールで事前にコード生成する必要があったり、Unity用のGrpc.CoreパッケージをGitHubに上げるためにサイズ削減したり、Appleシリコン版Macで動作させるために[YetAnotherHttpHandler](https://github.com/Cysharp/YetAnotherHttpHandler)を導入する必要があったり細かい部分で煩わしさがありましたが、Godotではそれらと比べると簡単に導入できると感じました。

以上、[Applibot Advent Calendar 2023](https://qiita.com/advent-calendar/2023/applibot) 18日目の記事でした。