Claude Media
Claude CodeでPhaserゲームを作る — スクリーンショットで動作を確かめさせる

Claude CodeでPhaserゲームを作る — スクリーンショットで動作を確かめさせる

Phaserのブラウザゲームは画面がcanvasに描かれるため、DOMの検査では中身が見えません。Playwright MCPのスクリーンショットと状態取得を検証ループに組み込む手順を示します。

PhaserはJavaScriptで書くHTML5ゲームのフレームワークです。Claude Codeにコードを書かせるところまでは普通のWeb開発と変わりません。難しいのは「動いたか」の確認です。ゲームの画面はcanvas要素に描かれるので、ページのDOMを調べても、キャラクターが画面のどこにいるかは分かりません。

この記事では、Playwright MCPのbrowser_take_screenshotとbrowser_evaluateを使い、Claude Code自身にゲームを起動させ、画面と内部状態の両方で確かめさせる検証ループを組みます。MCPの導入そのものはPlaywright MCPサーバーの使い方にあります。ここでは、canvasゲーム特有の組み立て方に絞ります。

Phaserゲームの検証が通常のWebアプリと違う理由

通常のWebアプリなら、Claude Codeはボタンやテキストの存在をDOMから確かめられます。Playwright MCPのbrowser_snapshotは、ページをアクセシビリティツリー(役割と名前で要素を表したデータ)として返す仕組みです。

Phaserのゲームでは事情が変わります。PhaserのPhaser.Gameは設定のwidthとheightの大きさでcanvasを1枚作り、スプライトも文字も背景もその中に描きます。DOMに現れるのはcanvas要素1つで、プレイヤーやスコア表示は個別の要素になりません。スナップショットを取っても、ゲームの中身は読み取れない形です。

そこで検証手段を2本立てにします。

くらべる

canvasゲームを確かめる2つの手段

見た目の確認

スクリーンショット

描画が崩れていないか、キャラクターが画面内にいるか、レイアウトが意図どおりかを画像で見ます。見た目の不具合はこれでしか拾えません。

数値の確認

browser_evaluate

ページ上でJavaScriptを実行し、座標・スコア・シーン名をJSONで返させます。画像からは読み取りにくい数値の正誤をこちらで判定します。

スクリーンショットだけに頼ると、画像の読み取りで「たぶん動いている」と判断してしまう余地が残ります。状態の数値を併用すれば、判定が曖昧になりません。

準備 — Phaserプロジェクトと開発サーバー

プロジェクトの作成は、Phaser公式のcreate-phaser-gameが手早い方法です。公式のインストールページは、次のコマンドで対話的にテンプレートを選べると説明しています。

npm create @phaserjs/game@latest

手作業でも構いません。npm install phaserでライブラリを入れ、バンドラーかCDN読み込みでindex.htmlから使う形です。

もう一点、index.htmlをブラウザへドラッグして開く方法は使えません。公式の開発環境ガイドは、file://で開いたページはブラウザが強く制限し、画像・音声・JSONなどのアセットを読み込めないため、http://で配信するWebサーバーが要ると説明しています。簡易な手段として、Pythonの内蔵HTTPサーバーやhttp-server(Node.js)が挙がっています。

# ゲームのフォルダで実行(ポートは任意)
python3 -m http.server 8000

Playwright MCPがアクセスするのもhttp://localhost:8000/のようなURLです。Claude Codeへの追加は次の1行です。

claude mcp add playwright -- npx @playwright/mcp@latest

--より後ろがMCPサーバーとして起動するコマンドです。この区切りの意味は、Claude Code公式のMCPドキュメントにも書かれています。

ゲームに「状態を返す口」を付ける

画像だけでなく数値でも確かめるために、ゲーム側に確認用の口を1つ用意します。windowにシーンの状態を返す関数を生やす方法です。次の例は、矢印キーで動く四角とスコアだけの最小のシーンです。公式の「Hello World」と同じ形式の、アーケード物理を使う構成を前提にしています。

class Main extends Phaser.Scene {
  create() {
    this.player = this.add.rectangle(400, 500, 40, 40, 0x44aaff);
    this.physics.add.existing(this.player);
    this.player.body.setCollideWorldBounds(true);
    this.cursors = this.input.keyboard.createCursorKeys();
    this.score = 0;
    this.label = this.add.text(16, 16, 'score: 0');
 
    // 検証用: テスト時にだけ状態を読めるようにする
    window.__gameState = () => ({
      scene: this.scene.key,
      player: { x: this.player.x, y: this.player.y },
      score: this.score,
    });
  }
 
  update() {
    const body = this.player.body;
    body.setVelocityX(
      this.cursors.left.isDown ? -200 : this.cursors.right.isDown ? 200 : 0
    );
  }
}
 
new Phaser.Game({
  type: Phaser.AUTO,
  width: 800,
  height: 600,
  backgroundColor: '#1d1d2b',
  scene: Main,
  physics: { default: 'arcade' },
});

Phaser.AUTOは、ブラウザのWebGL対応を見て描画方式を自動で選ぶ指定です。いずれの方式でも、見える画面はcanvas1枚になります。

window.__gameStateは検証専用です。公開ビルドに残したくないときは、location.searchに?debug=1が付いたときだけ定義する、といった分岐を入れておくと安全です。

Claude Codeに渡す検証ルール

口を作ったら、手順をCLAUDE.mdに書いておきます。毎回口頭で指示しなくても、変更のたびに同じ確認が走るようになります。次は書き方の一例です。

## ゲームの動作確認ルール
- コードを変更したら、完了を宣言する前に必ず次を行う
  1. python3 -m http.server 8000 を別プロセスで起動する(起動済みなら再利用)
  2. browser_navigate で http://localhost:8000/ を開く
  3. browser_console_messages でエラーが出ていないか見る
  4. browser_take_screenshot で画面を撮り、描画を目で確かめる
  5. browser_evaluate で window.__gameState() を呼び、座標とスコアを読む
- 操作を伴う変更は、browser_press_key で入力し、前後の __gameState を比較する
- 確かめられなかった項目は「未確認」と報告し、成功と書かない

最後の一行が大事です。画像の読み取りには限界があり、画面の一部だけが崩れていても見落とすことがあります。確認できなかった項目を成功と混ぜさせない指示は、ここで効きます。

検証ループの回し方

実際の流れは次のとおりです。CLAUDE.mdの手順どおりに動かせば、Claude Codeが以下の順でツールを呼びます。

手順

1回の検証サイクル

  1. 1

    ページを開いてエラーを見る

    browser_navigateでゲームのURLを開き、browser_console_messagesでコンソールの出力を取ります。アセットの読み込み失敗や例外は、画面が真っ黒になる原因の大半です。スクリーンショットの前に確認すると、原因の切り分けが早くなります。

  2. 2

    初期状態を撮る

    browser_take_screenshotで画面を保存し、browser_evaluateでwindow.__gameState()を呼びます。画像と数値が食い違えば、その時点で描画と内部状態のずれが分かります。

  3. 3

    入力して差分を見る

    browser_press_keyでArrowRightなどを押し、もう一度状態を取ります。xが増えていれば入力が効いています。

  4. 4

    結果を指示にフィードバックする

    差分が期待と違えば、Claude Codeがコードを直し、手順1からやり直します。

スクリーンショットの取得は、Playwright MCPのREADMEによれば既定ではプロジェクトの出力ディレクトリにpage-{timestamp}.pngとして保存されます。filenameを渡せば保存先を指定でき、fullPageやscaleも選べます。READMEは同じツールの説明で、スクリーンショットをもとに操作は行えず、操作にはbrowser_snapshotを使うよう求めています。ゲームでは操作をキー入力で行うので、この制約はそのまま問題になりません。

キー入力の落とし穴

browser_press_keyが受けるのは、ArrowLeftやaのように1つのキー名です。READMEの説明に、押しっぱなしを表す引数はありません。

上の例のように、isDownで毎フレーム判定する移動は、短いキー押下では1フレームも反応しないことがあります。入力の取りこぼしを避けるには、次の対応が考えられます。

  • キーの押下イベント(keydown)で1歩動く、ステップ型の操作にする
  • 検証用にwindow.__debug.move(dx)のような関数を足し、入力処理を介さず状態だけ動かす
  • テスト用に操作を再現するスクリプトをbrowser_evaluateで流す

どれを選ぶかはゲームの種類で変わります。アクションゲームの手触りまで確かめたいなら、人間が実際に触る確認は別に残してください。MCP経由の検証が保証するのは、描画と状態が壊れていないことまでです。

座標でクリックしたいとき

ゲーム内のボタンをクリックさせたい場合、canvasの中の要素はアクセシビリティツリーにありません。Playwright MCPでは、--caps visionを付けて起動すると座標指定のマウス操作(browser_mouse_click_xyなど)が使えるようになります。READMEは、これを座標ベースの操作を有効にするオプションとして案内しています。

claude mcp add playwright -- npx @playwright/mcp@latest --caps vision

クリックはブラウザ画面上の座標で指定するため、表示サイズがずれると狙った位置から外れます。browser_resizeで表示サイズをゲームに合わせてから押す、という順にしてください。

画像の確認が苦手なことと対策

スクリーンショットで確かめられるのは、大きな破綻です。スプライトが画面外にある、背景が黒いまま、といった異常は検出しやすい一方で、数ピクセルのずれや色の微妙な違いは見逃しやすくなります。対策は3つです。

  1. 位置・サイズ・スコアのように数値で判定できるものは、__gameStateの値で見る
  2. 見た目の確認は、確かめたい点を指示に書く(「プレイヤーが画面下半分にいるか」など)
  3. 同じ操作列を毎回同じ手順で流し、前回の画像と比べる

設定を変えるたびにゲームの見た目が変わるのは普通の挙動です。画像を「正解」とみなして厳密に比較するより、数値の許容範囲を決めて判定するほうが運用は安定します。

他の開発環境との比較

ゲームエンジンによって、検証の手段は変わります。UnityやGodotはコマンドラインで起動してログを読める形があり、手順はUnityゲーム開発とGodotゲーム開発で扱っています。RustのBevyも同様です。それらと違い、Phaserはブラウザで動くため、Playwrightのようなブラウザ操作ツールがそのまま使える点が強みです。

既存のE2Eテストを自動実行する運用はPlaywright E2Eテストをhookで回す手順が詳しい内容です。画像から画面を作る逆方向の使い方はスクリーンショットからUIを実装する手順にあります。

よくあるつまずき

画面が真っ黒のまま何も表示されない

file://で開いていないかを疑います。アセットの読み込みに失敗している可能性があります。http://localhostで開き直し、browser_console_messagesでエラーを読みます。

window.__gameState is not a function

関数を定義するcreate()がまだ呼ばれていません。アセットの読み込みが終わる前にbrowser_evaluateを実行すると起きます。テキストはcanvasに描かれるので、browser_wait_forのテキスト待ちは使えません。browser_wait_forのtimeで数秒待つか、browser_evaluateでtypeof window.__gameState === 'function'を確かめてから呼びます。

毎回ブラウザのウィンドウが開く

Playwright MCPの既定はヘッドありです。表示が不要なら--headlessを起動オプションに足します。

まとめ

Phaserのゲームは、画面の内容がDOMに現れません。スクリーンショットで見た目を、browser_evaluateで状態の数値を見る二本立ての検証ループを作り、手順をCLAUDE.mdに書いておけば、Claude Codeが変更のたびに自分で確かめるようになります。入力は短い押下になる点を、ゲームの設計側で吸収しておくことが肝です。

この記事を共有:XはてブLinkedIn