マガジン

WebRTCでサーバーなし対戦は可能か:P2Pの現実

遊びをつくる

執筆にClaudeを使用しています。

二つの駒をつなぐ橋の構造を青写真で示し、対戦の役割分担を表したイラスト。
記事のテーマをもとに制作したAI生成イラスト。

「サーバー代がかからない対戦ゲームを作りたい」という動機でWebRTCのP2P通信に行き着く人は少なくありません。結論から言うと、WebRTCによるブラウザ間の直接通信は技術的に実現可能です。ただし「サーバーが一切不要」という理解は正確ではなく、接続を確立するまでの仕組みを理解しないまま実装を始めると、ローカルでは動くのに公開したら誰もつながれないという状況に陥ります。 この記事では、WebRTCのP2P通信がゲームに使えるかどうかを判断するために必要な知識—シグナリング・NAT越え・遅延特性・同期設計—を順に整理し、「どこまでサーバーレスにできるか」という問いに実用的な答えを出します。

この記事の目次
  1. P2Pとは何か、そして「サーバーなし」の誤解
  2. シグナリングとSTUN/TURNの役割を整理する
  3. ゲームの遅延特性とP2Pの相性
  4. 実装の現実:2人対戦ならどう始めるか
  5. P2Pを選ぶ基準と避けるべき状況
  6. 次の行動
  7. 参考資料

P2Pとは何か、そして「サーバーなし」の誤解

P2P(Peer-to-Peer)とは、中央のサーバーを介さず、クライアント同士が直接データをやり取りする通信方式です。WebRTCはブラウザにこの能力を持たせる標準規格で、動画チャットや画面共有に広く使われています。ゲームデータのやり取りにも同じ仕組みが応用できます。

しかし「直接通信」が始まるまでには、必ず仲介が必要です。二台のブラウザが互いのIPアドレスも知らない状態から通信を始めるには、最初に「私はここにいる」という情報を交換しなければなりません。これをシグナリングと呼び、何らかのサーバーを経由して行います。加えて、家庭用ルーターの多くはNAT(Network Address Translation)という仕組みで外部からの接続をブロックしているため、これを越えるためのSTUN/TURNサーバーも必要になります。

つまりWebRTCのP2Pは「ゲームデータの中継サーバーが不要」という意味では正しいのですが、「インフラが一切不要」ではありません。この区別を最初に押さえておくことが、設計判断の出発点になります。

シグナリングとSTUN/TURNの役割を整理する

シグナリング

WebRTCで接続を確立するには、二者間でSDPオファー/アンサーとICE候補(接続経路の候補情報)を交換する必要があります。このやり取りをシグナリングと呼び、WebRTCの仕様にはシグナリングの具体的な手段は含まれていません。WebSocket・HTTP・あるいは手動コピーペーストでも構造上は成立します。

小規模なゲームで開発コストを抑えたい場合、無料枠のあるFirebase Realtime DatabaseやFirestore、PeerJSのパブリックサーバー、あるいはYjsと組み合わせたsignaling-serverなどを使う選択肢があります。いずれも「ゲームデータを中継するサーバー」ではなく「接続のきっかけだけを仲介するサーバー」という位置づけです。運用コストはゲームデータ中継型に比べて大幅に小さくなります。

STUNサーバーとTURNサーバー

STUNサーバーは、NAT越えのために自分のグローバルIPアドレスとポートを調べるために使います。GoogleやCloudflareがパブリックなSTUNサーバーを無償で提供しており、帯域負荷が非常に小さいため、小規模なゲームであればパブリックSTUNで十分なことが多いです。

問題はTURNサーバーです。TURNはNATの種類によってP2P直接接続が不可能な場合に、全データを中継するサーバーです。企業ネットワーク・特定のモバイル回線・対称型NATの環境下ではTURNなしでは接続が確立しません。実際のユーザー環境では一定割合でTURNに頼る接続が発生するため、「TURNを用意しない」という選択は「その環境の人はつながれない」を意味します。TURNサーバーの自前運用は帯域コストがかかり、CoTURNなどのOSSで構築するか、Twilio・Xirsysなどのクラウドサービスを使うかの判断が必要です。料金や無料枠の条件は変動するため、公式サイトで最新情報を確認してください。

ゲームの遅延特性とP2Pの相性

WebRTCのデータチャネル(RTCDataChannel)は、テキストとバイナリデータを低遅延で送受信できます。UDPに近い非信頼性モードも選べるため、格闘ゲームのフレーム入力のように最新状態の素早い送信が優先される場面に向いています。

一方で、P2P通信の遅延はプレイヤー同士の物理的な距離に直接左右されます。サーバー中継型では東京のサーバーに全員が接続するため遅延が均一に近くなりますが、P2Pでは東京のプレイヤーと海外のプレイヤーの間で100〜200ms以上の往復遅延が生じることがあります。対戦ゲームで許容できる遅延はジャンルによって大きく異なります。

  • ターン制・パズル・カードゲーム:遅延が数百msでも体験に影響が小さく、P2Pと相性が良いです。
  • リアルタイムアクション・格闘:フレーム単位の精度が求められるため、遅延補償(ロールバックネットコードなど)の実装難度が上がります。P2P自体は使えますが、設計コストが高くなります。
  • RTS・協力型ゲーム:決定論的ロックステップ(全員の入力が揃ってから進める方式)と相性が良く、P2Pで実装されている事例も多いです。

作るゲームのジャンルと想定する同時接続人数・地域をはっきりさせてから、P2Pが適切かを判断することが重要です。

実装の現実:2人対戦ならどう始めるか

2人対戦のシンプルなゲームを最短で動かしたい場合、PeerJSというライブラリが選択肢になります。PeerJSはWebRTCのAPIをラップして、シグナリングとSTUNの処理を隠蔽してくれます。パブリックなPeerJSサーバーを使えばシグナリングサーバーの準備も省略できますが、パブリックサーバーはレート制限や安定性の保証がないため、本番公開には自前のPeerJSサーバーかFirebaseをシグナリングに使う構成が堅実です。

接続の大まかな流れは次のとおりです。

  1. ホスト側がPeerオブジェクトを生成し、PeerIDを取得してゲストに共有する(URLパラメーターや画面表示など)。
  2. ゲスト側がPeerIDを入力または取得し、peer.connect(hostId)でデータチャネルを開く。
  3. 接続が確立したら、キー入力やゲーム状態のdiffを互いにJSON(またはバイナリ)で送り合う。
  4. 受信側は受け取ったデータでローカルのゲーム状態を更新する。

実装で最初につまずきやすいのは、ICE接続の完了を待たずにデータ送信を始めてしまうことです。connection.on('open', ...)のイベントを確認してからデータ送信を開始するよう実装する必要があります。また、片方がウィンドウを閉じたときの切断検知と再接続フローも早めに実装しておかないと、テスト中に接続が切れるたびにページ再読み込みが必要になります。

ゲーム状態の同期設計では、毎フレームの全状態を送るのではなく、「入力イベント」または「状態差分」だけを送る設計が基本です。全状態送信はデータ量が増えるだけでなく、パケットの到着順が保証されない非信頼性チャネルでは古いデータで状態が上書きされるバグの原因になります。送信するデータにシーケンス番号を付け、受信側で番号を確認してから適用する処理を組み込むことを最初から設計に含めておくと後の手戻りが減ります。

P2Pを選ぶ基準と避けるべき状況

P2Pが向いている状況をまとめると次のようになります。

  • プレイヤーが2〜4人程度:参加者が増えると全員がメッシュ状に接続するため接続数が急増します。6人以上になるとサーバー中継型の方が管理しやすいです。
  • ターン制・非リアルタイム:遅延補償の実装が不要で、シンプルな入力転送で動きます。
  • サーバー運用コストを最小化したい個人・小規模チーム:シグナリングを無料枠サービスで賄えれば、ゲームデータの中継費用はほぼゼロになります。
  • プレイヤーの地域が比較的均一:国内プレイヤー同士であれば遅延が安定しやすいです。

逆にP2Pを避けた方が良い状況は、「チート対策が重要なゲーム」です。P2Pではゲーム状態の正解をどのプレイヤーのクライアントが持つかが曖昧になりやすく、権威あるサーバーを持たない設計はゲームロジックの改ざんを防ぐのが難しくなります。また、観戦・リプレイ・マッチメイキングなどの機能を将来追加したい場合も、最初からサーバー型にしておく方が追加コストが小さいことが多いです。

次の行動

まずは2人対戦の小さなプロトタイプをPeerJSとFirebaseのシグナリングで作り、NAT越えがどのくらいの確率で成功するかを複数のネットワーク環境でテストしてみてください。その結果をもとにTURNサーバーが必要かどうかを判断するのが現実的な進め方です。完成したゲームはHTMLファイルまたはZIPとしてアソバースに投稿し、実際のプレイヤーから接続の安定性についてフィードバックを得ることで、次の改善点が見えてきます。

参考資料

  • MDN Web Docs — WebRTC API
  • W3C WebRTC 1.0仕様
  • PeerJS 公式ドキュメント
  • RFC 5389(STUN)・RFC 5766(TURN)