マガジン

対戦ゲームの再接続処理:切断からの復帰設計

遊びをつくる

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

小さなゲーム模型を四つの試験コースで動かし、修正を確かめる机。
記事のテーマをもとに制作したAI生成イラスト。

対戦ゲームを作るとき、接続が途切れた瞬間の処理を後回しにしがちです。しかし再接続の設計は「あると便利」ではなく、対戦体験の根幹に関わります。切断したプレイヤーが戻れなければ、残ったプレイヤーはその場で試合が壊れる体験をします。どちらの側も不満を抱えたまま離脱し、リピートにつながりません。 この記事では、ブラウザベースの対戦ゲームを対象に、切断から復帰するまでの処理を設計する考え方を整理します。WebSocketを前提にしつつ、概念はほかのリアルタイム通信にも応用できます。新しいサーバー技術の比較ではなく、「切断が起きたあと何をどの順で判断するか」という設計の論点に絞っています。

この記事の目次
  1. 切断には種類がある。まず分類する
  2. サーバー側の状態保持:タイムアウトを設ける
  3. 再接続フローの設計:クライアントとサーバーの役割分担
  4. 残ったプレイヤーへの通知:体験を壊さないために
  5. よくある設計ミスと対処
  6. 次の行動
  7. 参考資料

切断には種類がある。まず分類する

再接続処理を設計する前に、切断の種類を分けておく必要があります。種類によって適切な対応が異なるからです。

意図的な切断はプレイヤーがタブを閉じたり、ページを離脱したりするケースです。ブラウザはbeforeunloadイベントを発火しますが、モバイルでは信頼性が低く、これだけを頼りにする設計は崩れやすいです。

非意図的な切断は通信の瞬断、Wi-Fiの切り替わり、スリープからの復帰などです。プレイヤーは戻るつもりがあります。このケースが再接続処理の主な対象です。

タイムアウトはサーバー側の待機時間が尽きた結果です。非意図的な切断への対応として、サーバーが一定時間セッションを保持する設計がここに関係します。

この三つを区別しないまま設計すると、「すぐ再接続できるのに試合が終了扱いになった」「意図的に退出したのにゲームが止まったままになった」という矛盾が生まれます。

サーバー側の状態保持:タイムアウトを設ける

再接続を成立させる最小条件は、サーバーが切断後もゲームセッションの状態を保持していることです。プレイヤーが切断した瞬間にセッションを破棄する設計では、物理的に再接続できません。

設計の核心は猶予時間(grace period)の導入です。切断を検知したらすぐにセッションを終了するのではなく、一定時間(たとえば30秒から2分程度)は状態を維持し、その間に同じプレイヤーが戻ってくることを待ちます。

猶予時間の長さは対戦の性質によって変わります。

  • ターン制ゲーム(チェス・将棋など):切断中もゲームは進まないので、比較的長い猶予(2〜3分)を設けても相手への影響が限定的です。
  • リアルタイム対戦(アクション・格闘など):試合が止まるか相手が不利になるため、猶予は短く設定し(15〜30秒)、AIや棄権処理と組み合わせます。
  • 非同期型(パズル対戦のスコア比較など):リアルタイム性が低いので、猶予よりセッション再開の仕組み自体が主要課題になります。

猶予時間の終了後は、残ったプレイヤーの勝利扱いにする、試合を中断保存する、または最初からやり直すなど、ゲームのルールに合わせた終了処理に移ります。

再接続フローの設計:クライアントとサーバーの役割分担

再接続は「クライアントが試み、サーバーが認証して状態を渡す」という流れになります。ステップを整理すると以下のようになります。

1. セッショントークンの発行と保持

試合開始時に、サーバーはそのプレイヤー専用のセッショントークン(一時的な識別子)を発行します。クライアントはこれをsessionStorageまたはlocalStorageに保存します。sessionStorageはタブを閉じると消えるため、再接続の範囲をタブ内に限定したい場合に向いています。タブを閉じて再度開いても戻れるようにしたい場合はlocalStorageを使いますが、その分セキュリティ上の配慮が必要です。

2. 切断の検知

WebSocketはoncloseイベントで切断を検知できます。クライアントがこのイベントを受け取ったら、即座にUIへ「再接続中…」の表示を出し、自動再接続のループを開始します。

3. 再接続の試行

指数バックオフ(最初は1秒後、次は2秒後、4秒後……と間隔を伸ばす方式)で再接続を試みます。短い間隔で連続してリトライするとサーバー負荷を高めるため、上限(たとえば最大30秒間隔)を設けます。

試行1: 1秒後
試行2: 2秒後
試行3: 4秒後
試行4: 8秒後
試行5: 16秒後(以後この間隔で継続)

4. 再接続時の認証と状態復元

再接続が成功したら、クライアントは保存しておいたセッショントークンをサーバーへ送ります。サーバーはトークンを検証し、猶予時間内であればそのプレイヤーをセッションに再参加させ、現在の盤面・スコア・手番などの最新状態をクライアントへ送り返します。クライアントはこの状態でUIを再構築します。

残ったプレイヤーへの通知:体験を壊さないために

切断した側だけでなく、残ったプレイヤーへの対応も設計に含めます。相手が切断したのに何も表示されなければ、バグと勘違いして離脱します。

最低限表示すべき情報は「相手が切断した」という事実と「あと何秒で試合が終了するか(猶予時間のカウントダウン)」の二つです。猶予時間が終了したときに自動で勝利判定を出すか、もう少し待つかを残ったプレイヤーが選べるUIも一つの選択肢です。ただし選択肢を増やすほど実装は複雑になるため、まずは自動処理から始めるほうが現実的です。

残ったプレイヤーが猶予時間中に離脱した場合は、両者を切断扱いにして試合を無効化するか、引き分けにするかをあらかじめ決めておきます。このエッジケースを未定義にしたまま公開すると、仕様の隙間を突くプレイが生まれることがあります。

よくある設計ミスと対処

トークンの有効期限を長く設定しすぎる:セッショントークンを長期保存すると、別のデバイスや別のタイミングで同じトークンを使った不正な再接続が成立してしまいます。トークンには試合ID・プレイヤーID・有効期限を含め、猶予時間が終わったら無効化します。

再接続後の状態の整合性を検証しない:クライアントがローカルで持っている状態とサーバーの状態がずれたまま試合を再開すると、操作が噛み合わなくなります。再接続時は必ずサーバーの状態を正とし、クライアントの状態を上書きします。クライアントの状態を「信頼する」設計は不正操作の温床にもなります。

切断検知の遅延を見落とす:WebSocketのoncloseはネットワーク層の切断を即座に反映しないことがあります。アプリケーション層のハートビート(定期的なpingとpongのやり取り)を実装すると、実際の切断を早期に検知できます。ハートビートの間隔は5〜15秒程度が一般的です。

モバイルのバックグラウンド遷移を見落とす:スマートフォンでホームボタンを押すと、ブラウザはバックグラウンドに移行してWebSocket接続が切れることがあります。visibilitychangeイベントを監視して、ページが再びフォアグラウンドに戻ったときに再接続を試みる処理を加えると、モバイルの体験が向上します。

次の行動

まずターン制かリアルタイムかを確認し、猶予時間の長さと終了後の処理(勝利判定・中断保存・無効化)を仕様として文書に書き出してください。次にサーバー側でセッショントークンの発行と状態保持を実装し、クライアント側に指数バックオフによる自動再接続ループを加えます。アソバースへ投稿する際は、再接続の挙動をZIP内のREADMEや説明文に記載しておくと、プレイヤーが切断時に戸惑いにくくなります。

参考資料

  • MDN Web Docs(WebSocket API、Page Visibility API、sessionStorage / localStorage)
  • WHATWG HTML Living Standard(beforeunload イベントの仕様)