【UGDGアドカレ】Unity + MagicOnion + Docker でミニアプリ制作ぼっちRTA

# はじめに

本記事は [Unityゲーム開発者ギルド Advent Calendar 2021](https://adventar.org/calendars/6326) の14日目の記事です。
昨日は Seaeeesさんの[モデモデな一年を振り返る](https://note.com/seaeees/n/n4571fb7a6f69) でした！

https://adventar.org/calendars/6326

メリークリスマス！みなさん Unity してますか？
今回は、**Docker も MagicOnion も そもそもサーバサイドも未経験の私が、付け焼き刃の知識で簡単なサービスをどれくらいで作れるか**という遊びを記したものです。 RTA とは言いつつ、誰かと競うわけでも急いで作るわけでもありません。のんびり気ままに独りぼっちで作ります。
技術的なことも書きますが、この記事から得るものはあまりないと思うのでご了承を。

※もう一度言いますが、サーバ・通信・Docker など未経験なので記事の内容を**鵜呑みにしないでください。**

# 経緯

遥か昔大学生の頃、Unity の Photon(PUN2) を勉強したことがありました。
そして同時期に友人が「早押しボタンのWebサービス制作」を RTA でやっていたので自分も Unity と Photon で同じことをやってみました。

![screenshot_1639594952_LI (2).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260914/3d69a286-1707-c9d6-15a8-1577cbe62b85.jpeg)

(上の画像はRTAを配信している様子。タイムは1時間8分11秒)

なんか以外に早くできたし楽しかったので久ぶりに **ぼっちRTA** やるか～～って思って始めた感じです。
目標として

- 何か新しいことを学んでそれを使って作品を作る
- オンライン(ローカルホストでも可)のものを作る

以前は Photon 、そして今回は MagicOnion を使ってみることにしました。
あとなんとなく Docker と GCP 使ってワンチャンオンライン化できないかな～と思って Docker も初めて使ってみます。(GCPは断念したのでローカルホスト)

# 作ったもの

早速ですが作ったものです。(https://github.com/euglenach/OnlineVote)
~~(クソコード量産したので見せるのめっちゃ恥ずかしい)~~

年末ということで早押しボタンみたいな何か遊びで使えるツールみたいなのが作りたいと考え、今回は『**オンライン投票アプリ**』を作りました。**マジョリティゲーム**みたいなパーティゲームで遊ぶことを想定します。
別に急いで作るわけではないですが、UI とかは全然綺麗にしようとか思ってないのでそこはご勘弁を…

## ① ルーム名・ユーザ名・質問者 or 解答者 を入力して部屋に入る/作る

![ScreenShot_21-12-15-53-54-4594.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260914/7e48b41b-4907-b967-193b-90a3ff52c01f.png)

## ② 質問者は質問と選択肢を入力する

![ScreenShot_21-12-15-55-46-25668.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260914/7200a738-6312-6edf-9aa9-9225cdd585a2.png)

とりあえず選択肢はカンマ区切りにし、最大4つまで登録できるようにしました。

## ③ 回答者は質問が来たら選択肢のボタンを押す

![screenshot_1639502164.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260914/980afaf9-46b6-9043-6be3-d6ff57cfdeb5.png)

## ④ 質問者は投票が来るまで待つ

![ScreenShot_21-12-15-16-14-18219.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260914/093caf5c-f375-90d5-8eae-bfffe0db79a9.png)

## ⑤ 集計するボタンを押したら結果を表示する

![ScreenShot_21-12-15-07-08-18541.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260914/0c392894-0c2e-0853-b05a-18bb2562c6ed.png)

はい、今回できたものがこれです、これだけです。
質問してアンケート取って集計するだけ。
仕様は簡単です。これくらいで良いんですええ。

僕は MagicOnion と Docker が使えて私は満足です。

# 結果

開始時間と終了時間は以下のテキストファイルに書き込みgitにコミットしてその時間で計測します。

![screenshot_1639505807.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260914/481eddf2-f5b1-4f3a-00da-1eb93125a624.png)

結果は....

開始時間 12月12日 22時14分

![screenshot_1639506366.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260914/6741eefe-3582-2995-f015-404891d2d022.png)


終了時間 12月13日 13時28分

![screenshot_1639506345.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260914/be52b637-dbb4-e038-daa5-b688e8b3bcf7.png)

<FONT SIZE=6 COLOR=red>$\huge{＿人人人人人人人人人＿}$</FONT>
<FONT SIZE=6 COLOR=red>$\huge{＞　15時間14分　　＜}$</FONT>
<FONT SIZE=6 COLOR=red>$\huge{￣Y￣Y￣Y￣Y￣Y￣}$</FONT>

いやなげえ！！！意味が分からん！！！！！！！！！

こんな亀のようにノロマになった理由として

- 長くても3時間くらいで終わるな、と思って夜に始めたら意外と時間がかかって何度か寝落ちした
- 途中で VALORANT の世界大会の決勝が始まって普通に見ちゃった
- 久しぶりに VContainer を触ってちょっと混乱した
- あろうことか1箇所、 IObservable\<T>.Subscribe に対して AddTo してないことに気づかなくて、ずっとゲームループでバグが出ていた

などなど考えられる要因はいくつもありますが、最近はあんまり趣味プロしてなかったのでつけが回ってきましたね...

AddTo 忘れとか、手癖で Subscribe の後ろに書いてたのになぜか1か所だけ出来てなくて。何年も触っててもヒューマンエラーは起きるんだから RoslynAnalyzer を導入しようと心に決めました。**人は寝ないとダメだ**。

# 環境

ここからは技術的な話をします。

クライアントとサーバの構築は [Unity + .NET Core + MagicOnion v3 環境構築ハンズオン](https://qiita.com/_y_minami/items/db7b19eb5979ef1d6fe9) の記事を参考にさせていただきました。ありがとうございます。

- クライアント
  - Unity 2020.3.24f1
  - MagicOnion 3.0.12
  - MessagePack 2.1.152
  - gRPC 2.23.0
- サーバ
  - MagicOnion.Hosting 3.0.12
  - MagicOnion.Abstractions 3.0.12
  - MessagePack.UnityShims 2.1.152
  - MagicOnion.MSBuild.Tasks 3.0.12
  - MessagePack.MSBuild.Tasks 2.1.152

クライアント、コードシェア、サーバは全部一緒の gitリポジトリ で管理しました。

![screenshot_1639580663.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260914/2917b87a-dadb-a11b-fbb5-05baadfbe266.png)

# MagicOnion を使ってみて

今回サーバコードなんてほとんど書いてないですけど、C# でサーバを書くという今までにない体験ができて感動しました。
業務でもサーバを書くと言ってもリアルタイム通信は書いたことなかったので楽しませてもらいました。

初めて使ったので躓いたこともありました。

このゲーム(?)には大きく分けて2つの通信の処理を書く必要があります。

**マッチング** と **インゲーム** です

最初はサーバでもクライアントでもクラスを分けて書いてました

```c#:マッチングの通信まわりのクラス
class MatchingRPC : IMatchingReceiver{
    private IMatchingHub matchingHub;
    // 略...
```

```c#:インゲームの通信まわりのクラス
class QuestionRPC : IQuestionRecieiver{
    private IQuestionHub questionHub;
    // 略...
```

そしてサーバにはこんなクラスがいるわけですね。
プロMagicOnionチュートリアラーにはお馴染みだと思います。

```c#:MatchingRPCクラスと通信するサーバのやつ
public class MatchingHub : StreamingHubBase<IMatchingHub,IMatchingHubReceiver>, IMatchingHub{
    IGroup room;
    
    // 略...
```

```c#:QuestionRPCクラスと通信するサーバのやつ
public class QuestionHub : StreamingHubBase<IQuestionHub,IQuestionHubReceiver>, IQuestionHub{
    // 略...
```

しかしこうすると MatchingHubクラス だけが知っている `IGroup` に QuestionHubクラス がアクセスできずブロードキャストができない。どうすんだこれはと。途中でそれを解決しなきゃいけないことに気づいたのです(遅い)
1,2時間くらいどうするか考えましたが解決には至らず。。。結局この2つを結合することになりました。
そしてこんな StreamingHub / Receiver が爆誕するのでした...

```c#
using System.Threading.Tasks;
using MagicOnion;
using ServerShared.MessagePackObjects;

namespace ServerShared.StreamingHubs{
    public interface IGameHub : IStreamingHub<IGameHub,IGameReceiver>{
        /// <summary>
        /// ゲームに接続することをサーバに伝える
        /// </summary>
        Task JoinAsync(string roomName,Player player);
        
        /// <summary>
        /// ゲームから切断することをサーバに伝える
        /// </summary>
        Task LeaveAsync();
        
        /// <summary>
        /// 質問したことをサーバに伝える
        /// </summary>
        Task QuestionAsync(Question question,Player player);
        
        /// <summary>
        /// 選択肢を選んだことをサーバに伝える
        /// </summary>
        Task SelectAsync(int index,Player player);
        
        /// <summary>
        /// 集計結果をサーバに伝える
        /// </summary>
        Task ResultAsync(QuestionResult questionResults,Player player);
    }

    public interface IGameHubReceiver{
        /// <summary>
        /// 誰かがゲームに接続したことをクライアントに伝える
        /// </summary>
        void OnJoin(Player player);
        
        /// <summary>
        /// 誰かがゲームから切断したことをクライアントに伝える
        /// </summary>
        void OnLeave(Player player);
        
        /// <summary>
        /// 質問したことをクライアントに伝える
        /// </summary>
        void OnQuestion(Question question);
        
        /// <summary>
        /// 選択肢を選んだことをクライアントに伝える
        /// </summary>
        void OnSelect(int index);
        
        /// <summary>
        /// 集計結果が来たことをクライアントに伝える
        /// </summary>
        void OnResult(QuestionResult questionResults);
    }
}

```

あとちょっと迷ったのはサーバから呼ばれるクライアントのメソッドの扱い方とかですね。

`I~~HubReceiver` しか それを受け取れないので、そのメソッドの中で `IObserver<T>.OnNext` するなり `Pub/Sub` 使ったりなんなりするんだと思います。
上記の `IGameHubReceiver` の例で言うと、これを実装したクラスで `resultStream.OnNext();` みたいなのを全部のメソッドに書きました。
それともう一つ、よくあるチュートリアルのままでいくと、`IGameHubReceiver` を外部に公開することになってしまうのでインナークラスに隠ぺいして IObservable\<T> をそのまま横流しするようなことをしました。全部。

ここに書くと長いのでコードは下のリンクを踏んでください。

https://github.com/euglenach/OnlineVote/blob/main/Client/Assets/Scripts/Games/GameRPC.cs

これだと Pub/Sub の方が楽だったなぁと今になって思います。
MessagePipe とかほとんど触ってないのでやってみてもありだったなぁ。。。



# Docker を使ってみて

Docker。まったく触ってこなかった Docker。やっとちょっとかじってみました。

これは Docker を触る前のなんもわからん僕の様子

![screenshot_1638986514_L.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260914/64aa7a40-c14a-9e21-073b-8be0837b7fe4.jpeg)

この RTA をやる前にちょっとだけやってみて、四苦八苦しながら Build → Run をすることができました。
めっちゃうれしかった。ギルドの Slack にもそのよろこびをあらわにします。

![screenshot_1638986483_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260914/6317c006-25ad-4486-5e7e-88f58cc2b592.jpeg)

RTA でも 同じことしかしてないのでオマケ程度ですが、ほんとはオンライン化したかったですね。。。
いつくるかわからない次の RTA では絶対やりたいですね！目指せオンライン化！！

MagicOnionサーバ のコンテナ化については [Unity:MagicOnionの.NET CoreコンテナサーバーをAWS Fargateで実行する方法](https://qiita.com/simplestar/items/b1d29e986b0b2cd6af2b) を参考にさせていただきました。ありがとうございます。

ただ、自分の環境だとサーバが .Net 6 で動いているので DockerFile に以下の修正と追加が必要でした。

```dockerfile
FROM mcr.microsoft.com/dotnet/sdk:6.0

# ~~略

# (.Net 6 と gRPC のバージョンの相性が悪かったっぽい?)
RUN apt-get update && apt-get install -y libc-dev && apt-get clean

# ~~ 略
```

# おわりに

以上！こんな感じで RTA の記録は終わりです。
新しいことを触ってみるのはとても楽しいですね。またやりたいです。
「ここはこうだよ！！！」みたいなのがあれば、コメントを書いていただくか、ぜひ UGDG に参加して教えてください！！
UGDG はいいぞ

では💪💪💪💪💪

# 謝辞

参考にさせていただいた偉大なる先人たち

MagicOnionのサンプル(https://github.com/Cysharp/MagicOnion/tree/master/samples/ChatApp/ChatApp.Unity)
Unity + .NET Core + MagicOnion v3 環境構築ハンズオン(https://qiita.com/_y_minami/items/db7b19eb5979ef1d6fe9)
Unity:MagicOnionの.NET CoreコンテナサーバーをAWS Fargateで実行する方法(https://qiita.com/simplestar/items/b1d29e986b0b2cd6af2b)
C#のみを使って、今ソーシャルゲームアプリを作るとしたら(https://qiita.com/yoship1639/items/c54f6942d847f8374377)
Win10にWSL2とUbuntu 20.04をインストールする(https://astherier.com/blog/2020/07/install-wsl2-on-windows-10-may-2020/)
Windows 10 Home で WSL 2 + Docker を使う(https://qiita.com/KoKeCross/items/a6365af2594a102a817b)
gRPCのissue(https://github.com/grpc/grpc/issues/24153)