game-architecture について
Three.jsまたはPhaserでブラウザーゲームを構成する開発者向けのアーキテクチャスキルです。コアループ、イベント駆動のモジュール、状態と設定の一元管理に加え、安全な再起動と出荷前の検証も扱います。
主な機能
- 01コアループ優先の開発
- 02EventBusによる通信パターン
- 03状態と設定の一元管理
- 04安全な再起動のライフサイクル実践
- 05階層化されたシステム構成
- 06出荷前の検証チェックリスト
活用シーン
- 01ブラウザーゲームの設計計画
- 02イベントによるゲームモジュールの疎結合化
- 03再起動後の古い状態の防止
- 04出荷前のゲーム準備状況の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ playableintelligence/ game-creator --skill game-architectureインストール後、Claude Code で/game-architectureを入力して読み込まれたか確認します。
同梱ファイルsystem-patterns.md
詳細
ゲームアーキテクチャパターン
適切に構成されたブラウザーゲームを構築するためのリファレンスです。これらのパターンは、Three.js(3D)と Phaser(2D)の両方のゲームに適用できます。
リファレンスファイル
詳しい内容については、このディレクトリにある関連ファイルを参照してください。
system-patterns.md— オブジェクトプーリング、デルタタイムの正規化、リソースの破棄、ウェーブ/スポーンシステム、バフ/パワーアップシステム、触覚フィードバック、アセット管理
基本原則
-
コアループを最優先:演出を加える前に、最低限のゲームプレイループを実装します。順序は、入力 -> 移動 -> 失敗条件 -> スコア計算 -> 再スタートです。コアループが動作してから、ビジュアル、オーディオ、演出を追加してください。最初のスコープは小さく保ちます。シーン/レベルは1つ、メカニクスは1つ、失敗条件は1つにします。
-
イベント駆動型の通信:通信のためにモジュール同士が互いをインポートすることはありません。モジュール間のメッセージはすべて、定義済みのイベント定数を持つシングルトンの EventBus を介して送信します。
-
状態の一元管理:すべてのゲーム状態は、単一の GameState シングルトンで管理します。各システムは状態を直接読み取り、イベントを介して変更します。モジュール間に状態を分散させません。
-
設定の一元管理:マジックナンバー、バランス値、アセットパス、スポーン地点、タイミング値はすべて
Constants.jsにまとめます。ゲームロジックのファイルにハードコードされた値を含めてはいけません。 -
オーケストレーターパターン:1つの
Game.jsクラスですべてのシステムを初期化し、ゲームの進行(起動 -> ゲームプレイ -> 死亡/勝利 -> 再スタート)を管理して、メインループを実行します。システム自身による初期化は行いません。デフォルトではタイトル画面を設けません。ゲームプレイに直接移行します。ユーザーから明示的な要望があった場合にのみ、タイトル/メニューシーンを追加します。 -
再スタートへの対応と決定性:ゲームプレイは、完全な再スタートを繰り返しても正常に動作しなければなりません。
GameState.reset()は状態を完全に初期状態へ戻します。クリーンアップ/シャットダウン時に、すべてのイベントリスナーを削除します。古い参照、残存タイマー、解放されていないトゥイーン、孤立した物理ボディが再スタート後に残らないようにします。3回連続で再スタートしてテストし、3回目も1回目と同じように動作することを確認します。 -
関心事を明確に分離:コードは機能レイヤーごとに整理します。
core/- 基盤(Game、EventBus、GameState、Constants)systems/- エンジンレベルのシステム(入力、物理演算、オーディオ、パーティクル)gameplay/- ゲームメカニクス(プレイヤー、敵、武器、スコア計算)level/- ワールド構築(レベル構築、アセット読み込み)ui/- インターフェース(メニュー、HUD、オーバーレイ)
イベントシステムの設計
イベントの命名規則
機能領域ごとにグループ化し、domain:action 形式を使用します。
export const Events = {
// Player
PLAYER_DAMAGED: 'player:damaged',
PLAYER_HEALED: 'player:healed',
PLAYER_DIED: 'player:died',
// Enemy
ENEMY_SPAWNED: 'enemy:spawned',
ENEMY_KILLED: 'enemy:killed',
// Game flow
GAME_STARTED: 'game:started',
GAME_PAUSED: 'game:paused',
GAME_OVER: 'game:over',
// System
ASSETS_LOADED: 'assets:loaded',
LOADING_PROGRESS: 'loading:progress'
};
イベントデータの契約
プリミティブ値ではなく、構造化されたデータオブジェクトを常に渡します。
// Good
eventBus.emit(Events.PLAYER_DAMAGED, { amount: 10, source: 'enemy', damageType: 'melee' });
// Bad
eventBus.emit(Events.PLAYER_DAMAGED, 10);
状態管理
GameState の構造
状態を明確な領域ごとに整理します。
class GameState {
constructor() {
this.player = { health, maxHealth, speed, inventory, buffs };
this.combat = { killCount, waveNumber, score };
this.game = { started, paused, isPlaying };
}
}
ゲームの進行
2Dゲームと3Dゲームの両方に共通する標準的な進行です。
Boot/Load -> Gameplay <-> Pause Menu (if requested)
-> Game Over -> Gameplay (restart)
デフォルトではタイトル画面を設けません。 ゲームはゲームプレイに直接移行します。Play.fun ウィジェットがゲーム上部のデッドゾーンでスコア表示、リーダーボード、ウォレット接続を処理するため、ゲーム内のスコア HUD は不要です。ユーザーから明示的な要望があった場合にのみ、タイトル/メニューシーンを追加します。
よくあるアーキテクチャ上の落とし穴
- 物理ボディの接続漏れ — 静的な物理ボディ(地面や壁など)を作成しても、
physics.add.collider()またはphysics.add.overlap()を使ってほかのボディに接続しなければ、ゲームプレイに影響しません。境界や障害物には、相互作用するエンティティとの衝突設定を明示的に行う必要があります。静的なボディを作成したら、すぐにコライダーの呼び出しを追加してください。 - 重なった表示オブジェクトに操作要素が遮られる — UI(ボタンやメニューなど)を作成する場合、シーンリストで最前面にある表示オブジェクトがポインターイベントを受け取ります。操作要素を装飾レイヤーの背後に隠さないでください。ビジュアル要素自体を操作可能にするか、ヒット領域の上に何も描画されないようにしてください。
- ゲームプレイより先に演出を追加する — コアとなるゲームループが動作する前に、パーティクルや画面の揺れ、トランジションを追加するのは、よくある時間の浪費です。まずは入力 → アクション → 失敗条件 → スコア計算 → 再スタートが機能するようにしてください。それ以外はすべて仕上げの演出です。
- 再スタート時のクリーンアップ漏れ —
shutdown()でイベントリスナーの削除、タイマーの破棄、リソースの解放を忘れると、再スタート後にゴースト動作やイベントの二重発火、メモリリークが発生します。
出荷前の検証チェックリスト
ゲームを完成とみなす前に、以下のすべてを確認してください。
- コアとなるゲームループ — プレイヤーがゲームを開始してプレイし、勝敗が決まり、結果を確認できる
- 再スタート — 3回連続で、同じ動作を保ったまま問題なく動作する
- モバイル入力 — タッチ/タップ/スワイプ/ジャイロが動作する。タップ領域は最低44px
- デスクトップ入力 — キーボードとマウスが動作する
- レスポンシブ対応 — ウィンドウのサイズ変更時にキャンバスが正しくリサイズされる
- 定数 — ゲームロジックにハードコードされたマジックナンバーがない
- EventBus — 通信にモジュール間の直接インポートを使用していない
- クリーンアップ — シャットダウン時にすべてのリスナーが削除され、リソースが解放される
- ミュート切り替え —
mute-buttonルールを参照 - デルタベース — すべての移動でフレーム数ではなくデルタタイムを使用する
- ビルド —
npm run buildがエラーなしで成功する - エラーなし — 実行時に捕捉されない例外やコンソールエラーがない