phaser-particles について
phaser-particlesは、Phaser 4のゲーム開発者向けにパーティクル効果の構築と調整を案内するスキルです。エミッターや効果の設定に加え、粒子数の制限や実行時の検証も扱います。
主な機能
- 01バーストと連続エミッター
- 02粒子の動きと外観の設定
- 03発生ゾーンと消滅ゾーン
- 04移動するゲームオブジェクトへの追従
- 05粒子数とパフォーマンスの指針
- 06実行時のパーティクル検証
活用シーン
- 01ヒット時の火花や取得時の演出を追加
- 02雨、煙、炎の効果を作成
- 03画面外の粒子による負荷を抑制
- 04高負荷時の粒子数をテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yakoub-ai/ phaser4-gamedev --skill phaser-particlesインストール後、Claude Code で/phaser-particlesを入力して読み込まれたか確認します。
同梱ファイルreferences/particle-recipes.md
詳細
Phaser 4 パーティクル
パーティクルは、ゲームが反応しているように感じられるかどうかを左右します。ヒット時に 3 つの火花を出すだけで、同じヒットでも 2 倍しっかりした感触になり、他のあらゆる「ジュース」演出よりもはるかに少ない手間で済みます。
同時に、Phaser のゲームが 20fps で動いてしまう最も一般的な原因でもあります。
エミッター
const emitter = this.add.particles(x, y, 'texture', config);
1 回の呼び出しでエミッターを作成して追加します。これは通常のゲームオブジェクトです。depth、scroll factor、alpha を持ち、コンテナーに入れることもできます。
実用上重要なフィールドをすべて含めた設定例:
const sparks = this.add.particles(0, 0, 'flare', {
// Motion
speed: { min: 50, max: 150 }, // px/sec; a range gives natural spread
angle: { min: 0, max: 360 }, // degrees; direction of travel
gravityY: 300, // px/sec² — falling embers, debris
accelerationX: 0,
// Appearance over life — {start, end} interpolates across the particle's lifespan
scale: { start: 1, end: 0 },
alpha: { start: 1, end: 0 },
rotate: { start: 0, end: 360 },
tint: [0xffaa00, 0xff2200], // an array picks randomly per particle
blendMode: 'ADD', // ADD for light: fire, magic, sparks
// Timing
lifespan: 600, // ms each particle lives
frequency: 40, // ms between emissions; -1 = explode mode only
quantity: 2, // particles per emission
// Budget — always set this
maxParticles: 100,
emitting: false, // create dormant; start it deliberately
});
{ start, end } が主力です。 パーティクルエフェクトが「撒き散らした」ではなく「意図的に見える」かどうかの大部分は、ライフスパンにわたって scale と alpha が縮小・フェードすることにかかっています。最大サイズのまま消えるパーティクルはバグのように見えます。
1 回だけ放出するか、連続して放出するか
2 つのモードがあり、これを混同すると、エフェクトがまったく現れないか、永遠に止まらないかのどちらかになります。
バースト — 爆発、ヒットの火花、アイテム取得のポップ:
const burst = this.add.particles(0, 0, 'spark', {
speed: { min: 100, max: 300 },
lifespan: 400,
scale: { start: 0.8, end: 0 },
emitting: false, // dormant until told otherwise
});
burst.explode(20, x, y); // 20 particles, right here, right now
連続 — たいまつ、煙の柱、雨:
const smoke = this.add.particles(x, y, 'smoke', {
frequency: 100,
quantity: 1,
lifespan: 2000,
emitting: true,
});
smoke.stop(); // stop emitting; existing particles live out their lifespan
smoke.start();
smoke.pause(); // freeze everything, including live particles
smoke.resume();
stop() と pause() は異なり、その違いは目に見えます。stop() は煙の柱を自然に消散させ、pause() は空中で凍結させます。
クリーンアップ: 爆発ごとにエミッターを作成して破棄しないのはリークです。エミッターを 1 つ作成して再 explode() するか、最後のパーティクルが消えた後に破棄してください:
// Reuse — the right default
this.hitSparks.explode(8, enemy.x, enemy.y);
// Or fire-and-forget, cleaned up properly
const e = this.add.particles(x, y, 'spark', { lifespan: 400, emitting: false });
e.explode(20);
this.time.delayedCall(500, () => e.destroy());
移動するオブジェクトを追従する
emitter.startFollow(player, 0, -10); // offsetX, offsetY
emitter.stopFollow();
update() でエミッターの位置を設定するのではなく、これを使ってください。startFollow はフレーム間を追跡するため、高速で移動するオブジェクトは、各フレームの位置に塊が点線状に並ぶのではなく、連続した軌跡を残します。
ゾーン
放出ゾーン — パーティクルが生まれる場所:
// Along the edge of a shape: a ring, a magic circle
emitter.addEmitZone({
type: 'edge',
source: new Phaser.Geom.Circle(0, 0, 50),
quantity: 32,
});
// Anywhere inside a shape: rain across the top of the screen
emitter.addEmitZone({
type: 'random',
source: new Phaser.Geom.Rectangle(0, 0, 800, 10),
});
消滅ゾーン — パーティクルが破棄される場所:
emitter.addDeathZone({
type: 'onEnter', // or 'onLeave'
source: new Phaser.Geom.Rectangle(0, 400, 800, 200),
});
画面サイズの矩形と組み合わせた onLeave は、画面外のパーティクルにコストをかけないようにする方法です。
重力井戸 — 引き寄せるか反発させるか:
emitter.createGravityWell({ x: 400, y: 300, power: 3, epsilon: 100, gravity: 100 });
渦、ブラックホール、コインを引き寄せる磁石のアイテムなどです。
パフォーマンス — リリース前にこれを読んでください
パーティクルは Phaser のゲームでフレームレートが崩壊する最も一般的な原因であり、その崩壊はコードを書いてからずっと後に訪れます。負荷がかかったときにだけ現れるからです。
必ず maxParticles を設定してください。 上限がなく frequency が短いエミッターは、フレームバジェットがなくなるまで平気で確保し続けます。これはあらゆるエミッター設定の中で、単独で最も価値の高い一行です。
maxParticles: 100, // hard ceiling
サイズの見積もり: lifespan / frequency * quantity が定常状態の数です。lifespan: 2000, frequency: 40, quantity: 2 では、1 つのエミッターから 100 個のパーティクルが生きていることになります。そして、そのようなエミッターが 10 個あれば 1000 個です。
その他のレバー、おおよそ効果の大きい順:
| レバー | 理由 |
|---|---|
lifespan を短くする | 同じ視覚的密度でも生きているパーティクルが減る |
onLeave での消滅ゾーン | 画面外のパーティクルは見えているものとまったく同じコストがかかる |
| より少なく、より大きく | 大きな火花 20 個は小さなもの 200 個より見栄えがよく、コストは 10 分の 1 |
| エミッターを 1 つ使い回す | エミッターの生成と破棄はパーティクル本体よりコストがかかる |
| テクスチャを小さくする | パーティクルのテクスチャが 32×32 を超える必要はほとんどない |
モバイルでは ADD を避ける | 加算ブレンドはフィルレートを消費し、モバイルはフィルレートに制約される |
stopAfter | N 個のパーティクルを放出して自動的に停止 — 忘れがちなクリーンアップロジックが不要 |
モバイルのバジェット: エフェクトごとではなく、ゲーム全体で数百個の同時パーティクルです。デスクトップでの数を半分にして、実機で確認してください — skills/phaser-mobile/references/device-profiles.md を参照してください。
数千個の類似したスプライトには、パーティクルは間違ったツールです — skills/phaser-fx/references/renderer-and-shaders.md の SpriteGPULayer を参照してください。
パーティクルが表示されない理由
おおよそ可能性の高い順に並べています。
| 症状 | 原因 |
|---|---|
| まったく何も出ない | emitting: false かつ start() や explode() を一度も呼んでいない |
| まったく何も出ない | テクスチャキーが存在しない — ローダーと、プレイテスト時のアセットチェックを確認してください |
| まったく何も出ない | frequency: -1(explode 専用)なのに explode() を呼んでいない |
| 見えない | エミッターの深度が背景より下にある — emitter.setDepth(n) |
| 見えない | alpha: { start: 0 } — 終了値ではなく開始値が 0 になっている |
| 1 フレームだけ表示されて消える | lifespan が短すぎる |
| 位置がおかしい | エミッターの x/y はワールド座標です。コンテナ内ではローカル座標になります |
| 追従しない | 位置を startFollow() ではなく update() で設定している |
| フレームレートが崩壊する | maxParticles がない |
実行時にパーティクルを検証する
パーティクルのバグは型チェックを完璧に通過します — 誤ったテクスチャキーは文字列ですし、一度も起動しないエミッターも有効なコードです。実際に動作したことを証明しましょう。
// playtest/particles-check.mjs
export default [
{ name: 'hit sparks fire on damage', action: 'eval',
code: `scene('GameScene').hitEnemy(scene('GameScene').enemies[0])` },
{ name: 'particles actually spawned', action: 'expect',
expect: { expression: `scene('GameScene').hitSparks.getAliveParticleCount()`, atLeast: 1 } },
{ name: 'and they are capped', action: 'sample',
expression: `scene('GameScene').hitSparks.getAliveParticleCount()`,
duration: 3000, interval: 100,
expect: { stat: 'max', atMost: 100 } },
{ name: 'frame rate survives the effect', action: 'sample',
expression: `game.loop.actualFps`, duration: 3000, interval: 100,
expect: { stat: 'min', atLeast: 50 } },
];
生存数の max アサーションこそ、上限のないエミッターをプレイヤーより先に捕まえるものです。ゲームが最も重いパーティクル負荷にあるときに実行してください。アイドル状態でではありません。
その他のリソース
リファレンスファイル
references/particle-recipes.md— ゲームで実際に必要になるエフェクトの完全な設定: 爆発、炎、煙、雨、雪、火花、血、魔法、塵、マズルフラッシュ、紙吹雪、軌跡 — それぞれの数値の根拠付きです。
関連
skills/phaser-fx/— フィルターとブレンドモード。ADDパーティクルとブルームアクションの組み合わせが、標準的な「魔法」の見た目ですskills/phaser-analyze/references/performance-playbook.md— パーティクルがフレームを消費する場合skills/phaser-mobile/references/device-profiles.md— モバイルでのパーティクル予算