v0 × Momentoでリアルタイムマルチプレイゲームを作る

v0 × Momentoでリアルタイムマルチプレイゲームを作る

2025.12.12

ゲームソリューション部の えがわ です。

本ブログはClassmethod SaaSで加速するゲーム開発 Advent Calendar 2025の12日目のブログとなります。

https://dev.classmethod.jp/referencecat/gamesol-businesssol-advent-calendar-2025/

今回はv0Momentoを組み合わせて、簡単なオンラインゲームを作成してみます。

はじめに

最近はv0等のAIエージェントを活用することで、ある程度のブラウザゲームをかたちにすることはできると思います。
しかし、オンラインゲームを作るとなると話は別です。

そのような場合には、サーバーレスキャッシュのMomentoを使用することで簡単に解決できる場合があります。

  • v0: AIがプロンプトからフロントエンドを自動生成
  • Momento: サーバーレスなキャッシュ&Pub/Subサービス。CacheとTopicsでリアルタイム同期を簡単に実現

今回作るもの

リアルタイム暗算RPG「暗算ダンジョン」を作成します。

複数プレイヤーがリアルタイムで協力し、暗算問題を解いてモンスターを倒すダークファンタジー風RPGゲームです。

機能

  • リアルタイム協力プレイ: 複数プレイヤーが同時にモンスターと戦闘
  • 暗算バトルシステム: 各プレイヤーが独自の問題を解いてダメージを与える
  • 階層システム: フロアごとに難易度が上昇
  • リーダーボード: スコアランキングのリアルタイム更新

完成イメージ

v0-momento-online-game.gif

要件定義書(後に投げるプロンプトで使用します)

リアルタイム暗算RPG「暗算ダンジョン」要件定義書

1. 概要

1.1 ゲームコンセプト

複数プレイヤーがリアルタイムで協力し、暗算問題を解いてモンスターを倒すダークファンタジー風RPGゲーム。

1.2 主な特徴

  • リアルタイムマルチプレイヤー対応
  • 各プレイヤーが独自の問題を解く(問題の共有はなし)
  • 回答速度に応じたダメージ倍率システム
  • 階層(フロア)ごとに難易度が上昇
  • ダークファンタジー風のビジュアルデザイン

2. 技術スタック

2.1 フロントエンド

  • フレームワーク: Next.js 15 (App Router)
  • UI ライブラリ: React 19
  • スタイリング: Tailwind CSS v4

2.2 バックエンド

  • API: Next.js API Routes
  • リアルタイム同期: Momento Topics (Pub/Sub)
  • データ永続化: Momento Cache

2.3 環境変数

変数名 説明
MOMENTO_API_KEY Momento APIキー

3. 画面構成

3.1 参加画面 (JoinScreen)

  • 目的: プレイヤー名の入力とルームへの参加
  • 機能:
    • プレイヤー名入力フォーム
    • ルームID入力(オプション、空の場合は新規作成)
    • 参加ボタン
  • 遷移先: 待機画面

3.2 待機画面 (WaitingScreen)

  • 目的: 他プレイヤーの参加を待機
  • 機能:
    • 参加中プレイヤー一覧表示
    • ルームID表示(共有用)
    • ゲーム開始ボタン(2人以上で有効化)
  • 遷移先: バトル画面

3.3 バトル画面 (BattleScreen)

  • 目的: モンスターとの戦闘
  • 機能:
    • モンスター情報表示(名前、HP、画像)
    • HPバーアニメーション
    • 暗算問題表示(各プレイヤー独自)
    • 回答入力フォーム
    • バトルログ表示(リアルタイム同期)
    • 現在フロア表示
  • 遷移先:
    • 次のフロア(モンスター撃破時)
    • リザルト画面(ボス撃破時)

3.4 リザルト画面 (ResultScreen)

  • 目的: ゲーム結果の表示
  • 機能:
    • クリアメッセージ
    • プレイヤー別スコア表示
      • 総ダメージ
      • 正解数
    • 全体リーダーボード表示
    • 再プレイボタン

4. ゲームシステム

4.1 フロア構成

フロア モンスター HP 難易度
1 スライム 100 1桁 × 1桁
2 ゴブリン 200 2桁 × 1桁
3 ドラゴン(ボス) 800 2桁 × 2桁

4.2 問題生成ロジック

// 難易度に応じた数値範囲
Floor 1-2: num1 = 29, num2 = 29
Floor 3:   num1 = 1029, num2 = 1029

4.3 ダメージ計算

// 基本ダメージ: 10
// 回答時間による倍率
multiplier =
  time <= 1: 2.0倍
  time <= 2: 1.5倍
  time <= 3: 1.2倍
  time > 3:  1.0倍

finalDamage = Math.floor(baseDamage * multiplier)

4.4 バトルフロー

  1. 各プレイヤーがローカルで問題を生成
  2. プレイヤーが回答を入力
  3. 正解の場合:
    • 回答時間を計測
    • ダメージ倍率を計算
    • サーバーにダメージを送信
    • サーバーがモンスターHPを更新
    • バトルログを全プレイヤーに同期
  4. 不正解の場合:
    • 新しい問題を生成(ペナルティなし)
  5. モンスターHP <= 0 で次フロアまたはクリア

5. データ構造

5.1 RoomState

interface RoomState {
  id: string;                    // ルームID
  players: Player[];             // 参加プレイヤー一覧
  status: 'waiting' | 'playing' | 'finished';
  currentFloor: number;          // 現在フロア (1-5)
  monster: Monster | null;       // 現在のモンスター
  battleLog: BattleLogEntry[];   // バトルログ
}

5.2 Player

interface Player {
  id: string;           // プレイヤーID
  name: string;         // プレイヤー名
  totalDamage: number;  // 累計ダメージ
  correctAnswers: number; // 正解数
}

5.3 Monster

interface Monster {
  name: string;      // モンスター名
  maxHp: number;     // 最大HP
  currentHp: number; // 現在HP
  image: string;     // 画像URL
}

5.4 BattleLogEntry

interface BattleLogEntry {
  playerName: string;  // プレイヤー名
  damage: number;      // 与えたダメージ
  time: number;        // 回答時間(秒)
  timestamp: number;   // タイムスタンプ
}

6. API仕様

6.1 POST /api/game/join

ルームに参加または新規作成

リクエスト:

{
  "playerName": "string",
  "roomId": "string (optional)"
}

レスポンス:

{
  "roomId": "string",
  "playerId": "string",
  "room": "RoomState"
}

6.2 GET /api/game/room?roomId={roomId}

ルーム状態を取得(ポーリング用)

レスポンス:

{
  "room": "RoomState"
}

6.3 POST /api/game/start

ゲームを開始

リクエスト:

{
  "roomId": "string"
}

レスポンス:

{
  "room": "RoomState"
}

6.4 POST /api/game/answer

回答を送信

リクエスト:

{
  "roomId": "string",
  "playerId": "string",
  "damage": "number",
  "time": "number"
}

レスポンス:

{
  "room": "RoomState",
  "defeated": "boolean"
}

6.5 GET /api/game/leaderboard

リーダーボード取得

レスポンス:

{
  "leaderboard": [
    { "name": "string", "score": "number" }
  ]
}

6.6 POST /api/game/leaderboard

スコアを送信

リクエスト:

{
  "playerName": "string",
  "score": "number"
}

7. リアルタイム同期

7.1 同期方式

  • 方式: Momento Topics(Pub/Sub)
  • 永続化: Momento Cache

7.2 同期対象データ

  • プレイヤー参加/退出
  • ダメージイベント
  • モンスター撃破
  • ゲームクリア

7.3 Momento Cache キー構造

キー 説明
room:{roomId} RoomState (JSON) ルーム状態
leaderboard LeaderboardEntry[] (JSON) グローバルリーダーボード

7.4 Momento Topics トピック構造

トピック イベント 説明
room:{roomId} PLAYER_JOINED, DAMAGE_DEALT, MONSTER_DEFEATED, GAME_CLEAR ルーム内イベント配信

8. デザイン仕様

8.1 カラーパレット

--background: 222.2 47.4% 6.2%      /* ダークブルー背景 */
--foreground: 210 40% 96%           /* 明るいテキスト */
--primary: 262.1 83.3% 57.8%        /* パープルアクセント */
--destructive: 0 84% 60%            /* 赤(ダメージ) */
--muted: 217.2 32.6% 12%            /* ミュートカラー */

8.2 フォント

  • 見出し: Cinzel(セリフ、ファンタジー風)
  • 本文: Inter(サンセリフ)

8.3 UIコンポーネント

  • HPバー: アニメーション付きプログレスバー
  • カード: 半透明背景、ボーダー付き
  • ボタン: グロー効果、ホバーアニメーション

使用技術

技術 用途
v0 フロントエンドの自動生成
Momento Cache ゲーム状態の永続化・リーダーボード
Momento Topics Pub/Subによるリアルタイムイベント配信
Vercel デプロイ先

Momentoとは?

Momentoはサーバーレスのキャッシュ・Pub/Subサービスです。
インフラ管理不要で、リアルタイム通信が簡単に実装できます。

Momentoの特徴

  • 完全サーバーレス: インフラ管理不要
  • Cache: 高速なキャッシュストレージ
  • Topics(Pub/Sub): リアルタイムメッセージング(イベント配信)
  • 従量課金: 使った分だけ課金

オンラインゲーム開発に適した理由

  1. 低レイテンシ: ゲームに必要な高速通信
  2. スケーラビリティ: 自動でスケールアップ/ダウン
  3. シンプルなAPI: 少ないコードで実装可能
  4. Pub/Subパターン: Topicsでイベントを全プレイヤーにリアルタイム配信
  5. 状態管理: Cacheでゲーム状態を永続化・共有

手順

1. ゲームの内容を考える

まずは、作りたいゲームの内容を考えます。
今回は以下のようなゲームを作成します。

【ゲーム概要】暗算ダンジョン
- 複数プレイヤーがリアルタイムで協力
- 暗算問題を解いてモンスターにダメージを与える
- 回答速度に応じてダメージ倍率が変化
- 3階層のダンジョンを攻略してボスを倒す

2. MomentoのAPIキー・エンドポイントを取得

2.1 Momentoコンソールにアクセス

Momento Consoleにアクセスしてアカウントを作成します。

2.3 キャッシュの作成

  1. 「キャッシュ」セクションで「キャッシュを作成する」をクリック
  2. キャッシュ名を入力(例: math-rpg)し作成

v0-momento_04.png

2.2 APIキーの生成

  1. 「トークン」セクションに移動
  2. 「Fine-Grained Access Key」をクリック
  3. 権限を設定(今回はcachetopicsの権限を付与)し作成
  4. APIキーをコピー

v0-momento_09.png

3. v0にプロンプトをリクエスト

いよいよv0を使ってフロントエンドを生成します!

3.1 v0にアクセス

v0.appにアクセスします。

3.2 プロンプトを入力

要件定義書をそのまま投げます。

v0-momento_01.png

4. APIキーを設定

v0の画面でAPIキーの入力欄が表示されたら、先ほど取得した情報を入力します。

v0-momento_02.png
※画像はNEXT_PUBLIC_が付いていますが、最終的には消しています。

5. デプロイして動作確認

5.1 Vercelへデプロイ

v0の「Deploy」ボタンをクリックするだけです。

5.2 動作確認

デプロイされたURLにアクセスして、動作を確認します。

■タイトル
v0-momento_06.png

■ロビー
v0-momento_05.png

■ドラゴンとの激闘
v0-momento_07.png

■結果
v0-momento_08.png

Momento Cache + Topicsでのリアルタイム同期

アーキテクチャ

今回のゲームでは、CacheTopicsを組み合わせて使用しています。

サービス 役割
Momento Cache ゲーム状態の永続化(ルーム情報、リーダーボード)
Momento Topics リアルタイムイベント配信(ダメージ通知、フロアクリアなど)

Momento Cacheのキー設計

キー TTL 説明
room:{roomId} RoomState (JSON) 1時間 ルームのゲーム状態
leaderboard LeaderboardEntry[] (JSON) 24時間 グローバルランキング

Momento Topicsのトピック設計

トピック 配信イベント 説明
room:{roomId} PLAYER_JOINED, DAMAGE_DEALT, MONSTER_DEFEATED, GAME_CLEAR ルーム内イベント
// 配信されるイベントの例
// ダメージイベント
{
  "type": "DAMAGE_DEALT",
  "playerName": "勇者",
  "damage": 15,
  "time": 1.2,
  "currentHp": 285,
  "timestamp": 1702345678901
}

// モンスター撃破イベント
{
  "type": "MONSTER_DEFEATED",
  "floor": 2,
  "nextMonster": {
    "name": "ドラゴン",
    "maxHp": 500,
    "currentHp": 500
  }
}

さいごに

今回はv0Momentoを組み合わせて、簡単なオンラインゲームを作る方法を紹介しました。
従来のアプローチに比べて、v0とMomentoを組み合わせることで、WebSocketサーバーの構築やインフラ管理の負担を軽減できる場合があります。
Momento Topicsを使えば、ポーリングなしでリアルタイム通信が実現できます。
Momentoは無料枠を提供しているので、試験的な開発にも活用できます。
ぜひ皆さんもこのお手軽な組み合わせでオリジナルのオンラインゲームを作ってみてください。
この記事がどなたかの参考になれば幸いです。

この記事をシェアする

FacebookHatena blogX

関連記事