关于 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 粒子
粒子能让游戏显得反应灵敏。一次命中触发三个火花,带来的扎实感是没有火花时的两倍,而所需工作量只是其他任何增强表现手法的一小部分。
它们也是导致 Phaser 游戏以 20fps 运行的最常见原因。
发射器
const emitter = this.add.particles(x, y, 'texture', config);
一次调用就能创建并添加一个发射器。它是一个普通游戏对象:具有深度、滚动因子和 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 在生命周期内逐渐缩小并淡出。粒子如果以完整尺寸突然消失,看起来就像个 bug。
单次发射还是持续发射
两种模式,混淆它们就会导致效果要么完全不出现,要么永不停止。
爆发式发射 — 爆炸、命中火花、拾取物弹出:
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() 则会让它悬停在半空中。
清理: 每次爆炸都创建一个发射器却从不销毁,会造成泄漏。可以创建一个发射器并重复调用 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 时,一个发射器就有 100 个活动粒子——如果有十个这样的发射器,就有 1,000 个。
其他优化手段,按大致收益排序:
| 手段 | 原因 |
|---|---|
缩短 lifespan | 在视觉密度不变的情况下减少活动粒子 |
在 onLeave 上使用死亡区域 | 屏幕外粒子的开销与可见粒子完全相同 |
| 使用更少、更大的粒子 | 20 个大火花比 200 个小火花更醒目,开销也低 10 倍 |
| 重用同一个发射器 | 发射器频繁创建和销毁的开销比粒子本身还大 |
| 使用更小的纹理 | 粒子纹理通常无需超过 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() |
| 不可见 | 发射器深度低于背景——emitter.setDepth(n) |
| 不可见 | alpha: { start: 0 }——归零的是起始值,而不是结束值 |
| 出现一帧后消失 | lifespan 过短 |
| 位置错误 | 发射器的 x/y 是世界坐标;在容器内时,它们是局部坐标 |
| 不跟随 | 在 update() 中设置位置,而不是使用 startFollow() |
| 帧率骤降 | 没有设置 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— 移动端粒子预算