以前作った Phaser-EZ ライブラリに、Vue版に加えてバニラJS版のサンプルを追加しました。これは業務案件での必要性から生まれたものですが、結果的に「フレームワークを使わずに Phaser-EZ を使いたい」という声にも応えられるようになりました。
背景:実務で感じた課題
実は、バニラJS版を作ったのは外部からの要望対応ではありません。実際の業務案件で Phaser-EZ を使う必要があり、その過程である問題に気づいたからです。
最初のサンプルは Vue との連携にフォーカスしていましたが、業務では Vue を使わないプロジェクトもありました。そこでバニラJS での使用を試してみると...
柔軟性のジレンマ
業務でゲーム開発をしていると、プロジェクトごとに要求が全然違います:
- 「アセットの読み込み方法を変えたい」
- 「独自のシーン管理システムと組み合わせたい」
- 「特定のフレームワークでラップしたい」
- 「メモリ管理を細かく制御したい」
そこで、これらの要望に応えるために、たくさんの設定オプションを用意しました。しかし、その結果...
初期設定がものすごく複雑になってしまいました。
そこで、全く同じゲームロジックを持つバニラJS版サンプルを作成しました。
比較:Vue版 vs バニラJS版
プロジェクト構成の違い
Vue版
examples/vue/
├── src/
│ ├── components/ # Vue コンポーネント
│ ├── stores/ # Pinia ストア
│ ├── game/ # ゲーム関連
│ └── App.vue # メインアプリ
├── package.json # Vue, Pinia, SSG関連の依存関係
└── vite.config.mjs # Vue プラグイン設定
バニラJS版
examples/vanilla/
├── src/
│ ├── scenes/ # ゲームシーン
│ ├── assets/ # ゲームアセット
│ ├── libs/ # ユーティリティ
│ └── main.js # エントリーポイント
├── package.json # 最小限の依存関係
└── vite.config.js # シンプルな設定
依存関係の比較
Vue版の dependencies
{
"phaser": "^3.87.0",
"phaser-ez": "^0.0.218",
"pinia": "^2.3.0",
"vue": "^3.5.13"
}
バニラJS版の dependencies
{
"phaser": "^3.87.0",
"phaser-ez": "^0.0.218"
}
必要最小限の依存関係で済みます。
設定の複雑さを比較してみる
現在の初期化コードの現実
実際に両方のサンプルを見ると、初期設定がかなり複雑になっているのがわかります:
ゲーム初期化コード
Vue版(PhaserGame.vue)
// Vue コンポーネント内
const startGame = async () => {
const config = {
parent: gameContainerId,
type: Phaser.CANVAS,
width: 1024,
height: 768,
// ...
}
// アセットコンテキストの設定(複雑!)
DefaultScene.soundsContext = import.meta.glob('@/assets/sounds/**/*', {
eager: true
})
DefaultScene.imagesContext = import.meta.glob('@/assets/images/**/*', {
eager: true
})
// プリローダーの詳細設定(これも複雑!)
Preloader.imagesCommonContext = import.meta.glob('@/assets/common/images/Preloader/*', {
eager: true
})
Preloader.nextSceneKey = 'MainMenu'
Preloader.config.autoStart = false
// Vue のリアクティブな状態との連携
const appStore = useAppStore()
EventBus.on('scene-start', (scene) => {
selectedScene.value = scene.sceneKey
})
}
バニラJS版(main.js)
// 純粋なJavaScript
const startGame = async () => {
const config = {
parent: 'game-container',
type: Phaser.CANVAS,
width: 1024,
height: 768,
// ...
}
// 同じく複雑なアセットコンテキスト設定
DefaultScene.soundsContext = import.meta.glob('./assets/sounds/**/*', {
eager: true
})
DefaultScene.imagesContext = import.meta.glob('./assets/images/**/*', {
eager: true
})
// 同じくプリローダー設定
Preloader.imagesCommonContext = import.meta.glob('./assets/common/images/Preloader/*', {
eager: true
})
Preloader.nextSceneKey = 'MainMenu'
Preloader.config.autoStart = false
// シンプルなコンソールログ
EventBus.on('scene-start', (scene) => {
console.log(`Scene ${scene.sceneKey} started`)
})
}
場面の実装
ゲームロジック自体は全く同じですが、バニラJS版では Vue特有の部分を削除しています:
Vue版のシーン
// Vue コンポーネントとの連携を想定
EventBus.emit('scene-log', 'User clicked - transitioning to Vue scene')
DefaultScene.start(this, 'Vue')
バニラJS版のシーン
// 純粋なJavaScript版
EventBus.emit('scene-log', 'User clicked - transitioning to Vanilla scene')
DefaultScene.start(this, 'Vanilla')
実際の使用感
Vue版のメリット
- フレームワークとの連携: Vue のリアクティブシステムとの連携が簡単
- コンポーネント分割: UI部分を Vue コンポーネントで管理
- 状態管理: Pinia によるグローバル状態管理
- 開発体験: Vue Devtools, ホットリロード
バニラJS版のメリット
- 軽量: 最小限の依存関係
- シンプル: フレームワークの学習コストなし
- 高速: 余分な抽象化レイヤーなし
- 自由度: どんなプロジェクトにも組み込みやすい
具体的な使い方
バニラJS版の始め方
# サンプルをクローン
git clone https://github.com/gimwachan-git/phaser-ez.git
cd phaser-ez/examples/vanilla
# 依存関係をインストール
pnpm install
# 開発サーバー起動
pnpm run dev
新しいプロジェクトで使う場合
# 新しいプロジェクトを作成
mkdir my-game
cd my-game
pnpm init -y
# 必要な依存関係をインストール
pnpm install phaser phaser-ez vite
# シンプルなゲームを作成
mkdir -p src/assets/images/MyScene
echo 'console.log("Hello Phaser-EZ!")' > src/main.js
プロジェクトの柔軟性を実感
今回バニラJS版を作ってみて、Phaser-EZ の設計思想が正しかったと確信しました:
1. フレームワーク非依存の設計
- 内部的に Phaser と軽量なユーティリティのみ使用
- Vue, React, Angular など、どのフレームワークとも組み合わせ可能
- もちろんフレームワークなしでも使用可能
2. 段階的な導入
- 既存のプロジェクトに徐々に導入可能
- 最小限の変更で Phaser-EZ の恩恵を受けられる
- 必要に応じて機能を追加していける
3. 実装の自由度
- UI部分は好きな技術で実装
- ゲームロジックは Phaser-EZ で統一
- 状態管理も自由に選択
使い分けの指針
Vue版を選ぶべき場合
- Vue.js を使った Web アプリケーション内にゲームを組み込みたい
- UI とゲームの状態を密に連携させたい
- SSG(Static Site Generation)で配信したい
- Vue の開発体験を活用したい
バニラJS版を選ぶべき場合
- 軽量なゲームを作りたい
- 既存のプロジェクトに組み込みたい
- フレームワークの学習コストを避けたい
- 最大限の自由度を求める
技術的な工夫
共通コードの維持
両方のサンプルで同じアセットとアニメーションライブラリを使用することで、コードの重複を避けました:
// 共通のアニメーションライブラリ
import { createFallAnimation } from '../libs/Phaser/animations/index.js'
// Vue版でもバニラJS版でも同じように使用
createFallAnimation(this, this.logo)
ビルド設定の最適化
それぞれの用途に最適化したビルド設定を用意:
Vue版: SSG対応、コンポーネント分割、開発ツール統合
バニラJS版: 最小限の設定、高速ビルド、シンプルな構成
課題:設定が複雑すぎる問題
正直に言うと、現在の Phaser-EZ は "OOTB"(out-of-the-box)ではありません。
上記のコードを見ると分かる通り、Vue版もバニラJS版も、初期設定で大量のボイラープレートコードが必要です:
- アセットコンテキストの設定(6行)
- プリローダーの詳細設定(4行)
- EventBusリスナーの設定(複数行)
これは「簡単に使えるライブラリ」とは言えません。
原因:柔軟性を追求しすぎた
業務でのカスタマイズ要求に応えるため、たくさんの設定オプションを用意した結果:
// 現在:設定項目が多すぎる
DefaultScene.soundsContext = ...
DefaultScene.imagesContext = ...
DefaultScene.videosContext = ...
DefaultScene.persistentSounds = ...
Preloader.imagesCommonContext = ...
Preloader.soundsCommonContext = ...
Preloader.nextSceneKey = ...
Preloader.config.autoStart = ...
Preloader.imagesCommonFilenames = ...
// まだまだ続く...
理想:こうなってほしい
// vite.config.js
import { defineConfig } from 'vite'
import { phaserEZ } from 'vite-plugin-phaser-ez'
export default defineConfig({
plugins: [
phaserEZ({
scenes: './src/scenes/*.js',
assets: './src/assets',
autoStart: true
})
]
})
// main.js - シンプルに
import { createGame } from 'phaser-ez'
const game = createGame() // Viteプラグインが全部やってくれる
解決策:Viteプラグインを検討中
現在、この問題を解決するために Viteプラグイン の開発を検討しています。みんなが Vite を使っている今の流れに合わせて、より自然な開発体験を提供したいと思います。
Viteプラグインの構想
// vite.config.js
import { defineConfig } from 'vite'
import { phaserEZ } from 'vite-plugin-phaser-ez'
export default defineConfig({
plugins: [
phaserEZ({
// 基本設定
scenes: './src/scenes/*.js',
assets: './src/assets',
// オプション設定
autoStart: true,
sceneTransitions: true,
memoryManagement: 'auto',
// フレームワーク統合
framework: 'vue', // 'vue' | 'react' | 'none'
})
]
})
// main.js - 驚くほどシンプル
import { createGame } from 'phaser-ez'
// Viteプラグインが build time に全部解析してくれる
const game = createGame()
Viteプラグインの利点
- ビルド時最適化: シーンとアセットをビルド時に自動解析
- HMR対応: 開発中のホットリロードが効く
- 型安全性: TypeScriptでアセットの型も自動生成
- エコシステム統合: 他のViteプラグインとの組み合わせが自然
- 設定の一元化: vite.config.js ですべて完結
段階的な機能提供
// レベル1:最小構成
export default defineConfig({
plugins: [phaserEZ()] // デフォルト設定で即座に動く
})
// レベル2:カスタマイズ
export default defineConfig({
plugins: [
phaserEZ({
scenes: './src/scenes/*.js',
assets: './src/assets'
})
]
})
// レベル3:フル機能
export default defineConfig({
plugins: [
phaserEZ({
scenes: './src/scenes/*.js',
assets: './src/assets',
framework: 'vue',
preloader: {
autoStart: false,
customLoader: './src/CustomLoader.vue'
},
performance: {
memoryManagement: true,
assetOptimization: true
}
})
]
})
まとめ
Phaser-EZ の魅力は「必要な機能だけを使って、好きな技術と組み合わせられる」ことです。しかし、現在は初期設定が複雑になってしまい、本来の魅力を活かしきれていません。
現在の状況:
- Vue を使いたい人: Vue版サンプルを参考に(ただし設定が複雑)
- フレームワークを使いたくない人: バニラJS版サンプルを参考に(同じく複雑)
- React を使いたい人: バニラJS版をベースに React と組み合わせ(やはり複雑)
理想の未来:
- 簡単に始めたい人: 基本プラグインで即座にスタート
- カスタマイズしたい人: 専用プラグインで柔軟に拡張
- 上級者: 独自プラグインで完全カスタマイズ
プラグインシステムが実現すれば、真に「使いやすくて柔軟なライブラリ」になると確信しています。
次回予告
プラグインシステムの設計と実装について、詳しく記事にする予定です。また、React 版のサンプルも作る予定ですが、その時にはプラグインベースの新しいアーキテクチャで実装したいと思います。
リンク
- GitHub: https://github.com/gimwachan-git/phaser-ez
- Vue版サンプル: examples/vue/
- バニラJS版サンプル: examples/vanilla/
- ドキュメント: README.ja.md