🎮

サーバー代0円! P2Pと無料サービスだけでリアルタイム対戦オセロを作った話

に公開

概要

この記事では、サーバーコストを一切かけずにブラウザだけで動作するリアルタイム対戦ゲームを開発した際の、技術的な裏側をご紹介します。WebRTC (PeerJS) によるP2P通信を主軸に、マッチングを仲介するシグナリングサーバーをRender、フロントエンドをGitHub Pagesにデプロイすることで、完全無料で遊べるブラウザオセロゲームを実現しました。

はじめに

皆さんは「ゴッドフィールド」というWebゲームをご存知でしょうか?ブラウザだけで手軽に遊べるターン制のオンライン対戦ゲームで、これがめちゃくちゃ楽しいんです。

「こんなに面白いゲームを自分でも作ってみたい!」

そう思ったものの、個人開発者にとって悩ましいのがサーバーコストです。一般的なオンラインゲームでは、プレイヤー間のやり取りを仲介するために24時間稼働するサーバーが必要になります。当然ながら、この構成ではサーバーの維持費がかさんでしまいます。

そこで、「何とか工夫して無料で同等のサービスを実現できないか?」と考えました。サーバーの負荷を極限まで減らす方法として、「対戦処理そのものをプレイヤー同士のPCで直接行わせる」というP2P (Peer-to-Peer) 通信のアイデアにたどり着きました。

今回はこのP2Pの仕組みを使い、技術的な面白さを追求しつつ、サーバーレスで動作する対戦オセロゲームを開発してみました。

P2P Othello の特徴

開発したゲームには、主に以下の4つの特徴があります。

  • サーバーレスによる低遅延な対戦: WebRTC (PeerJS) を利用し、ゲーム中の通信はプレイヤー間で直接行われるため、サーバー経由の遅延がありません。
  • インストール不要: Webブラウザさえあれば、PCでもスマートフォンでも、いつでもどこでもプレイ可能です。
  • URLだけで完結する簡単な招待機能: 生成されたURLを友達に送るだけで、すぐに対戦を始められます。
  • 完全無料で運用可能: GitHub PagesとRenderの無料枠を組み合わせているため、ホスティング費用は一切かかりません。

システム構成

このゲームは、静的なWebページを配信する「フロントエンド」と、プレイヤー間の最初の接続を仲介する「シグナリングサーバー」の2つだけで構成されています。

役割 担当 利用技術・サービス
フロントエンド ゲーム本体の画面描画、操作ロジック GitHub Pages (HTML/CSS/JavaScript)
P2P通信 プレイヤー間のデータ送受信 WebRTC (PeerJS)
シグナリング P2P接続の仲介(マッチング) Render (Node.js + PeerServer)

システム構成とその通信の様子

P2P通信を始めるには、まずお互いのIPアドレスといった接続情報を交換する必要があります。この最初の「顔合わせ」を仲介するのが、シグナリングサーバーの役割です。

そして、一度プレイヤー同士のP2P接続が確立されてしまえば、シグナリングサーバーはもうお役御免。ゲーム中の石を置くデータ(move)などは、Renderのサーバーを経由せず、プレイヤーのブラウザ間で直接リアルタイムにやり取りされます。この仕組みのおかげでサーバー負荷を最小限に抑えられ、Renderの無料プランでも十分に運用可能なシステムが実現できました。

WebRTCとは? なぜシグナリングサーバーが必要なのか?

WebRTC (Web Real-Time Communication) とは、ブラウザ間で直接ビデオや音声、そして任意のデータをやり取りするための技術です。ブラウザ上でP2P通信を実現するためのAPI群、と考えると分かりやすいかもしれません。

しかし、P2Pで通信を始めるには、一つ大きな壁があります。それは、「通信したい相手のIPアドレスをどうやって知るのか?」という問題です。

インターネット上のほとんどのデバイスは、ルーターやファイアウォールの内側にあり、直接外部からアクセスできないプライベートIPアドレスしか持っていません。そこで登場するのがシグナリングサーバーです。

  1. サーバーに集合: まず、各クライアントがシグナリングサーバーに接続します。
  2. 接続情報を交換: 次に、シグナリングサーバーを介して、お互いのIPアドレスやポート番号といった接続に必要な情報(メタデータ)を交換します。このプロセスをシグナリングと呼びます。
  3. P2P接続の確立: 接続情報を受け取ったクライアント同士は、STUN/TURNサーバー(NAT越えを助ける仕組み)などを利用して、直接通信するためのトンネルを確立しようと試みます。

このように、一度P2Pのトンネルが確立されれば、シグナリングサーバーの役目は完了し、あとは当事者間で直接データをやり取りできるのです。このゲームでは、Render上のサーバーがこのシグナリングの役割だけを担っています。

PeerJSによる実装の簡略化

とはいえ、WebRTCのAPIをゼロから直接扱うのは、シグナリングの実装なども含めるとかなり複雑です。そこで今回は、WebRTCをうまい具合に抽象化し、非常にシンプルなAPIでP2P通信を扱えるようにしたライブラリ PeerJS を採用しました。

フロントエンド(クライアント側)の実装

PeerJSのおかげで、クライアント側のコードは驚くほどシンプルになります。

main.js
// 自分のPeerオブジェクトを初期化
// 'peerjs-server.onrender.com' は自前でデプロイしたシグナリングサーバーです
const peer = new Peer({
  host: 'peerjs-server.onrender.com',
  secure: true,
});

// シグナリングサーバーへの接続が完了すると'open'イベントが発火します
peer.on('open', id => {
  // idが自分のID。これを相手に教えることで接続してもらえるようになります
  console.log('My peer ID is: ' + id);
});

// 相手から接続要求が来た時の処理
peer.on('connection', conn => {
  // データを受信した時の処理
  conn.on('data', data => {
    // dataには相手が置いた石の座標などが入ってきます
    console.log('Received:', data);
    // ゲーム盤面を更新する処理などをここに記述します
  });
});

// 相手に接続しにいく処理
function connectToPeer(opponentId) {
  const conn = peer.connect(opponentId);
  // 接続が確立したら、データを送信できるようになります
  conn.on('open', () => {
    // 石を置いた時などにこのsendメソッドでデータを送信します
    conn.send({ type: 'move', position: { x: 3, y: 4 } });
  });
}

このように、PeerJSが内部でシグナリングサーバーとのやり取りや複雑な接続処理を全て肩代わりしてくれるため、開発者はon('connection')connect()といった直感的なメソッドだけでP2P通信を実装できます。

マッチングサーバー側の実装

さらに、Renderで動かしているサーバー側のコードはもっと短く、ほんとにこれだけです。

server.js
const { PeerServer } = require('peer');

const peerServer = PeerServer({
  port: 9000,
  path: '/',
});

console.log('PeerJS server running on port 9000');

PeerJSが提供するPeerServerを起動するだけで、シグナリングサーバーとして必要な機能がすべて整います。Renderの無料プランではWebサービスとしてデプロイするため、自動的にHTTPS化され、外部から安全にアクセスできるのも嬉しいポイントです。

プレイ方法

2人のプレイヤー(Aさん、Bさん) で対戦を開始する手順です。

1. ゲームページを開く (Aさん)

まず、招待する側(Aさん)がゲームのURLにアクセスします。
少し待つと、Your IDの欄に固有のIDが自動で表示されます。

Aさんのゲーム画面

2. 招待URLをコピーする (Aさん)

IDの隣にある**🔗 Share Link**ボタンをクリックしてください。
すると、対戦用の招待URLがクリップボードにコピーされます。

Aさんのゲーム画面

▼コピーされるURLの例:

https://kinn00kinn.github.io/osero_p2p_front.github.io/?connect_to=d5bdfc55-bbf0-4425-80e3-087291520299

3. URLを友達に送る (Aさん → Bさん)

コピーしたURLを、LINEやDiscordなどのチャットツールで対戦相手(Bさん)に送ります。
重要:URLを送った後、Aさんはページを閉じたりリロードしたりせず、Bさんが接続してくるのをそのまま待機してください。

4. URLを開いて接続する (Bさん)

招待された側(Bさん)がAさんから送られてきたURLを開くと、「Opponent's ID」の欄にAさんのIDが自動で入力された状態になっています。
Bさんは**Connect**ボタンをクリックします。

Bさんのゲーム画面

5. 対戦開始!

接続が成功すると、両者の画面でゲームが始まります。
先手は招待した側(Aさん)で、石の色は黒です。思う存分、対戦をお楽しみください!

Aさんのゲーム画面

感想と今後の展望

今回、WebRTCという技術を軸に据えることで、サーバーコストを一切かけずにリアルタイム対戦ゲームのプロトタイプを構築することができました。技術的な面白さを追求するという点では、非常に満足のいく開発体験でした。

しかしその一方で、本来の目的であった「面白いゲーム」そのものの中身にまでは、あまり手が回らなかったのが少し心残りです。今後は、今回構築したP2P対戦の仕組みを基盤として、より複雑で戦略的な、自分だけのオリジナルゲーム開発に挑戦していきたいです。

この記事が、WebRTCやP2P技術を使った開発に興味を持つ誰かのきっかけになれば幸いです。
もしよろしければ、GitHubリポジトリにスターをいただけると大変励みになります!

Discussion