phaser について
コーディングエージェント向けに、Phaser 3 のブラウザーゲーム開発を支援するスキルです。シーン構成やモバイル入力を扱い、TypeScript、イベント駆動の通信、安全な再起動を重視します。
主な機能
- 01TypeScript優先のPhaser 3パターン
- 02シーンベースのゲーム構成
- 03EventBusと集中管理された状態
- 04Spectacleイベントフック
- 05モバイルとデスクトップの入力ガイド
- 06パフォーマンスとクリーンアップの実践
活用シーン
- 01Phaser 3ブラウザーゲームの足場作り
- 02シーンとゲームシステムの構成
- 03タッチ操作とキーボード操作の対応
- 04決定的なゲーム再起動の実現
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ playableintelligence/ game-creator --skill phaserインストール後、Claude Code で/phaserを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets-and-performance.mdconventions.mdgame-objects.mdno-asset-design.mdpatterns.mdphysics-and-movement.mdproject-setup.mdscenes-and-lifecycle.mdexamples/complex-game.mdsimple-game.md詳細
Phaser 3 ゲーム開発
game-creator プラグインを使用してゲームを開発する、Phaser のエキスパート開発者として、以下のパターンに従い、構成が整い、見た目も洗練された、保守しやすい 2D ブラウザーゲームを作成してください。
基本原則
- まずコアループを実装する — 見た目を整える前に、最小限のゲームプレイループを実装します:起動 → プリロード → 作成 → 更新。ビジュアル、オーディオ、演出を追加する前に、勝敗条件とスコアリングを追加します。初期スコープは小さく保ちます:1 シーン、1 メカニクス、1 失敗条件。コアループと並行して、演出用 EventBus フック(
SPECTACLE_*イベント)も組み込みます。これらは後回しにする仕上げではなく、土台の一部です。 - TypeScript を優先する — 型安全性と IDE サポートのため、常に TypeScript を使用します
- シーンベースのアーキテクチャ — 各ゲーム画面を Scene とし、役割を絞ります
- Vite によるバンドル — 公式の
phaserjs/template-vite-tsテンプレートを使用します - 継承よりコンポジション — 深いクラス階層より、振る舞いを組み合わせる方法を優先します
- データ駆動設計 — レベル、敵、設定を JSON/データファイルで定義します
- イベント駆動の通信 — シーンやシステム間の通信はすべて EventBus 経由にします
- 再起動に安全な設計 — ゲームプレイは完全に再起動に対応し、決定論的でなければなりません。
GameState.reset()は状態を完全に初期状態へ戻す必要があります。再起動後に古い参照、残存タイマー、リークしたイベントリスナーが残らないようにします。
演出イベント
プレイヤーの操作とゲーム内イベントはすべて、少なくとも 1 つの演出イベントを発行する必要があります。これらのフックはテンプレートの EventBus に用意されており、デザイン工程で視覚効果を割り当てます。
| イベント | 定数 | 発行するタイミング |
|---|---|---|
spectacle:entrance | SPECTACLE_ENTRANCE | プレイヤーやエンティティが初めて画面に登場する create() 内 |
spectacle:action | SPECTACLE_ACTION | プレイヤーの入力(タップ、ジャンプ、射撃、スワイプ)のたび |
spectacle:hit | SPECTACLE_HIT | プレイヤーが敵に攻撃を当てる/倒す、アイテムを獲得する、または得点する時 |
spectacle:combo | SPECTACLE_COMBO | ミスせず連続して攻撃を当てる/得点する時。{ combo: n } を渡します |
spectacle:streak | SPECTACLE_STREAK | コンボが節目(5、10、25、50)に達した時。{ streak: n } を渡します |
spectacle:near_miss | SPECTACLE_NEAR_MISS | プレイヤーが危険をかろうじて回避した時(衝突半径の約 20% 以内) |
ルール:ゲームプレイ上のイベントに演出イベントがない場合は、追加してください。フックがなければ、デザイン工程で演出を加えられません。
必須規約
すべてのゲームはgame-creator の規約に従う必要があります:
core/ディレクトリに EventBus、GameState、Constants を配置- EventBus シングルトン —
domain:action形式のイベント名を使用し、シーンを直接参照しない - GameState シングルトン —
reset()によるクリーンな再起動に対応する、一元管理された状態 - Constants ファイル — マジックナンバー、色、速度、設定値をすべて定義し、ハードコードされた値をゼロにする
- シーンのクリーンアップ —
shutdown()で EventBus のリスナーを削除
詳しくは、conventions.md を参照してください。設定やコード例も記載されています。
プロジェクトのセットアップ
出発点として、公式の Vite + TypeScript テンプレートを使用します:
npx degit phaserjs/template-vite-ts my-game
cd my-game && npm install
必須ディレクトリ構成
src/
├── core/
│ ├── EventBus.ts # Singleton event bus + event constants
│ ├── GameState.ts # Centralized state with reset()
│ └── Constants.ts # ALL config values
├── scenes/
│ ├── Boot.ts # Minimal setup, start Game scene
│ ├── Preloader.ts # Load all assets, show progress bar
│ ├── Game.ts # Main gameplay (starts immediately, no title screen)
│ └── GameOver.ts # End screen with restart
├── objects/ # Game entities (Player, Enemy, etc.)
├── systems/ # Managers and subsystems
├── ui/ # UI components (buttons, bars, dialogs)
├── audio/ # Audio manager, music, SFX
├── config.ts # Phaser.Types.Core.GameConfig
└── main.ts # Entry point
設定やツールの詳細については、project-setup.md を参照してください。
シーンアーキテクチャ
- ライフサイクル:
init()→preload()→create()→update(time, delta) - シーン遷移からデータを受け取る場合は
init()を使用します - アセットは各シーンではなく、専用の
Preloaderシーンで読み込みます update()は軽量に保ち、サブシステムやゲームオブジェクトに処理を委譲します- デフォルトではタイトル画面を作らない — ゲームプレイへ直接移行します。ユーザーから明示的な要望があった場合に限り、タイトル/メニューシーンを追加します
- ゲーム内にスコア HUD を作らない — Play.fun ウィジェットがゲーム上部のデッドゾーンにスコアを表示します。スコア表示用の独立した UIScene や HUD オーバーレイは作成しないでください
- UI オーバーレイ(ポーズメニュー)用の並列シーンは、要望があった場合に限り使用します
Play.fun セーフゾーン
モバイル Safari でゲームを Play.fun ダッシュボード内で実行すると、SDK はゲームの iframe の document.documentElement に CSS カスタムプロパティを設定します。
--ogp-safe-top-inset— Play.fun ヘッダーのバブルの下の余白(モバイルでは約68px)--ogp-safe-bottom-inset— Safari の下部コントロールの上の余白(モバイルでは約148px)
ダッシュボード内で実行されていない場合(デスクトップ、スタンドアロン)は、どちらもデフォルトで 0px です。
テンプレートの Constants.js は起動時にこれらを読み取り、キャンバスのピクセル単位(CSS 値 × DPR)で SAFE_ZONE.TOP と SAFE_ZONE.BOTTOM を公開します。静的なフォールバック(GAME.HEIGHT * 0.08)により、SDK がなくても上部のセーフゾーンが機能します。
ルール:
- すべての UI テキスト、ボタン、HUD 要素は、
SAFE_ZONE.TOPより下、かつGAME.HEIGHT - SAFE_ZONE.BOTTOMより上に配置する必要があります - ゲームプレイのエンティティはセーフゾーン内にスポーンさせないでください
- ゲームオーバー画面、スコアパネル、再スタートボタンは、
SAFE_ZONE.TOPとSAFE_ZONE.BOTTOMの両方を考慮してオフセットする必要があります - UI シーンで比例位置を計算するには、
const usableH = GAME.HEIGHT - SAFE_ZONE.TOP - SAFE_ZONE.BOTTOMを使用します - ゲームのキャンバスと背景は、ビューポート全体を埋める必要があります(ブラウザーのクロームの背後まで広げます)
- 画面下部のタッチコントロールでは、
SAFE_ZONE.BOTTOMを考慮する必要があります
import { SAFE_ZONE } from '../core/Constants.js';
// In any UI scene:
const safeTop = SAFE_ZONE.TOP;
const safeBottom = SAFE_ZONE.BOTTOM;
const usableH = GAME.HEIGHT - safeTop - safeBottom;
const title = this.add.text(cx, safeTop + usableH * 0.15, 'GAME OVER', { ... });
const button = createButton(scene, cx, safeTop + usableH * 0.6, 'PLAY AGAIN', callback);
// Touch controls / bottom HUD:
const bottomY = GAME.HEIGHT - safeBottom - 40 * PX;
Constants.js での仕組み:
function _readSafeInsets() {
const s = getComputedStyle(document.documentElement);
const top = parseInt(s.getPropertyValue('--ogp-safe-top-inset')) || 0;
const bottom = parseInt(s.getPropertyValue('--ogp-safe-bottom-inset')) || 0;
return { top: top * DPR, bottom: bottom * DPR };
}
const _insets = _readSafeInsets();
export const SAFE_ZONE = {
TOP: Math.max(GAME.HEIGHT * 0.08, _insets.top),
BOTTOM: _insets.bottom,
LEFT: 0,
RIGHT: 0,
};
- シーン間の通信には EventBus を使用します(直接参照は使用しません)
パターンと例については、scenes-and-lifecycle.md を参照してください。
ゲームオブジェクト
- カスタムオブジェクトを作成するには、
Phaser.GameObjects.Sprite(または他の基底クラス)を拡張します - オブジェクトプーリング(弾、コイン、敵)には
Phaser.GameObjects.Groupを使用します - 複合オブジェクトには
Phaser.GameObjects.Containerを使用します。ただし、深いネストは避けてください - シーンからアクセスできるように、カスタムオブジェクトを
GameObjectFactoryに登録します
実装パターンについては、game-objects.md を参照してください。
物理演算
- Arcade Physics — シンプルなゲーム(プラットフォーマー、トップダウン型)に使用します。高速で軽量です。
- Matter.js — リアルな衝突、拘束、または複雑な形状が必要な場合に使用します。
- 同じゲームで物理エンジンを混在させないでください。
- キャラクターの動き(待機、歩行、ジャンプ、攻撃)にはステートパターンを使用します。
詳細については、physics-and-movement.md を参照してください。
パフォーマンス(重要なルール)
- テクスチャアトラスを使用する — スプライトをアトラスにまとめ、大規模な用途で個別の画像を読み込まないでください
- オブジェクトプーリング —
maxSizeを指定した Group を使用し、setActive(false)/setVisible(false)で再利用します - 更新処理を最小限にする — アクティブなオブジェクトだけを反復処理します。
getChildren().filter(c => c.active)を使用します - カメラカリング — 大規模なワールドで有効にします。画面外のオブジェクトは描画をスキップします
- バッチ描画 — フレームごとの固有テクスチャが少ないほど、ドローコールのバッチ処理が効率化されます
- モバイル — パーティクル数を減らし、物理演算を簡略化し、30fps を目標として検討します
pixelArt: true— ピクセルアートのゲームでは、ゲーム設定で有効にします(ニアレストネイバー方式のスケーリング)
最適化ガイドの全容については、assets-and-performance.md を参照してください。
高度なパターン
- bitECS による ECS — データ指向設計のためのエンティティ・コンポーネント・システム(Phaser 4 で内部的に使用)
- ステートマシン — エンティティの動作状態を整理して管理します
- シングルトンマネージャー — シーン間で共有するサービス(オーディオ、セーブデータ、分析)
- イベントバス — 共有の EventEmitter を使ってシステム間の結合を疎にします
- Tiled との連携 — レベルデザインに Tiled マップエディターを使用します
実装については、patterns.md を参照してください。
モバイル入力戦略(60/40 ルール)
特に指定がない限り、すべてのゲームはデスクトップとモバイルの両方で動作する必要があります。トレードオフを検討する際は、モバイル 60%、デスクトップ 40%を重視してください。ゲームのコンセプトに最適なモバイル入力方式を選びます。
| ゲームの種類 | モバイルの主な入力 | デスクトップの入力 |
|---|---|---|
| プラットフォーマー | 左右どちらか半分をタップ+タップでジャンプ | 矢印キー/WASD |
| ランナー/エンドレス | タップ/上にスワイプしてジャンプ | Space/上矢印キー |
| パズル/マッチ | 対象をタップ(最小 44px) | クリック |
| シューティング | 仮想ジョイスティック+タップで発射 | マウス+WASD |
| トップダウン | 仮想ジョイスティック | 矢印キー/WASD |
実装パターン
ゲームロジックが入力元に依存しないよう、入力を inputState オブジェクトに抽象化します。
// In Scene update():
const isMobile = this.sys.game.device.os.android ||
this.sys.game.device.os.iOS || this.sys.game.device.os.iPad;
let left = false, right = false, jump = false;
// Keyboard
left = this.cursors.left.isDown || this.wasd.left.isDown;
right = this.cursors.right.isDown || this.wasd.right.isDown;
jump = Phaser.Input.Keyboard.JustDown(this.spaceKey);
// Touch (merge with keyboard)
if (isMobile) {
// Left half tap = left, right half = right, or use tap zones
this.input.on('pointerdown', (p) => {
if (p.x < this.scale.width / 2) left = true;
else right = true;
});
}
this.player.update({ left, right, jump });
レスポンシブな Canvas 設定(Retina/高 DPI)
レスポンシブな Canvas の完全な設定、エンティティのサイズ、HTML のボイラープレート、縦向き優先のゲームパターンについては、project-setup.md を参照してください。
表示されるタッチ操作
タッチ対応デバイスでは、必ず視覚的なタッチインジケーターを表示してください。見えないタップ領域に頼ってはいけません。タッチ対応の判定には、OS ベースではなく機能検出を使用します。
// Good — detects touch laptops, tablets, 2-in-1s
const hasTouch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
// Bad — misses touch-screen laptops, iPadOS (reports as desktop)
const isMobile = device.os.android || device.os.iOS;
画面下部に半透明の矢印ボタン(または方向インジケーター)を描画します。サイズ(Canvas 幅の 12%)、アルファ値(非操作時 0.35/操作時 0.6)、マージンには Constants.js の TOUCH 定数を使用してください。入力状態に基づいて update() ループでアルファ値を更新し、視覚的なフィードバックを提供します。
すべてのデバイスでポインター入力(pointerdown、pointermove、pointerup)を有効にしてください。ポインターイベントはマウスとタッチの両方で機能します。これにより、モバイルとデスクトップで入力コードを分ける必要がなくなります。
モバイル向けのエンティティ最小サイズ
収集アイテム、障害物、操作可能なアイテムは、スマートフォンの画面で認識できるよう、少なくとも GAME.WIDTH の 7~8% の大きさにしてください。これより小さいエンティティは、モバイルでは見分けのつかない塊になります。
// Good — recognizable on mobile
ATTACK_WIDTH: _canvasW * 0.09,
POWERUP_WIDTH: _canvasW * 0.072,
// Bad — too small on phone screens
ATTACK_WIDTH: _canvasW * 0.04,
POWERUP_WIDTH: _canvasW * 0.035,
メインのプレイヤーキャラクターには、GAME.WIDTH の 12~15%を使用してください(上記の「エンティティのサイズ」を参照)。
ボタンのパターン(Container+Graphics+Text)
ボタンの完全な実装パターン(Container+Graphics+Text、ホバー/押下状態)と、避けるべき壊れたパターンの一覧については、game-objects.md を参照してください。
アンチパターン(避けるべきもの)
- 肥大化した
update()メソッド — すべてのゲームロジックを、条件分岐がネストされた巨大な更新処理に詰め込まないでください。オブジェクトやシステムに処理を委譲してください。 - Scene のインジェクションマップのプロパティを上書きする — プロパティに
world、input、cameras、add、make、scene、sys、game、cache、registry、sound、textures、events、physics、matter、time、tweens、lights、data、load、anims、renderer、pluginsという名前を決して付けないでください。これらは Phaser によって予約されています。 - プーリングせずに
update()でオブジェクトを作成する — これにより GC の急増が発生します。頻繁に作成・破棄するオブジェクトは、必ずプールしてください。フレームごとのコストの高い割り当ては避け、オブジェクト、配列、一時変数を再利用してください。 - アトラスではなく個別のスプライトを読み込む — テクスチャごとに描画呼び出しが発生します。まとめてください。
- シーンを密結合する — シーン間で直接参照を保持しないでください。EventBus を使用してください。
- 更新処理で
deltaを無視する — フレーム単位ではなく、時間に基づく移動には必ずdeltaを使用してください。 - コンテナーを深くネストする — コンテナーは子要素のレンダリングバッチ処理を無効にします。階層はフラットに保ってください。
- クリーンアップを行わない — メモリーリークを防ぐため、
shutdown()でイベントリスナーとタイマーを削除してください。再起動時の安全性に不可欠です。古いリスナーが残っていると、再起動後にイベントが二重に発生したり、予期しない動作が起きたりします。 - 値をハードコードする — 数値はすべて
Constants.tsに記述してください。ゲームロジックにマジックナンバーを使わないでください。 - 物理コライダーを接続しない —
physics.add.existing(obj, true)で静的ボディを作成しても、それだけでは何も起こりません。2 つのボディを接続するには、必ずphysics.add.collider(bodyA, bodyB, callback)を呼び出してください。すべての静的コライダー(地面、壁、足場)には、相互作用させるエンティティと接続するための明示的なコライダーまたはオーバーラップの呼び出しが必要です。 - 見えない、または隠れたボタン要素 — インタラクティブなゲームオブジェクトに
setAlpha(0)を設定し、その上に Graphics などの表示オブジェクトを重ねないでください。ボタンには、常に Container + Graphics + Text のパターンを使用してください(game-objects.md を参照)。よくある問題のあるパターン:(1) Text を追加した後に Graphics の長方形を描画し、ラベルをその背後に隠す。(2) ヒット領域に Zone を作成し、その上に Graphics を描画して、Zone にアクセスできなくする。(3) Text をインタラクティブに設定し、その後に描画した Graphics の背景で覆う。解決方法は常に同じです。まず Container を作成し、Graphics を Container に追加し、その後 Text を Container に追加します(この順序)。Container をインタラクティブな要素にします。 - ミュート切り替えがない —
mute-buttonルールを参照してください。オーディオを使用するゲームには、ミュート切り替え機能が必要です。
例
リリース前の検証チェックリスト
ゲームを完成と判断する前に、次の項目を確認してください。
- コアとなるゲームループが動作する — プレイヤーがゲームを開始してプレイし、敗北または勝利し、その結果を確認できます
- 問題なく再スタートできる —
GameState.reset()で状態が初期化され、古いリスナーやタイマーが残りません - タッチとキーボードで操作できる — モバイルではタップ/スワイプ、デスクトップではキーボード/マウスでゲームを操作できます
- キャンバスが画面サイズに応じて調整される —
Scale.FIT+CENTER_BOTH+ DPR を乗じた寸法でzoom: 1/DPRを設定し、Retina でも鮮明に表示されます - すべての値が Constants にある — ゲームロジックにハードコードされたマジックナンバーがありません
- EventBus のみを使用する — 通信にシーン/モジュール間の直接インポートを使用していません
- シーンをクリーンアップする —
shutdown()ですべての EventBus リスナーを削除します - 物理演算が設定されている — すべての静的ボディに、明示的な
collider()またはoverlap()の呼び出しがあります - オブジェクトプーリングを使用する — 頻繁に生成/破棄するオブジェクトには、
maxSizeを指定した Groups を使用します - デルタ時間に基づいて移動する — すべての移動にフレーム数ではなく
deltaを使用します - ミュートを切り替えられる —
mute-buttonルールを参照してください - スペクタクル用のフックが設定されている — プレイヤーの操作とゲームイベントのすべてで
SPECTACLE_*イベントを発行し、create()で登場シーケンスを開始します - ビルドが成功する —
npm run buildがエラーなしで成功します - コンソールエラーがない — 捕捉されない例外や WebGL の障害なしでゲームが動作します
参考ファイル
| ファイル | トピック |
|---|---|
| conventions.md | ゲーム制作者向けの必須アーキテクチャ規約 |
| project-setup.md | スキャフォールディング、Vite、TypeScript の設定、レスポンシブキャンバス、エンティティのサイズ設定、縦向きモード |
| scenes-and-lifecycle.md | シーンシステムの詳細 |
| game-objects.md | カスタムオブジェクト、グループ、コンテナー、ボタンのパターン |
| physics-and-movement.md | 物理エンジン、移動パターン |
| assets-and-performance.md | アセット、最適化、モバイル |
| patterns.md | ECS、ステートマシン、シングルトン |
| no-asset-design.md | プロシージャルなビジュアル:グラデーション、視差効果、パーティクル、演出 |