WebRTC 通信を介してじゃんけんができるサイトを作った。
6月に人生で初めてポーカーという遊びに触れて楽しくて、そのときP2P通信を使えばブラウザ間で完結するオンラインゲームって可能なんじゃないかと思ってやってみたくなった。(もしかして世にある Paper.io とかのブラウザゲームってそうなのか?)
いきなりポーカーは無理なのでじゃんけんを実装した。1v1のみ対応でかつ一歩間違えると通信が確立しなくなるのでおおよそ実用的ではないのだが、WebRTC を使ったオンライン対戦の PoC としては十分な出来なのではないか。
ちなみに実際にポーカーを不正できない形で実装するとしたら Mental Poker という問題に対処する必要がある。(ライブラリとして提供してるすごい人がいる。)今回のじゃんけんも、送信された内容を見ることができれば後出しで必ず勝つことができるが、軽く調べた感じだと見る方法はわからなかった。
6月から WebRTC について調べ始めたものの、実装に入れたのは8月からだった。出てくる単語の関係や、「自分」と「相手」で変わる部分と変わらない部分を把握するのにとても時間かかった。同じサイトを2ヶ月ほどずっとぐるぐる巡回してた。
ぐるぐる巡回してたサイト:
- WebRTC API - Web APIs | MDN
- A simple RTCDataChannel sample - Web APIs | MDN
- Firebase + WebRTC Codelab
- WebRTC Video Chat on Firebase
今回のサイトでは Cloud Firestore をシグナリングサーバーとして使っているためバックエンドを一切書く必要がなかったが、それでも通信を確立する部分に最も苦労した。「まず相手側の peerConnection.createAnswer()
の内容をもとに RTCSessionDescription
のインスタンスを作って setRemoteDescription()
して、相手側で setLocalDescription()
が呼ばれると icecandidate
イベントが発生するからシグナリングサーバーに candidate の情報を送ってそれをもとに手元側で~~~」という一連の流れがあるのだが、どこまで動いていてどこから動いていないのかを知るのが大変だった。(このあたりは既存コードをコピペして事なきを得たのでいまだによくわかってないが。)特に icecandidate
イベントが発生すべきところで発生しなかったときはかなりハマった。原因は DataChannel
が無い状態で createOffer
したことだった。(この stackoverflow 記事ではエラーが表示されるって書いてあるけどそんなものはなかった。 )
非同期処理のタイミングと React のレンダリングのタイミングは雰囲気でやってる。 useReducer
で1箇所 Cannot update a component while rendering a different component のエラーが出て対処する必要があったけどそれだけで済んだ。今回はイベントハンドラーの非同期処理と useEffect が干渉しそうな場面はなかったけど、もしそういう場面が出てきた場合対応できる自信はない。あと React 18 で batching の挙動が変わっているらしいので追々理解していきたいところ。
- React v18.0 – React Blog
- Automatic batching for fewer renders in React 18 · Discussion #21
- What happens when using this.setState multiple times in React component? - Stack Overflow
- Does React batch state update functions when using hooks? - Stack Overflow
以下雑多なメモや感想
- 自分と対戦相手両方が「もう一戦」ボタンを押したら state をリセットするとか、チャットを送信したあとにテキストボックスを空にするとかが地味に面倒。
- スマホ回線と家の回線は通信が可能だったけど、環境によって direct socket 通信ができないということはよくあるそうなので、そういったケースに対応するためには TURN サーバーを用意する必要がある。
- Firestore のリアルタイムアップデートはすごい。
onSnapshot()
を一度呼ぶだけで全部やってくれる。謎技術。 - Firefox だと同じブラウザの別タブだと通信が確立できない。Chrome はできる。
- TypeScript に救われた回数は数知れず。Vanilla JavaScript だったら永遠に終わっていなかった可能性がある。
お盆休みは連日1日中ずっと書いていた。コードを書いていてこれほどのフロー状態になったのは久しぶりで気持ちが良かった。道具箱の道具を1つ増やすことができた気がするし、普通に React の練習にもなった。今後も少しずつ機能を足していきたいという意思はある。(けどしばらくは読書や翻訳に重点を置く気がする。)
- 複数言語対応したい
- 複数人対戦に対応したい
- もうちょっとゲーム性のあるゲームを実装したい
書きながら思い出したリンク
個人で凄まじいサイトを作っている方のブログ。
ネットゲームの同期の話