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 8000Playwright 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
ページを開いてエラーを見る
browser_navigateでゲームのURLを開き、browser_console_messagesでコンソールの出力を取ります。アセットの読み込み失敗や例外は、画面が真っ黒になる原因の大半です。スクリーンショットの前に確認すると、原因の切り分けが早くなります。 - 2
初期状態を撮る
browser_take_screenshotで画面を保存し、browser_evaluateでwindow.__gameState()を呼びます。画像と数値が食い違えば、その時点で描画と内部状態のずれが分かります。 - 3
入力して差分を見る
browser_press_keyでArrowRightなどを押し、もう一度状態を取ります。xが増えていれば入力が効いています。 - 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つです。
- 位置・サイズ・スコアのように数値で判定できるものは、
__gameStateの値で見る - 見た目の確認は、確かめたい点を指示に書く(「プレイヤーが画面下半分にいるか」など)
- 同じ操作列を毎回同じ手順で流し、前回の画像と比べる
設定を変えるたびにゲームの見た目が変わるのは普通の挙動です。画像を「正解」とみなして厳密に比較するより、数値の許容範囲を決めて判定するほうが運用は安定します。
他の開発環境との比較
ゲームエンジンによって、検証の手段は変わります。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が変更のたびに自分で確かめるようになります。入力は短い押下になる点を、ゲームの設計側で吸収しておくことが肝です。