5日間で同期メンバーとgRPCを使ったリアルタイム通信ゲームを作ってみた

# はじめに
`#おうちハッカソン`というものに同期と参加して、gRPCを使ったリアルタイム通信ゲームを作りました。

これは、ゲームクライアントを作った人間の視点での話になります。私はサーバサイドを担当していないので、サーバサイドをやってくれた人が記事を投稿してくれることを期待しています。

## 自己紹介
- Twitter [@AblerBiri](https://twitter.com/AblerBiri)
- Github [Gamu2059](https://github.com/Gamu2059)
- サイバーエージェント21年度入社予定
- Unity、ゲームクライアント系がメインフィールドのエンジニア
- プログラミング歴だけだと7年くらい

# おうちハッカソン
https://connpass.com/event/174573/
[@at_sushi_at](https://twitter.com/at_sushi_at)さんが主催されたハッカソンです。

内定者同士でリモート飲み会をした時に、「なかなか交流できないからハッカソンとかやって交流したいね」という意見が出ていました。
ちょうどその時にこのハッカソンが開催されるということで、これは参加するしかない！と勢いで参加を決めました。

ハッカソン開始前に内定者同士でチームが編成され、私のチームはクライアント2人、サーバ2人のチームとなりました。

お題は、**お家で楽しめるなにか**です。
楽しめるなにか、という時点で私にはゲームを作る以外の考えはありませんでした。というか、どんなお題でも多分ゲームにこじつけるつもりでした。

## 作ったゲーム
今回作ったゲームこんな感じです。
![スクショ1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225590/47d2ee97-6233-7fe8-3999-33bca1179449.png)
![スクショ2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225590/4d5d28c5-4a1e-9944-4fe9-1032f7cb3e2d.png)
![スクショ3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225590/644f00ec-248b-a9b3-aaca-10ad7cf5d85e.png)
![スクショ4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225590/cada41e6-3f2f-7161-526a-cce4b65a66fa.png)

これ、いわゆるRoll a ballなのですが、一つだけ大きな違いがあります。
それは、**一人ひとりの玉を転がす方向が違う**ということです。

スクリーンショットでは右下の十字キーの下キーだけ赤くなっていますが、これはこの画面のプレイヤーは下方向にしか操作できないことを意味しています。他の3人の画面では別々の方向のキーが赤くなっています。

落ちそうになった時がもうメチャクチャ楽しいです。

# ハッカソン中
さて、Direction Bowlを作る工程がどんな感じだったかを振り返っていきます。

## 1日目
### アイデア出し
お題が発表され、何を作るかのアイデア出しを始めました。

アイデア出しに際して、以下の点に制限を設けてキーワード等を挙げていきました。

- 通信要素を取り入れること。
- 倫理とか権利とかに抵触するのは嫌なので、キーワードは**あえて**コロナ関連以外にすること。
- 対戦型では通信ラグが防げなかった場合の勝敗判定や座標修正の難易度が高いので、協力型のゲームにすること。

アイデア出しは以下のように行いました。

1. メンバーが自由にゲームに使えそうなキーワードを挙げていく。
2. キーワードに他のメンバーが追加でアイデアを出したり、似たような実在するゲームを挙げたりして候補を増やしていく。
3. 実現できそうなもの、作っても大丈夫そうなものだけを選択して、そこから再びアイデアを連想していく。

という方法で、メンバーのアイデアを統合して「4人で協力する玉転がしゲーム」がテーマとして決定しました。

### 技術選定
今回のハッカソンはそれぞれの勉強のためという目的もあったので、メンバーが使っていなかった技術を使うことにしました。

メンバーがgRPCを触ってみたいということで、gRPCを通信に用いることにしました。gRPCをUnityで使えることを知っていたメンバーがいたのでラッキーでした。
クライアントの開発はUnityを使うことにしましたが、これは私がUnity枠で参加したためそうなりました。

### 通信フロー
以下にこのゲームでの通信フローを紹介します。

部屋を作成したり部屋へ参加したりする部分のフローは以下のようにしました。
![LobbySeq.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225590/3184b848-f06b-8da3-7141-9ed1236b32d8.png)

1.クライアントがCreateRoomRequestでサーバとコネクションを張る。このクライアントが親クライアントになる。
2.サーバは親クライアントに対してCreateRoomResponseを送る。
3.クライアントがJoinRoomRequestでサーバとコネクションを張る。このクライアントが子クライアントになる。
4.サーバは同じ部屋にいる全てのクライアントに対してJoinRoomResponseを送る。
5.部屋から子クライアントが抜けた場合も、部屋に残っている全てのクライアントに対してJoinRoomResponseを送る。
6.部屋に4人そろったら同じ部屋にいる全てのクライアントに対してReadyResponseを送る。

コネクションを張る際にServer-side streamingでレスポンスされるようにしているので、サーバからの一方的に送られてくるデータも受け取れる仕組みになっています。

インゲームのフローは以下のようにしました。
![InGameSeq (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225590/f74f0e96-f84e-d287-09f4-71eaa55f0bf8.png)

1.子クライアントはプレイヤーの入力を取得して、自身に割り当てられた方向と共にChildOperationとしてサーバに送信する。
2.サーバは子クライアントの入力をそのまま親クライアントへとリレーする。
3.親クライアントは3人分の入力データと自身の入力データを合成して、玉に加速度を与える。
4.親クライアントは玉の座標と開始からの経過時刻をCoordinateSharingとしてサーバに送信する。
5.サーバは親クライアントのデータをそのまま子クライアントへとリレーする。
6.子クライアントはサーバから送られてきたデータを使って自身の玉の座標を更新する。

ChildOperationとCoordinateSharingはそれぞれBidirectional streamingによってデータを送受信しています。
元々ChildOperationはClient-side streamingで実装していましたが、後になって変更しました。(後述)

## 2日目
私はゲームジャムの投稿作品の仕上げを行っていたため、もう一人のクライアントメンバーにprotoビルドやgRPCの通信処理部分をお願いしました。私はgRPCについてQiitaで調べたり、書いてもらったコードを読んだりして勉強しました。

ここはもう本当にメンバーに感謝でいっぱいです。私が動けない間に進めてくれてありがとうございました。

## 3日目
メンバーがgRPCの通信処理をまとめたクラスを実装していたので、私はgRPCの勉強をしつつゲームロジック側の実装に専念することにしました。

クライアント-サーバ間のAPIは定義出来ていましたが、通信クラスとゲームロジック間のインタフェース定義が曖昧だったので、この日に定義しました。

そして、午後になって通信クラスとゲームロジックの統合を行いました。

## 4日目
クライアント-サーバの連携をテストしました。

部屋作成のリクエストの時点でもなかなかサーバとの連携がうまく取れずに苦戦しました。
インゲームのリアルタイム通信部分のテストをしている時に、Client-side streamingを使うとリクエストのストリームが終わらないとレスポンスが来ないという問題に当たり、この問題の切り分けと対応に4時間掛けてしまいました。

**Bidirectional streamingで通信している部分と記述は全く同じなのに挙動が違う**というのは非常にややこしい問題でした。

最低限必要なゲームロジックや通信処理の実装が済んだので、4人で操作感を楽しんでこの日は終了しました。

私はUIの実装などを残していたので、手が空いたメンバーにはフォントやサウンドを選んでもらいました。

## 5日目
午前中にステージのモデルをblenderで作成してステージを構築し、PostProcessingのBloomを使ってエモい雰囲気の世界に仕上げました。やっぱPostProcessingって万能調味料みたいですよね。

そしてフォントを適用してサウンドも追加して完成しました。

# ハッカソンの結果
ハッカソンではベストエンジニアリングと最優秀賞を頂きました。

技術面ではほぼ遅延なくリアルタイム通信を実装できたことが評価されたのだと思います。
クライアントサイドの遅延に対する工夫としては、

- プレイヤーの入力がない時は子クライアントからデータを送信しない。
- 親クライアントから送信するデータは時刻と座標だけ。
    - 回転は不要と判断して入れなかった。子クライアントでは回転しないのをごまかすために玉を真っ白にした。
- プレイヤーの入力を玉の加速度に対して適用することで、万が一遅延してもごまかしが利くようにした。

という感じです。

# さいごに
gRPCを今まで使ったことが無かったのでとても勉強になりました。結局私は深くgRPCに触れませんでしたが、これを機にもっと勉強しようと思います。また、Magic OnionというC#だけでクライアントとサーバを実装できるものがあるので、今後はサーバサイドも挑戦していきたいなと思います。

今回のハッカソンと並行してUnity1週間ゲームジャムに参加していたので、1週間以上ゴリゴリにゲームを実装してとても疲れました。段々疲れが溜まって終盤は頭が悪い実装ばかりになってしまいました。恥ずかしいのでコードはあまり見られたくない...

それと、内定者同士の交流という意味でも今回のハッカソンはとても有意義だったと思います。協力型ゲームということで開発中も一緒に楽しみながらテストプレイできました。

せっかくなのでDirection Bowlはunityroomなどにアップロードして残しておきたいなあという気持ちはあったのですが、gRPCがWebGLビルドに標準では対応していないので、現状は一般公開されていません。しかし、今後時間があればメンバーと追加実装してリリースまで漕ぎ着けたいところです。

このような楽しいハッカソンの機会を設けて下さった[@at_sushi_at](https://twitter.com/at_sushi_at)さん、そして5日間一緒に開発してくれたメンバー、バリエーション豊かな作品を作ってくれた同期達に感謝します。