サーバーを1台も持たずに5人対戦 — Durable Objectsの使い方と落とし穴

公開: 2026年10月25日 ・ 筆者: Toshi(開発者)

バズ走のフレンド対戦は、QRコードで友達を招待して最大5人が同時に走るリアルタイム対戦です。「リアルタイム対戦=専用サーバーが必要」と思われがちですが、バズ走はサーバーを1台も持っていません。使っているのは Cloudflare Workers の Durable Objects と WebSocket だけです。この記事では、その設計と、実際にはまった落とし穴を書きます。

全体の構成

役割実装
部屋の作成・招待端末側で部屋コードを生成(0/O/1/Iを除いた英数字)。QRコードも端末側で生成(自前実装)
部屋の状態管理部屋コードごとに Durable Object を1つ割り当て(idFromName)
通信WebSocket(wss://vs.buzzsou.com/room/部屋コード/ws)
同期する情報各プレイヤーの距離・高さ・生存状態のみ。地形は同期しない
永続化Durable Object の storage に勝利数・フェーズ・ホスト・参加枠を保存

地形を同期しないから軽い

最大のポイントは「今日のコース」の設計です。地形は日付から決まるので、対戦相手と地形を同期する必要がそもそもありません。全員の端末が同じ計算式で同じ地形を作れるため、通信するのは1人あたり毎秒12回、距離と高さの数値だけ。1回の送信は数十バイトで、Workersの無料枠で十分に運用できます。

部屋の中身:5つの枠とフェーズ

部屋には A〜E の5つの枠があり、参加者は空いている枠に入ります。部屋の状態は lobby(待機)→ racing(レース中)→ result(結果)の3フェーズで管理し、ホスト(部屋を作った人)だけが開始の合図を出せます。

各枠には、プレイヤーID・ニックネーム・接続中かどうか・現在の距離と高さ・走り終わったか・最終距離・最後に位置を受信した時刻、を持たせています。クライアントからの位置更新を受けるたびに、部屋内の他の全員へそのままブロードキャストします。

落とし穴1:ハイバネーションで状態が消える

いちばん苦労したのが WebSocket ハイバネーションです。Durable Objects は省コストのため、接続が静かになるとオブジェクトをメモリから降ろし、次のメッセージで作り直します。この仕様を理解しないまま書いた初期版では、「2人目が入ると1人目の情報が消えている」という不可解な不具合に悩まされました。メモリ上の変数に入れていた参加者一覧が、ハイバネーションのたびに初期化されていたのです。

落ち着いた原則は2つです。

この2つを守ってからは、部屋を1時間放置しても勝利数が消えなくなりました。

落とし穴2:走っている人を巻き込んで再スタート

5人対応にしたとき、「誰かがまだ走っているのにホストが次のレースを始めてしまい、走っていた人の画面が強制リセットされる」問題が起きました。対策として、レース中は原則として開始を拒否するようにしています。

ただし、全員が切断してしまった部屋や、誰かが放置したまま帰ってしまった部屋は、永遠に次のレースが始められなくなります。そこで「走者全員が切断済み、または30秒以上位置更新がない」場合に限り、部屋の立て直しとして開始を許可しました。この「30秒」は、通信が一瞬切れた人を切り捨てない長さと、放置を待ちすぎない長さの折衷です。

落とし穴3:ホストがいなくなる

部屋を作った人が途中で帰ってしまうと、誰も開始できません。ホストの判定は「保存されたホストIDを持つ枠」を探し、見つからなければ接続中の先頭の枠を自動で繰り上げるようにしました。クライアントには毎回「今のホストは誰か」を含めた状態を送っているので、画面上の開始ボタンも自動で移ります。

再接続は同じ枠に戻す

スマートフォンでは画面を消すだけで WebSocket が切れます。復帰時に新しい枠へ入れてしまうと、1人で2枠を占有してしまい、5人部屋がすぐに満室になります。そこで接続時にプレイヤーIDを受け取り、同じIDなら元の枠へ戻すようにしました。IDは端末のブラウザに保存した乱数文字列で、アカウントは不要です。

結果の確定と勝利数

レースに参加した全員が「走り終わった」状態になった時点で順位を確定します。途中で切断した人を待ち続けると結果が出ないので、接続中かつ未完走の人がいなくなったら確定というルールにしました。1位の枠の勝利数を1増やして保存し、順位表を全員に配ります。同じリンクから入り直せば勝利数が引き継がれるのは、この保存のおかげです。

セキュリティと負荷について

まとめ

「地形を同期しない」という設計上の判断が、通信量・コスト・実装の複雑さをすべて下げてくれました。Durable Objects は「部屋」のような小さな状態を扱うのに向いていますが、ハイバネーションを前提に何を保存し、何を毎回計算し直すかを決めておかないと、原因の分かりにくい不具合に悩まされます。これから同じものを作る方は、まず storage の設計から始めることをおすすめします。

次の記事:QRコードを自分で実装した話 — リード・ソロモン符号で2回はまった

⚽ いますぐ走る