メインコンテンツへスキップ
記事一覧に戻る
programming

同一フィールドで戦うスネークバトルを実装してみた

Cloudflare Durable Objectsを使ったリアルタイムスネークバトルゲームの実装解説。サーバー側でのゲームロジック処理、衝突判定、WebSocketによる状態同期の仕組みを詳しく紹介します。

2026-01-0413 min readAdabana Saki
-
スネークゲームCloudflare WorkersDurable ObjectsWebSocketリアルタイム通信TypeScriptゲーム開発

同一フィールドで戦うスネークバトルを実装してみた

「スネークゲームの対戦版を作ろう」

そう思い立ったとき、最初に考えたのは「どうやって2匹のスネークを同じフィールドで戦わせるか」でした。

この記事では、Cloudflare Durable Objectsを使って実装したリアルタイムスネークバトルの技術的な裏側を解説します。

完成したもの

まずは完成したゲームの概要です:

  • 同一フィールド対戦: 2人のプレイヤーが30×30のグリッド上で対戦
  • リアルタイム同期: 100msごとにゲーム状態を同期(10 FPS)
  • 多様な衝突判定: 壁、自分の体、相手の体、正面衝突
  • クイックマッチ: ランダムなプレイヤーとすぐに対戦可能

なぜサーバー側でゲームロジックを実行するのか?

クライアント側実行の問題点

スネークゲームをクライアント側で実行する場合、いくつかの問題があります:

Text

特に「正面衝突」のような判定では、どちらが先に衝突したかで勝敗が変わってしまいます。

サーバー側実行のメリット

サーバーを**唯一の信頼できる情報源(Single Source of Truth)**にすることで、この問題を解決できます:

Text

Cloudflare Durable Objectsの採用

なぜDurable Objectsなのか?

リアルタイムゲームサーバーには以下の要件があります:

要件Durable Objectsの対応
WebSocket対応ネイティブサポート
ステートフルオブジェクト内で状態を保持
低レイテンシエッジで実行される
スケーラビリティ自動的にスケール

通常のサーバーレス関数(AWS Lambda等)はステートレスなので、ゲームの状態を外部に保存する必要があります。Durable Objectsはステートフルなので、ゲーム状態をメモリに保持できます。

ルーム構造

TypeScript

各ルームが独立したDurable Objectとして存在し、接続したプレイヤーの状態を管理します。

ゲームループの実装

100msごとのティック

ゲームは100ミリ秒ごとにサーバー側で状態を更新します:

TypeScript

「100msは遅くない?」と思うかもしれませんが、スネークゲームにはちょうど良いテンポです。古典的なスネークゲームも同様のスピードでした。

ティック処理の流れ

TypeScript

衝突判定の詳細

4種類の衝突

スネークバトルには4種類の衝突判定があります:

TypeScript

それぞれの判定を見ていきましょう。

1. 壁衝突

TypeScript

グリッドの範囲外に出たら壁に衝突したと判定します。

2. 自分の体への衝突

TypeScript

自分のスネークの体と新しい頭の位置が重なったらアウト。

3. 正面衝突(両者死亡)

TypeScript

これが一番面白い判定です。両者が同じマスに移動しようとした場合、両方とも死亡します。この場合はスコアで勝敗を決めます。

4. 相手の体への衝突

TypeScript

相手のスネークの体に突っ込んだ場合。相手は無傷です。

プレイヤーの初期配置

対戦ゲームとして公平にするため、プレイヤーは対角線上に配置されます:

TypeScript
Text

クライアント側の実装

方向変更のみ送信

クライアントは方向変更のみをサーバーに送信します:

TypeScript

スネークの位置計算はすべてサーバー側で行い、クライアントは結果を描画するだけです。

キーボード操作

TypeScript

クライアント側でも180度反転をブロックしていますが、最終的な判定はサーバー側で行います。これは二重チェックで、チート対策にもなっています。

タッチ操作(モバイル対応)

TypeScript

スワイプ操作で直感的に方向を変えられるようにしています。

死亡通知のアニメーション

誰かが死んだときは、全プレイヤーに通知を表示します:

TypeScript

Framer Motionを使ったアニメーションで、通知がスムーズに表示・非表示されます:

TSX

勝敗判定のロジック

通常の勝利

TypeScript

両者死亡の場合

TypeScript

同時死亡でも必ず勝者を決定できる仕組みです。

マッチメイキングの仕組み

クイックマッチ

TypeScript

マッチング待ちの場合は、500msごとにサーバーをポーリングしてマッチを確認します。

まとめ

技術要素採用した解決策
ゲーム状態の同期サーバー側でロジック実行
リアルタイム通信WebSocket(Durable Objects)
衝突判定サーバー側で一元管理
公平性対角線配置 + シード乱数
モバイル対応スワイプ操作

サーバー側でゲームロジックを実行することで、チート耐性と公平性を両立できました。

Cloudflare Durable Objectsは、こういったステートフルなリアルタイムアプリケーションに最適です。従来のサーバーレスでは実現が難しかったゲームサーバーも、簡単に構築できるようになりました。

遊んでみる

実際にスネークバトルを遊んでみたい方は、こちらからどうぞ:

ソースコード

この記事で紹介したコードの完全版はGitHubで公開しています:

このシリーズの他の記事

このサイトでは、他にもオンライン対戦ゲームの実装記事を公開しています:

参考リンク