关于 particles
面向 Phaser 4 开发者的粒子系统技能,介绍 ParticleEmitter 的创建与配置,以及粒子的发射、更新和销毁控制。涵盖多种发射模式、区域与自定义粒子逻辑。
主要功能
- 01创建和配置 ParticleEmitter
- 02流式发射与爆发模式
- 03生命周期属性与颜色插值
- 04重力井及发射、销毁区域
- 05跟随游戏对象与粒子回调
- 06自定义粒子类和处理器
适用场景
- 01制作持续喷射或瞬间爆发效果
- 02在指定几何区域生成或清除粒子
- 03让粒子效果跟随游戏对象
- 04实现逐帧自定义粒子行为
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ phaserjs/ phaser --skill particles安装后在 Claude Code 中输入/particles确认已加载。
附带文件references/REFERENCE.md
详细介绍
粒子系统
在 Phaser 4 中创建和控制粒子效果——ParticleEmitter 的创建与配置、emitter ops(值格式)、重力井、发射区域和消亡区域、流动模式与爆发模式、跟随游戏对象,以及粒子回调。
关键源码路径: src/gameobjects/particles/
相关技能: ../sprites-and-images/SKILL.md, ../loading-assets/SKILL.md
快速开始
// In a Scene's create() method:
// Basic continuous emitter (flow mode)
const emitter = this.add.particles(400, 300, 'flares', {
frame: 'red',
speed: 200,
lifespan: 2000,
scale: { start: 1, end: 0 },
alpha: { start: 1, end: 0 },
gravityY: 150
});
// One-shot burst (explode mode)
const burst = this.add.particles(400, 300, 'flares', {
frame: 'blue',
speed: { min: 100, max: 300 },
lifespan: 1000,
scale: { start: 0.5, end: 0 },
emitting: false // don't auto-start
});
burst.explode(20); // emit 20 particles at once
核心概念
ParticleEmitter
ParticleEmitter 继承自 GameObject,可直接添加到显示列表中。它既是游戏对象(可定位、可缩放、可遮罩),也是 emitter 本身。没有独立的管理器——this.add.particles() 返回一个 ParticleEmitter 实例。
工厂方法签名:
this.add.particles(x, y, texture, config);
// x, y: world position (both optional, default 0)
// texture: string key or Texture instance
// config: ParticleEmitterConfig object (optional, can call setConfig later)
Mixins: AlphaSingle、BlendMode、Depth、Lighting、Mask、RenderNodes、ScrollFactor、Texture、Transform、Visible。因此你可以调用 setPosition()、setScale()、setDepth()、setBlendMode()、setMask()、setScrollFactor() 等。
Particle
由其 emitter 拥有的轻量级对象。关键属性:x、y、velocityX/Y、accelerationX/Y、scaleX/Y、alpha、angle、rotation、tint、life(总毫秒数)、lifeCurrent(剩余毫秒数)、lifeT(0-1 归一化值)、bounce、delayCurrent、holdCurrent。粒子在内部池化——你无需手动创建它们。
EmitterOp 值格式
大多数配置属性(speed、scale、alpha、angle、x、y 等)接受灵活的值格式:
x: 400 // static value
x: [100, 200, 300, 400] // random pick from array
x: { min: 100, max: 700 } // random float in range
x: { min: 100, max: 700, int: true } // random integer
x: { random: [100, 700] } // random integer shorthand
scale: { start: 0, end: 1 } // ease over lifetime (default linear)
scale: { start: 0, end: 1, ease: 'bounce.out' } // custom ease
scale: { start: 4, end: 0.5, random: true } // random start, ease to end
x: { values: [50, 500, 200, 800], interpolation: 'catmull' } // interpolation
x: { steps: 32, start: 0, end: 576 } // stepped sequential
x: { steps: 32, start: 0, end: 576, yoyo: true } // stepped with yoyo
x: { // custom callbacks
onEmit: (particle, key, t, value) => value,
onUpdate: (particle, key, t, value) => value
}
x: (particle, key, t, value) => value + 50 // emit-time callback shorthand
仅发射(无 onUpdate):angle、delay、hold、lifespan、quantity、speedX、speedY。
发射 + 更新(支持 start/end、onUpdate):accelerationX/Y、alpha、bounce、maxVelocityX/Y、moveToX/Y、rotate、scaleX/Y、tint、x、y。
流动模式与爆发模式(Burst)
流动模式(frequency >= 0):每 frequency 毫秒发射 quantity 个粒子。默认为 frequency: 0(每帧)且 emitting: true。
爆发模式(frequency = -1):一次性批量发射,然后停止。
emitter.flow(100, 5); // 5 particles every 100ms
emitter.flow(100, 5, 50); // auto-stop after 50 total
emitter.explode(30, 200, 400); // burst 30 at position
emitter.explode(30); // burst at emitter position
常见模式
随生命周期缩放、透明度与颜色
// Scale and alpha with custom easing
this.add.particles(400, 300, 'spark', {
lifespan: 2000,
speed: 100,
scale: { start: 1, end: 0, ease: 'power2' },
alpha: { start: 1, end: 0, ease: 'cubic.in' }
});
颜色插值
color 属性在粒子生命周期内沿一组颜色数组进行插值(会覆盖 tint):
this.add.particles(400, 300, 'spark', {
lifespan: 2000, speed: 100, scale: { start: 0.5, end: 0 },
color: [0xfacc22, 0xf89800, 0xf83600, 0x9f0404], colorEase: 'quad.out'
});
为粒子着色
this.add.particles(400, 300, 'spark', { tint: 0xff0000 }); // static
this.add.particles(400, 300, 'spark', { tint: { start: 0xffffff, end: 0xff0000 } }); // over lifetime
重力井
GravityWell 施加平方反比重力,将粒子拉向(或使用负 power 时推离)某个点。
const emitter = this.add.particles(400, 300, 'spark', {
speed: 100, lifespan: 4000, scale: { start: 0.4, end: 0 }, quantity: 2
});
const well = emitter.createGravityWell({
x: 400, y: 300, power: 2, epsilon: 100, gravity: 50
});
// Update at runtime
well.x = 300;
well.power = -1; // negative = repel
// Or create manually and add
const well2 = new Phaser.GameObjects.Particles.GravityWell(500, 200, 3, 100, 50);
emitter.addParticleProcessor(well2);
emitter.removeParticleProcessor(well2);
发射区域(随机)
RandomZone 在形状内的随机位置生成粒子。源必须具有 getRandomPoint(point) 方法——所有 Phaser 几何类(Circle、Ellipse、Rectangle、Triangle、Polygon、Line)都支持此方法,或者提供自定义源:
// Using built-in geometry
this.add.particles(400, 300, 'spark', {
speed: 50, lifespan: 2000,
emitZone: { type: 'random', source: new Phaser.Geom.Circle(0, 0, 100) }
});
// Custom source object (any object with getRandomPoint)
emitter.addEmitZone({
type: 'random',
source: {
getRandomPoint: (point) => {
const a = Math.random() * Math.PI * 2;
point.x = Math.cos(a) * 100;
point.y = Math.sin(a) * 50;
return point;
}
}
});
发射区域(边缘)
EdgeZone 沿形状边缘按顺序放置粒子。源必须具有 getPoints(quantity, stepRate) 方法。Curves、Paths 以及所有几何形状都支持此方法:
this.add.particles(400, 300, 'spark', {
lifespan: 1500, speed: 20,
emitZone: {
type: 'edge',
source: new Phaser.Geom.Circle(0, 0, 150),
quantity: 48, // number of points on edge (use 0 with stepRate instead)
yoyo: false, // reverse direction at ends
seamless: true // remove duplicate endpoint
}
});
// Or add post-creation with any source that has getPoints
emitter.addEmitZone({ type: 'edge', source: geom, quantity: 50, yoyo: false, seamless: true });
多个发射区域: 向 emitZone 传入数组,或多次调用 addEmitZone()。区域按顺序迭代。total 属性控制发射多少粒子后轮换到下一个区域(-1 = 永不轮换)。
this.add.particles(400, 300, 'spark', {
emitZone: [
{ type: 'random', source: new Phaser.Geom.Circle(0, 0, 50) },
{ type: 'random', source: new Phaser.Geom.Circle(200, 0, 50) }
]
});
消亡区域
DeathZone 在粒子进入(或离开)某个区域时将其销毁。源必须具有 contains(x, y) 方法。
// Kill particles entering a rectangle
this.add.particles(400, 100, 'spark', {
speed: 200, lifespan: 5000, gravityY: 100,
deathZone: { type: 'onEnter', source: new Phaser.Geom.Rectangle(300, 400, 200, 50) }
});
// Kill particles leaving a circle (confine to area)
this.add.particles(400, 300, 'spark', {
speed: 100, lifespan: 5000,
deathZone: { type: 'onLeave', source: new Phaser.Geom.Circle(400, 300, 150) }
});
// Custom death zone source (any object with contains)
emitter.addDeathZone({
type: 'onEnter',
source: { contains: (x, y) => x > 600 && y > 400 }
});
跟随游戏对象
const player = this.add.sprite(100, 100, 'player');
const emitter = this.add.particles(0, 0, 'spark', {
speed: 50, lifespan: 800, scale: { start: 0.5, end: 0 }
});
emitter.startFollow(player); // follow position
emitter.startFollow(player, 10, -20); // with offset
emitter.startFollow(player, 0, 0, true); // track visibility too
emitter.stopFollow();
// Or via config:
this.add.particles(0, 0, 'spark', { follow: player, followOffset: { x: 0, y: -20 } });
粒子回调
// Via config
const emitter = this.add.particles(400, 300, 'spark', {
speed: 100, lifespan: 2000,
emitCallback: (particle, emitter) => { /* on emit */ },
deathCallback: (particle) => { /* on death */ }
});
// Or set after creation
emitter.onParticleEmit((particle, emitter) => { /* ... */ });
emitter.onParticleDeath((particle) => { /* ... */ });
// Iterate alive/dead particles
emitter.forEachAlive((particle, emitter) => { /* particle.x, particle.lifeT */ });
Duration、StopAfter 与 Advance
// Auto-stop after 3 seconds (alive particles continue until they expire)
this.add.particles(400, 300, 'spark', { speed: 100, duration: 3000 });
// Emit exactly 50 particles then stop
this.add.particles(400, 300, 'spark', { speed: 100, stopAfter: 50 });
// Pre-warm: fast-forward 2 seconds so particles visible on first frame
this.add.particles(400, 300, 'spark', { speed: 100, lifespan: 2000, advance: 2000 });
// Or manually: emitter.fastForward(2000, 50);
粒子边界(弹跳)
this.add.particles(400, 300, 'spark', {
speed: 200, lifespan: 5000, bounce: 0.8,
bounds: { x: 100, y: 100, width: 600, height: 400 },
collideLeft: true, collideRight: true, collideTop: true, collideBottom: true
});
// Or: emitter.addParticleBounds(100, 100, 600, 400);
纹理帧与动画
// Random frame per particle
this.add.particles(400, 300, 'flares', { frame: ['red', 'green', 'blue'] });
// Sequential frames cycling through with quantity per frame
this.add.particles(400, 300, 'flares', {
frame: { frames: ['red', 'green', 'blue'], cycle: true, quantity: 4 }
});
// Particle animation (plays anim over particle lifetime)
this.add.particles(400, 300, 'explosion', { anim: 'explode_anim', lifespan: 1000 });
// Multiple anims, randomly assigned
this.add.particles(400, 300, 'sheet', {
anim: { anims: ['fire', 'smoke'], cycle: false, quantity: 1 }
});
对粒子排序
this.add.particles(400, 300, 'spark', { sortProperty: 'y', sortOrderAsc: true });
// Or: sortCallback: (a, b) => a.y - b.y
自定义粒子处理器
继承 ParticleProcessor,为每帧应用自定义的逐粒子逻辑。实现 update(particle, delta, step, t):
class WindProcessor extends Phaser.GameObjects.Particles.ParticleProcessor {
constructor (windX, windY) {
super(0, 0);
this.windX = windX;
this.windY = windY;
}
update (particle, delta, step, t) {
particle.velocityX += this.windX * step;
particle.velocityY += this.windY * step;
}
}
emitter.addParticleProcessor(new WindProcessor(0.5, 0));
自定义粒子类
继承 Particle 并重写 update 以实现逐粒子行为。在配置中通过 particleClass 设置:
class TrailParticle extends Phaser.GameObjects.Particles.Particle {
update (delta, step, processors) {
const result = super.update(delta, step, processors);
this.alpha = this.lifeT; // custom: alpha matches life progress
return result; // must return true if particle is still alive
}
}
this.add.particles(400, 300, 'spark', {
particleClass: TrailParticle,
speed: 100, lifespan: 2000
});
配置参考
ParticleEmitterConfig -- 简单属性
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
active | boolean | true | False = 发射器完全不更新 |
emitting | boolean | true | False = 不产生新粒子(存活粒子仍会更新) |
blendMode | string/number | 0 | 用于渲染的混合模式 |
frequency | number | 0 | 流循环之间的毫秒数;0 = 每帧;-1 = 爆炸 |
gravityX, gravityY | number | 0 | 重力,单位为 px/s^2 |
maxParticles | number | 0 | 粒子对象总数的硬性上限(0 = 无限制) |
maxAliveParticles | number | 0 | 同时存活的粒子数上限(0 = 无限制) |
duration | number | 0 | 在指定毫秒数后自动停止(0 = 永久) |
stopAfter | number | 0 | 发射 N 个粒子后自动停止(0 = 无限制) |
advance | number | 0 | 创建时快进(毫秒) |
radial | boolean | true | True = speed+angle;false = speedX/speedY |
particleBringToTop | boolean | true | 新粒子渲染在最上层 |
timeScale | number | 1 | 更新时的时间倍率 |
follow | Vector2Like | null | 要跟随的对象 |
followOffset | Vector2Like | 相对于跟随目标的偏移 | |
trackVisible | boolean | false | 匹配跟随目标的可见性 |
reserve | number | 预分配粒子对象 | |
particleClass | function | Particle | 自定义粒子类 |
sortProperty | string | 用于排序的粒子属性 | |
sortOrderAsc | boolean | 若为 true 则按升序排序 |
ParticleEmitterConfig -- EmitterOp 属性
全部接受上文所述的灵活取值格式。
| 属性 | 默认值 | E/U | 描述 |
|---|---|---|---|
x, y | 0 | E+U | 粒子相对于发射器的偏移 |
speed | 0 | E | 径向速度(设置 speedX,停用 speedY) |
speedX, speedY | 0 | E | 定向速度(设置 radial=false) |
angle | {min:0,max:360} | E | 发射角度,单位为度 |
scale | 1 | E+U | 统一缩放(设置 scaleX,停用 scaleY) |
scaleX, scaleY | 1 | E+U | 非统一缩放 |
alpha | 1 | E+U | Alpha 透明度 |
rotate | 0 | E+U | 旋转角度,单位为度 |
tint | 0xffffff | E+U | 着色颜色(WebGL) |
color | E+U | 用于插值的颜色数组(覆盖 tint) | |
colorEase | 颜色插值的缓动 | ||
lifespan | 1000 | E | 生命周期,单位为毫秒 |
delay | 0 | E | 可见前的延迟(毫秒) |
hold | 0 | E | 在生命结束前保持的时间(毫秒) |
quantity | 1 | E | 每个流循环的粒子数 |
accelerationX/Y | 0 | E+U | 加速度(px/s^2) |
maxVelocityX/Y | 10000 | E+U | 最大速度 |
bounce | 0 | E+U | 反弹恢复系数(0-1) |
moveToX, moveToY | 0 | E+U | 目标位置(覆盖 angle/speed) |
E = 仅发射,E+U = 发射 + 更新(支持 start/end、onUpdate)
区域配置属性
| 配置键 | 类型 | 属性 |
|---|---|---|
emitZone | object 或 array | { type: 'random', source: <shape> } |
{ type: 'edge', source: <shape>, quantity, stepRate, yoyo, seamless, total } | ||
deathZone | object 或 array | { type: 'onEnter'|'onLeave', source: <shape> } |
bounds | object | { x, y, width, height } 或 { x, y, w, h } |
事件
所有事件都在 ParticleEmitter 实例本身上发出。
| 事件 | 字符串 | 回调参数 | 触发时机 |
|---|---|---|---|
START | 'start' | (emitter) | 调用 start() 且发射器开始发射时 |
STOP | 'stop' | (emitter) | 调用 stop(),或达到 duration/stopAfter 限制时 |
COMPLETE | 'complete' | (emitter) | 发射器停止后最后一个存活粒子消亡时 |
EXPLODE | 'explode' | (emitter, particle) | 调用 explode() 时 |
DEATH_ZONE | 'deathzone' | (emitter, particle, zone) | 死亡区域杀死一个粒子时 |
emitter.on('stop', (emitter) => { /* stopped emitting */ });
emitter.on('complete', (emitter) => { /* all particles dead */ });
emitter.on('deathzone', (emitter, particle, zone) => { /* ... */ });
API 快速参考
ParticleEmitter 关键方法
生命周期: start(advance?, duration?)、stop(kill?)、pause()、resume()、flow(frequency, count?, stopAfter?)、explode(count?, x?, y?)、emitParticleAt(x?, y?, count?)、emitParticle(count?, x?, y?)、fastForward(time, delta?)。
配置: setConfig(config)、updateConfig(config)。
跟随: startFollow(target, offX?, offY?, trackVisible?)、stopFollow()。
区域: addEmitZone(config)、removeEmitZone(zone)、clearEmitZones()、addDeathZone(config)、removeDeathZone(zone)、clearDeathZones()。
处理器: createGravityWell(config)、addParticleProcessor(processor)、removeParticleProcessor(processor)、getProcessors()。
边界: addParticleBounds(x, y, w, h, collideL?, collideR?, collideT?, collideB?)。
回调/迭代: onParticleEmit(cb, ctx?)、onParticleDeath(cb, ctx?)、killAll()、forEachAlive(cb, ctx?)、forEachDead(cb, ctx?)。
计数: getAliveParticleCount()、getDeadParticleCount()、getParticleCount()、atLimit()、reserve(count)。
属性设置器: setParticleSpeed(x, y?)、setParticleScale(x, y?)、setParticleGravity(x, y)、setParticleAlpha(value)、setParticleTint(value)、setParticleLifespan(value)、setEmitterAngle(value)、setQuantity(qty)、setFrequency(freq, qty?)、setRadial(value)、setEmitterFrame(frames, random?, qty?)、setAnim(anims, random?, qty?)。
排序: setSortProperty(property, ascending?)、setSortCallback(callback)、depthSort()。
实用工具: getBounds(padding?, advance?, delta?, output?)、overlap(target)。
GravityWell
| 属性/方法 | 描述 |
|---|---|
x, y | 引力井的世界坐标位置 |
power | 力的大小(负值表示排斥) |
epsilon | 力计算的最小距离(默认 100) |
gravity | 引力常数(默认 50) |
active | 启用/禁用处理(继承自 ParticleProcessor) |
构造函数: new GravityWell(x, y, power, epsilon, gravity) 或 new GravityWell(config),其中 config 为 { x, y, power, epsilon, gravity }。
注意事项
- 没有 ParticleEmitterManager: 在 v3.60 中已移除。
this.add.particles()直接返回一个ParticleEmitter。 speed与speedX/speedY:speed会设置 speedX 并停用 speedY(径向)。speedX/speedY会切换到点模式(radial: false)。scale与scaleX/scaleY:scale会应用于 scaleX 并停用 scaleY。若要实现非等比缩放,请同时使用两者。color会覆盖tint: 两者互斥;color(数组)优先。moveToX/moveToY: 必须同时设置才会生效。会覆盖angle和speed。emitting与active:emitting: false= 不产生新粒子,但存活粒子仍会更新。active: false= 整个发射器冻结。stop与complete: 当发射停止时会触发'stop'。当最后一个存活粒子死亡时会触发'complete'。frequency: 0: 表示每帧都发射(最大速率),而不是“从不”。若要阻止发射,请使用emitting: false。frequency: -1: 会让发射器进入爆炸模式——它不会自动持续发射。使用explode()来发射爆发。hold会冻结粒子: 在生命周期结束后,hold会让粒子保持可见并冻结指定的毫秒数,然后才消亡。适用于拖尾/滞留效果。advance会快进: 通过在创建时模拟给定的毫秒数来预预热发射器,因此粒子在第一帧就已经可见。reserve(count)会预分配: 调用reserve()或在配置中设置reserve,以预先创建粒子对象,避免游戏过程中按需分配导致的 GC 峰值。- 区域源方法: RandomZone 需要
getRandomPoint(point)。EdgeZone 需要getPoints(quantity, stepRate)。DeathZone 需要contains(x, y)。 - 粒子池:
maxParticles限制对象总数(不是存活数量)。可见数量限制请使用maxAliveParticles。 - 需要纹理: 发射器需要一个有效的纹理键。多帧纹理请使用
frame配置。
源文件
完整源文件映射请参见 references/REFERENCE.md。关键入口点:src/gameobjects/particles/ParticleEmitter.js(主类)、src/gameobjects/particles/Particle.js(单个粒子)、src/gameobjects/particles/zones/(区域类)。