pixi-particles-engine 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/behaviour.d.ts +69 -0
- package/dist/behaviour.d.ts.map +1 -0
- package/dist/behaviour.js +2 -0
- package/dist/behaviour.js.map +1 -0
- package/dist/behaviours/alpha-behaviour.d.ts +18 -0
- package/dist/behaviours/alpha-behaviour.d.ts.map +1 -0
- package/dist/behaviours/alpha-behaviour.js +21 -0
- package/dist/behaviours/alpha-behaviour.js.map +1 -0
- package/dist/behaviours/alpha-curve-behaviour.d.ts +22 -0
- package/dist/behaviours/alpha-curve-behaviour.d.ts.map +1 -0
- package/dist/behaviours/alpha-curve-behaviour.js +24 -0
- package/dist/behaviours/alpha-curve-behaviour.js.map +1 -0
- package/dist/behaviours/behaviour-utils.d.ts +1 -0
- package/dist/behaviours/behaviour-utils.d.ts.map +1 -0
- package/dist/behaviours/behaviour-utils.js +2 -0
- package/dist/behaviours/behaviour-utils.js.map +1 -0
- package/dist/behaviours/curved-behaviour/curve-key-frame.d.ts +22 -0
- package/dist/behaviours/curved-behaviour/curve-key-frame.d.ts.map +1 -0
- package/dist/behaviours/curved-behaviour/curve-key-frame.js +2 -0
- package/dist/behaviours/curved-behaviour/curve-key-frame.js.map +1 -0
- package/dist/behaviours/curved-behaviour/curve-sampler.d.ts +29 -0
- package/dist/behaviours/curved-behaviour/curve-sampler.d.ts.map +1 -0
- package/dist/behaviours/curved-behaviour/curve-sampler.js +75 -0
- package/dist/behaviours/curved-behaviour/curve-sampler.js.map +1 -0
- package/dist/behaviours/movement-behaviours/gravity-behaviour.d.ts +35 -0
- package/dist/behaviours/movement-behaviours/gravity-behaviour.d.ts.map +1 -0
- package/dist/behaviours/movement-behaviours/gravity-behaviour.js +35 -0
- package/dist/behaviours/movement-behaviours/gravity-behaviour.js.map +1 -0
- package/dist/behaviours/movement-behaviours/movement-curve-behaviour.d.ts +29 -0
- package/dist/behaviours/movement-behaviours/movement-curve-behaviour.d.ts.map +1 -0
- package/dist/behaviours/movement-behaviours/movement-curve-behaviour.js +30 -0
- package/dist/behaviours/movement-behaviours/movement-curve-behaviour.js.map +1 -0
- package/dist/behaviours/movement-behaviours/radial-burst-behaviour.d.ts +37 -0
- package/dist/behaviours/movement-behaviours/radial-burst-behaviour.d.ts.map +1 -0
- package/dist/behaviours/movement-behaviours/radial-burst-behaviour.js +42 -0
- package/dist/behaviours/movement-behaviours/radial-burst-behaviour.js.map +1 -0
- package/dist/behaviours/scale-curve-behaviour.d.ts +21 -0
- package/dist/behaviours/scale-curve-behaviour.d.ts.map +1 -0
- package/dist/behaviours/scale-curve-behaviour.js +26 -0
- package/dist/behaviours/scale-curve-behaviour.js.map +1 -0
- package/dist/behaviours/spawn-behaviours/circle-spawn-behaviour.d.ts +16 -0
- package/dist/behaviours/spawn-behaviours/circle-spawn-behaviour.d.ts.map +1 -0
- package/dist/behaviours/spawn-behaviours/circle-spawn-behaviour.js +20 -0
- package/dist/behaviours/spawn-behaviours/circle-spawn-behaviour.js.map +1 -0
- package/dist/behaviours/spawn-behaviours/rectangle-spawn-behaviour.d.ts +15 -0
- package/dist/behaviours/spawn-behaviours/rectangle-spawn-behaviour.d.ts.map +1 -0
- package/dist/behaviours/spawn-behaviours/rectangle-spawn-behaviour.js +21 -0
- package/dist/behaviours/spawn-behaviours/rectangle-spawn-behaviour.js.map +1 -0
- package/dist/behaviours/static-behaviours/static-rotation-behaviour.d.ts +30 -0
- package/dist/behaviours/static-behaviours/static-rotation-behaviour.d.ts.map +1 -0
- package/dist/behaviours/static-behaviours/static-rotation-behaviour.js +24 -0
- package/dist/behaviours/static-behaviours/static-rotation-behaviour.js.map +1 -0
- package/dist/behaviours/static-behaviours/static-scale-behaviour.d.ts +13 -0
- package/dist/behaviours/static-behaviours/static-scale-behaviour.d.ts.map +1 -0
- package/dist/behaviours/static-behaviours/static-scale-behaviour.js +15 -0
- package/dist/behaviours/static-behaviours/static-scale-behaviour.js.map +1 -0
- package/dist/emitter.d.ts +207 -0
- package/dist/emitter.d.ts.map +1 -0
- package/dist/emitter.js +340 -0
- package/dist/emitter.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/px-particle.d.ts +61 -0
- package/dist/px-particle.d.ts.map +1 -0
- package/dist/px-particle.js +76 -0
- package/dist/px-particle.js.map +1 -0
- package/dist/texture-provider.d.ts +62 -0
- package/dist/texture-provider.d.ts.map +1 -0
- package/dist/texture-provider.js +2 -0
- package/dist/texture-provider.js.map +1 -0
- package/dist/texture-providers/animated-texture-provider.d.ts +98 -0
- package/dist/texture-providers/animated-texture-provider.d.ts.map +1 -0
- package/dist/texture-providers/animated-texture-provider.js +72 -0
- package/dist/texture-providers/animated-texture-provider.js.map +1 -0
- package/dist/texture-providers/single-texture-provider.d.ts +14 -0
- package/dist/texture-providers/single-texture-provider.d.ts.map +1 -0
- package/dist/texture-providers/single-texture-provider.js +15 -0
- package/dist/texture-providers/single-texture-provider.js.map +1 -0
- package/dist/texture-providers/weighted-texture-provider.d.ts +24 -0
- package/dist/texture-providers/weighted-texture-provider.d.ts.map +1 -0
- package/dist/texture-providers/weighted-texture-provider.js +36 -0
- package/dist/texture-providers/weighted-texture-provider.js.map +1 -0
- package/dist/utils.d.ts +6 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +12 -0
- package/dist/utils.js.map +1 -0
- package/package.json +49 -0
package/dist/emitter.js
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { ParticleContainer, Ticker } from "pixi.js";
|
|
2
|
+
import { PxParticle } from "./px-particle";
|
|
3
|
+
/**
|
|
4
|
+
* Emitter is a ParticleContainer that owns a pool of {@link PxParticle} instances.
|
|
5
|
+
*
|
|
6
|
+
* Core responsibilities:
|
|
7
|
+
* - Keep a pool of particles to avoid allocations during gameplay.
|
|
8
|
+
* - Emit particles according to the selected EmissionMode.
|
|
9
|
+
* - Update active particles each tick (movement, behaviours, textures).
|
|
10
|
+
* - Recycle dead particles back into the pool.
|
|
11
|
+
*/
|
|
12
|
+
export class Emitter extends ParticleContainer {
|
|
13
|
+
constructor(options, textureProvider) {
|
|
14
|
+
super({
|
|
15
|
+
label: "Emitter",
|
|
16
|
+
...options.containerOptions,
|
|
17
|
+
/**
|
|
18
|
+
* Important optimization:
|
|
19
|
+
* We enable only the dynamic properties that are actually needed
|
|
20
|
+
* (based on behaviours + texture provider requirements).
|
|
21
|
+
*/
|
|
22
|
+
dynamicProperties: Emitter.computeDynamicProperties(options.behaviours ?? [], textureProvider),
|
|
23
|
+
});
|
|
24
|
+
/** Inactive particle pool (reused). */
|
|
25
|
+
this.pool = [];
|
|
26
|
+
/** Active particles currently simulated and rendered. */
|
|
27
|
+
this.active = [];
|
|
28
|
+
/**
|
|
29
|
+
* Sorted behaviours (priority + stable insertion order).
|
|
30
|
+
* Behaviours are applied in order every frame.
|
|
31
|
+
*/
|
|
32
|
+
this.behaviours = [];
|
|
33
|
+
this.nextBehaviourOrder = 0;
|
|
34
|
+
/** Accumulator used to compute spawn counts in rate mode. */
|
|
35
|
+
this.emitAcc = 0;
|
|
36
|
+
/** Accumulator used to track time between waves in wave mode. */
|
|
37
|
+
this.waveAcc = 0;
|
|
38
|
+
this.tickerAttached = false;
|
|
39
|
+
this.maxParticles = options.maxParticles;
|
|
40
|
+
this.mode = options.mode;
|
|
41
|
+
this.ratePerSecond = options.ratePerSecond;
|
|
42
|
+
this.waveInterval = options.waveInterval;
|
|
43
|
+
this.particlesPerWave = options.particlesPerWave;
|
|
44
|
+
this.lifetime = options.lifetime;
|
|
45
|
+
this.emitting = options.emitting;
|
|
46
|
+
this.maxDeltaSeconds = options.maxDeltaSeconds ?? 0.1;
|
|
47
|
+
this.addAtBack = options.addAtBack;
|
|
48
|
+
this.textureProvider = textureProvider;
|
|
49
|
+
// Register behaviours (sorted by priority + stable order).
|
|
50
|
+
if (options.behaviours) {
|
|
51
|
+
for (const behaviour of options.behaviours) {
|
|
52
|
+
this.addBehaviour(behaviour);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Pre-allocate particles upfront:
|
|
57
|
+
* - avoids runtime allocations / GC spikes
|
|
58
|
+
* - allows "maxParticles" to be a hard cap
|
|
59
|
+
*
|
|
60
|
+
* Each pooled particle gets an initial texture (required by Pixi Particle).
|
|
61
|
+
* The actual texture may be replaced on spawn by the provider.
|
|
62
|
+
*/
|
|
63
|
+
for (let i = 0; i < this.maxParticles; i++) {
|
|
64
|
+
const p = new PxParticle({ texture: this.textureProvider.initialTexture() });
|
|
65
|
+
this.pool.push(p);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* In manual mode we do NOT attach to a ticker.
|
|
69
|
+
* User code calls emitBurst/emitWave and also needs to call updateEmitter manually
|
|
70
|
+
* (or you can provide a separate public update method if you prefer).
|
|
71
|
+
*
|
|
72
|
+
* NOTE: currently updateEmitter is still public and can be called manually.
|
|
73
|
+
*/
|
|
74
|
+
this.ticker = options.ticker ?? Ticker.shared;
|
|
75
|
+
if (this.mode !== "manual") {
|
|
76
|
+
this.attachTicker();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Adds a behaviour and inserts it into the sorted execution order.
|
|
81
|
+
* Priority controls order; ties are resolved by insertion order.
|
|
82
|
+
*/
|
|
83
|
+
addBehaviour(b) {
|
|
84
|
+
this.behaviours.push({
|
|
85
|
+
b,
|
|
86
|
+
order: this.nextBehaviourOrder++,
|
|
87
|
+
prio: b.priority ?? 0,
|
|
88
|
+
});
|
|
89
|
+
// lower priority runs earlier
|
|
90
|
+
this.behaviours.sort((a, c) => a.prio - c.prio || a.order - c.order);
|
|
91
|
+
// Optional init hook for behaviour to cache references or precompute curves.
|
|
92
|
+
b.init?.(this);
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Ticker callback: advances simulation and handles emission.
|
|
97
|
+
*
|
|
98
|
+
* The emitter uses ticker.deltaMS (milliseconds between frames) converted to seconds.
|
|
99
|
+
* We clamp dt to maxDeltaSeconds to avoid large jumps and excessive spawning.
|
|
100
|
+
*/
|
|
101
|
+
updateEmitter(ticker) {
|
|
102
|
+
const maxDt = this.maxDeltaSeconds;
|
|
103
|
+
let dt = ticker.deltaMS / 1000;
|
|
104
|
+
// Clamp dt for stability (e.g. tab in background).
|
|
105
|
+
if (dt > maxDt)
|
|
106
|
+
dt = maxDt;
|
|
107
|
+
if (dt <= 0)
|
|
108
|
+
return;
|
|
109
|
+
// Spawn new particles if enabled.
|
|
110
|
+
if (this.emitting)
|
|
111
|
+
this.emitParticles(dt);
|
|
112
|
+
/**
|
|
113
|
+
* Update particles & kill dead ones.
|
|
114
|
+
* Iterate backwards so we can remove by index safely.
|
|
115
|
+
*/
|
|
116
|
+
for (let i = this.active.length - 1; i >= 0; i--) {
|
|
117
|
+
const p = this.active[i];
|
|
118
|
+
// Texture provider may animate / swap textures per frame.
|
|
119
|
+
this.textureProvider.update?.(p, dt);
|
|
120
|
+
// Base integrator: apply velocity and angular velocity.
|
|
121
|
+
// Behaviours can also modify velocity, position, rotation, etc.
|
|
122
|
+
p.age += dt;
|
|
123
|
+
p.x += p.vx * dt;
|
|
124
|
+
p.y += p.vy * dt;
|
|
125
|
+
p.rotation += p.angleV * dt;
|
|
126
|
+
// Apply behaviours in sorted order.
|
|
127
|
+
for (const entry of this.behaviours)
|
|
128
|
+
entry.b.update?.(p, dt, this);
|
|
129
|
+
// Kill particle if it exceeded its lifetime.
|
|
130
|
+
if (p.age >= p.life) {
|
|
131
|
+
this.killAtIndex(i);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Emits particles according to the emitter's mode.
|
|
137
|
+
* This method is called automatically each tick when emitting=true.
|
|
138
|
+
*/
|
|
139
|
+
emitParticles(dt) {
|
|
140
|
+
if (this.mode === "rate") {
|
|
141
|
+
const rate = this.ratePerSecond ?? 0;
|
|
142
|
+
if (rate <= 0)
|
|
143
|
+
return;
|
|
144
|
+
/**
|
|
145
|
+
* Accumulate fractional time and convert into "how many particles should we emit".
|
|
146
|
+
* This produces stable emission even with variable frame rates.
|
|
147
|
+
*/
|
|
148
|
+
this.emitAcc += dt;
|
|
149
|
+
const want = Math.floor(this.emitAcc * rate);
|
|
150
|
+
if (want <= 0)
|
|
151
|
+
return;
|
|
152
|
+
// Keep the remainder time after spawning `want` particles.
|
|
153
|
+
this.emitAcc -= want / rate;
|
|
154
|
+
for (let i = 0; i < want; i++)
|
|
155
|
+
this.spawnOne();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (this.mode === "wave") {
|
|
159
|
+
const interval = this.waveInterval ?? 0.25;
|
|
160
|
+
const perWave = this.particlesPerWave ?? 1;
|
|
161
|
+
if (interval <= 0 || perWave <= 0)
|
|
162
|
+
return;
|
|
163
|
+
// Emit full waves when the accumulated time crosses the interval.
|
|
164
|
+
this.waveAcc += dt;
|
|
165
|
+
while (this.waveAcc >= interval) {
|
|
166
|
+
this.waveAcc -= interval;
|
|
167
|
+
for (let i = 0; i < perWave; i++)
|
|
168
|
+
this.spawnOne();
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Spawns a single particle from the pool.
|
|
175
|
+
*
|
|
176
|
+
* IMPORTANT:
|
|
177
|
+
* - If the pool is empty, spawning is skipped (hard cap).
|
|
178
|
+
* - Texture is selected by TextureProvider at spawn time.
|
|
179
|
+
* - Behaviours' onSpawn hooks initialize per-particle state.
|
|
180
|
+
*/
|
|
181
|
+
spawnOne() {
|
|
182
|
+
const p = this.pool.pop();
|
|
183
|
+
if (!p)
|
|
184
|
+
return;
|
|
185
|
+
if (this.textureProvider.textureForSpawn)
|
|
186
|
+
p.texture = this.textureProvider.textureForSpawn(p);
|
|
187
|
+
p.onSpawn();
|
|
188
|
+
// Allow behaviours to initialize the particle for this spawn.
|
|
189
|
+
for (const behaviour of this.behaviours)
|
|
190
|
+
behaviour.b.onSpawn?.(p, this);
|
|
191
|
+
// Add particle to the container (front or back).
|
|
192
|
+
if (this.addAtBack) {
|
|
193
|
+
this.addParticleAt(p, 0);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
this.addParticle(p);
|
|
197
|
+
}
|
|
198
|
+
// Randomize lifetime in [min, max].
|
|
199
|
+
const { min, max } = this.lifetime;
|
|
200
|
+
p.life = min + Math.random() * Math.max(0, max - min);
|
|
201
|
+
this.active.push(p);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Kills and recycles a particle at a given index in the active list.
|
|
205
|
+
*
|
|
206
|
+
* Order of operations:
|
|
207
|
+
* 1) behaviour kill hooks (reverse order, mirroring teardown)
|
|
208
|
+
* 2) provider kill hook
|
|
209
|
+
* 3) reset particle instance
|
|
210
|
+
* 4) remove from container + active list
|
|
211
|
+
* 5) return to pool
|
|
212
|
+
*/
|
|
213
|
+
killAtIndex(activeIndex) {
|
|
214
|
+
const p = this.active[activeIndex];
|
|
215
|
+
// Call behaviours' teardown in reverse to match common init/apply ordering expectations.
|
|
216
|
+
for (let i = this.behaviours.length - 1; i >= 0; i--) {
|
|
217
|
+
this.behaviours[i].b.onKill?.(p, this);
|
|
218
|
+
}
|
|
219
|
+
this.textureProvider.onKill?.(p);
|
|
220
|
+
p.onKill();
|
|
221
|
+
this.removeParticle(p);
|
|
222
|
+
/**
|
|
223
|
+
* Remove from active list using "swap remove":
|
|
224
|
+
* - O(1)
|
|
225
|
+
* - does not preserve ordering (fine for particle sims)
|
|
226
|
+
*/
|
|
227
|
+
const last = this.active.length - 1;
|
|
228
|
+
if (activeIndex !== last)
|
|
229
|
+
this.active[activeIndex] = this.active[last];
|
|
230
|
+
this.active.pop();
|
|
231
|
+
this.pool.push(p);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Immediately kills all active particles and resets emission accumulators.
|
|
235
|
+
* Useful when restarting an effect or changing scenes.
|
|
236
|
+
*/
|
|
237
|
+
clearParticles() {
|
|
238
|
+
this.emitAcc = 0;
|
|
239
|
+
this.waveAcc = 0;
|
|
240
|
+
while (this.active.length > 0) {
|
|
241
|
+
this.killAtIndex(this.active.length - 1);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Manual emission helpers.
|
|
246
|
+
* Only relevant if mode === "manual" or emitting === false.
|
|
247
|
+
*/
|
|
248
|
+
emitBurst(count) {
|
|
249
|
+
for (let i = 0; i < count; i++)
|
|
250
|
+
this.spawnOne();
|
|
251
|
+
}
|
|
252
|
+
emitWave() {
|
|
253
|
+
const n = this.particlesPerWave ?? 0;
|
|
254
|
+
for (let i = 0; i < n; i++)
|
|
255
|
+
this.spawnOne();
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Computes ParticleContainer dynamicProperties from:
|
|
259
|
+
* - TextureProvider.requires
|
|
260
|
+
* - each Behaviour.requires
|
|
261
|
+
*
|
|
262
|
+
* Keeping these minimal is important for performance:
|
|
263
|
+
* enabling extra dynamic properties can increase per-frame GPU uploads.
|
|
264
|
+
*/
|
|
265
|
+
static computeDynamicProperties(behaviours, provider) {
|
|
266
|
+
const props = {
|
|
267
|
+
position: false,
|
|
268
|
+
rotation: false,
|
|
269
|
+
vertex: false,
|
|
270
|
+
uvs: false,
|
|
271
|
+
color: false,
|
|
272
|
+
};
|
|
273
|
+
// Texture provider may require updates like uvs (animated textures) or vertex.
|
|
274
|
+
if (provider.requires) {
|
|
275
|
+
for (const key in provider.requires) {
|
|
276
|
+
props[key] = true;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Behaviours declare which GPU-updated properties they touch over time.
|
|
280
|
+
for (const b of behaviours) {
|
|
281
|
+
if (!b.requires)
|
|
282
|
+
continue;
|
|
283
|
+
for (const key in b.requires) {
|
|
284
|
+
props[key] = true;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return props;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Attaches the emitter update loop to the configured ticker.
|
|
291
|
+
*/
|
|
292
|
+
attachTicker() {
|
|
293
|
+
if (this.tickerAttached)
|
|
294
|
+
return;
|
|
295
|
+
if (!this.updateEmitterBound) {
|
|
296
|
+
this.updateEmitterBound = this.updateEmitter.bind(this);
|
|
297
|
+
}
|
|
298
|
+
if (!this.ticker) {
|
|
299
|
+
this.ticker = Ticker.shared;
|
|
300
|
+
}
|
|
301
|
+
this.ticker.add(this.updateEmitterBound);
|
|
302
|
+
this.tickerAttached = true;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Detaches the emitter update loop from the ticker.
|
|
306
|
+
*/
|
|
307
|
+
detachTicker() {
|
|
308
|
+
if (!this.tickerAttached)
|
|
309
|
+
return;
|
|
310
|
+
if (!this.ticker || !this.updateEmitterBound)
|
|
311
|
+
return;
|
|
312
|
+
this.ticker.remove(this.updateEmitterBound);
|
|
313
|
+
this.tickerAttached = false;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Changes the emission mode at runtime.
|
|
317
|
+
*
|
|
318
|
+
* - Switching to "manual" detaches the ticker.
|
|
319
|
+
* - Switching to "rate" or "wave" attaches the ticker.
|
|
320
|
+
*
|
|
321
|
+
*/
|
|
322
|
+
setMode(mode) {
|
|
323
|
+
if (this.mode === mode)
|
|
324
|
+
return;
|
|
325
|
+
const wasManual = this.mode === "manual";
|
|
326
|
+
const willBeManual = mode === "manual";
|
|
327
|
+
this.mode = mode;
|
|
328
|
+
if (!wasManual && willBeManual) {
|
|
329
|
+
this.detachTicker();
|
|
330
|
+
}
|
|
331
|
+
else if (wasManual && !willBeManual) {
|
|
332
|
+
this.attachTicker();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
destroy(options) {
|
|
336
|
+
this.detachTicker();
|
|
337
|
+
super.destroy(options);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
//# sourceMappingURL=emitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitter.js","sourceRoot":"","sources":["../src/emitter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAgD,MAAM,EAAE,MAAM,SAAS,CAAC;AAGlG,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAqG3C;;;;;;;;GAQG;AACH,MAAM,OAAO,OAAQ,SAAQ,iBAAiB;IA2D1C,YAAY,OAAuB,EAAE,eAAgC;QACjE,KAAK,CAAC;YACF,KAAK,EAAE,SAAS;YAChB,GAAG,OAAO,CAAC,gBAAgB;YAE3B;;;;eAIG;YACH,iBAAiB,EAAE,OAAO,CAAC,wBAAwB,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE,EAAE,eAAe,CAAC;SACjG,CAAC,CAAC;QAjCP,uCAAuC;QACtB,SAAI,GAAiB,EAAE,CAAC;QAEzC,yDAAyD;QACxC,WAAM,GAAiB,EAAE,CAAC;QAE3C;;;WAGG;QACK,eAAU,GAAsB,EAAE,CAAC;QACnC,uBAAkB,GAAG,CAAC,CAAC;QAE/B,6DAA6D;QACrD,YAAO,GAAG,CAAC,CAAC;QAEpB,iEAAiE;QACzD,YAAO,GAAG,CAAC,CAAC;QAGZ,mBAAc,GAAG,KAAK,CAAC;QAe3B,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;QACzC,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QACzB,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;QAC3C,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;QACzC,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC;QACjD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe,IAAI,GAAG,CAAC;QACtD,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QAEnC,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QAEvC,2DAA2D;QAC3D,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YACrB,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;gBACzC,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;YACjC,CAAC;QACL,CAAC;QAED;;;;;;;WAOG;QACH,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,CAAC;YACzC,MAAM,CAAC,GAAG,IAAI,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,eAAe,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;YAC7E,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACtB,CAAC;QAED;;;;;;WAMG;QACH,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC;QAC9C,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACzB,IAAI,CAAC,YAAY,EAAE,CAAC;QACxB,CAAC;IACL,CAAC;IAED;;;OAGG;IACK,YAAY,CAAC,CAAY;QAC7B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YACjB,CAAC;YACD,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE;YAChC,IAAI,EAAE,CAAC,CAAC,QAAQ,IAAI,CAAC;SACxB,CAAC,CAAC;QAEH,8BAA8B;QAC9B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QAErE,6EAA6E;QAC7E,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC;QACf,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;;;;OAKG;IACI,aAAa,CAAC,MAAc;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC;QACnC,IAAI,EAAE,GAAG,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;QAE/B,mDAAmD;QACnD,IAAI,EAAE,GAAG,KAAK;YAAE,EAAE,GAAG,KAAK,CAAC;QAC3B,IAAI,EAAE,IAAI,CAAC;YAAE,OAAO;QAEpB,kCAAkC;QAClC,IAAI,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;QAE1C;;;WAGG;QACH,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/C,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAEzB,0DAA0D;YAC1D,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAErC,wDAAwD;YACxD,gEAAgE;YAChE,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;YACjB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;YACjB,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC;YAE5B,oCAAoC;YACpC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,UAAU;gBAAE,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;YAEnE,6CAA6C;YAC7C,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;gBAClB,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;YACxB,CAAC;QACL,CAAC;IACL,CAAC;IAED;;;OAGG;IACK,aAAa,CAAC,EAAU;QAC5B,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,IAAI,CAAC,CAAC;YACrC,IAAI,IAAI,IAAI,CAAC;gBAAE,OAAO;YAEtB;;;eAGG;YACH,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;YACnB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;YAC7C,IAAI,IAAI,IAAI,CAAC;gBAAE,OAAO;YAEtB,2DAA2D;YAC3D,IAAI,CAAC,OAAO,IAAI,IAAI,GAAG,IAAI,CAAC;YAE5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE;gBAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC/C,OAAO;QACX,CAAC;QAED,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC;YAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,gBAAgB,IAAI,CAAC,CAAC;YAC3C,IAAI,QAAQ,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC;gBAAE,OAAO;YAE1C,kEAAkE;YAClE,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;YACnB,OAAO,IAAI,CAAC,OAAO,IAAI,QAAQ,EAAE,CAAC;gBAC9B,IAAI,CAAC,OAAO,IAAI,QAAQ,CAAC;gBACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE;oBAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACtD,CAAC;YACD,OAAO;QACX,CAAC;IACL,CAAC;IAED;;;;;;;OAOG;IACK,QAAQ;QACZ,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QAC1B,IAAI,CAAC,CAAC;YAAE,OAAO;QAEf,IAAI,IAAI,CAAC,eAAe,CAAC,eAAe;YAAE,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAE9F,CAAC,CAAC,OAAO,EAAE,CAAC;QAEZ,8DAA8D;QAC9D,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,UAAU;YAAE,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAExE,iDAAiD;QACjD,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACjB,IAAI,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7B,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QACxB,CAAC;QAED,oCAAoC;QACpC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;QACnC,CAAC,CAAC,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,GAAG,CAAC,CAAC;QAEtD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC;IAED;;;;;;;;;OASG;IACK,WAAW,CAAC,WAAmB;QACnC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAEnC,yFAAyF;QACzF,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YACnD,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAC3C,CAAC;QAED,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;QAEjC,CAAC,CAAC,MAAM,EAAE,CAAC;QAEX,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;QAEvB;;;;WAIG;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;QACpC,IAAI,WAAW,KAAK,IAAI;YAAE,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACvE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;QAElB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IAED;;;OAGG;IACI,cAAc;QACjB,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC;QAEjB,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC7C,CAAC;IACL,CAAC;IAED;;;OAGG;IACI,SAAS,CAAC,KAAa;QAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE;YAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;IACpD,CAAC;IAEM,QAAQ;QACX,MAAM,CAAC,GAAG,IAAI,CAAC,gBAAgB,IAAI,CAAC,CAAC;QACrC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;YAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;IAChD,CAAC;IAED;;;;;;;OAOG;IACK,MAAM,CAAC,wBAAwB,CAAC,UAAuB,EAAE,QAAyB;QACtF,MAAM,KAAK,GAAiB;YACxB,QAAQ,EAAE,KAAK;YACf,QAAQ,EAAE,KAAK;YACf,MAAM,EAAE,KAAK;YACb,GAAG,EAAE,KAAK;YACV,KAAK,EAAE,KAAK;SACf,CAAC;QAEF,+EAA+E;QAC/E,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACpB,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;gBAClC,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;YACtB,CAAC;QACL,CAAC;QAED,wEAAwE;QACxE,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;YACzB,IAAI,CAAC,CAAC,CAAC,QAAQ;gBAAE,SAAS;YAE1B,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;gBAC3B,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;YACtB,CAAC;QACL,CAAC;QAED,OAAO,KAAK,CAAC;IACjB,CAAC;IAED;;OAEG;IACK,YAAY;QAChB,IAAI,IAAI,CAAC,cAAc;YAAE,OAAO;QAChC,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC3B,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAChC,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QACzC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;IAC/B,CAAC;IAED;;OAEG;IACK,YAAY;QAChB,IAAI,CAAC,IAAI,CAAC,cAAc;YAAE,OAAO;QACjC,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,kBAAkB;YAAE,OAAO;QACrD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC5C,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;IAChC,CAAC;IAED;;;;;;OAMG;IACI,OAAO,CAAC,IAAkB;QAC7B,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI;YAAE,OAAO;QAE/B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC;QACzC,MAAM,YAAY,GAAG,IAAI,KAAK,QAAQ,CAAC;QAEvC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QAEjB,IAAI,CAAC,SAAS,IAAI,YAAY,EAAE,CAAC;YAC7B,IAAI,CAAC,YAAY,EAAE,CAAC;QACxB,CAAC;aAAM,IAAI,SAAS,IAAI,CAAC,YAAY,EAAE,CAAC;YACpC,IAAI,CAAC,YAAY,EAAE,CAAC;QACxB,CAAC;IACL,CAAC;IAEe,OAAO,CAAC,OAAa;QACjC,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;CACJ","sourcesContent":["import { ParticleContainer, ParticleContainerOptions, ParticleProperties, Ticker } from \"pixi.js\";\r\nimport { Behaviour } from \"./behaviour\";\r\nimport { TextureProvider } from \"./texture-provider\";\r\nimport { PxParticle } from \"./px-particle\";\r\n\r\n/**\r\n * How this emitter produces particles over time:\r\n * - \"rate\": continuously emits at a fixed particles-per-second\r\n * - \"wave\": emits discrete bursts (\"waves\") every interval\r\n * - \"manual\": user code calls emitBurst/emitWave; no ticker hookup\r\n */\r\nexport type EmissionMode = \"rate\" | \"wave\" | \"manual\";\r\n\r\n/**\r\n * Configuration passed to {@link Emitter}.\r\n *\r\n * Notes:\r\n * - In \"rate\" mode, you must provide `ratePerSecond`\r\n * - In \"wave\" mode, you should provide `waveInterval` and `particlesPerWave`\r\n * - In \"manual\" mode, the emitter will NOT auto-update; you trigger emission yourself\r\n */\r\nexport interface EmitterOptions {\r\n /**\r\n * Options forwarded to PixiJS ParticleContainer.\r\n * You can set blendMode, position, etc.\r\n *\r\n * NOTE: dynamicProperties are computed automatically based on behaviours + texture provider.\r\n */\r\n containerOptions?: ParticleContainerOptions;\r\n\r\n /** Maximum number of particles alive at the same time. Also defines the pool size. */\r\n maxParticles: number;\r\n\r\n /** Emission strategy (rate / wave / manual). */\r\n mode: EmissionMode;\r\n\r\n /**\r\n * Particles per second when mode === \"rate\".\r\n */\r\n ratePerSecond?: number;\r\n\r\n /**\r\n * Seconds between waves when mode === \"wave\".\r\n * If omitted, a default is used.\r\n */\r\n waveInterval?: number;\r\n\r\n /**\r\n * How many particles are spawned each wave when mode === \"wave\".\r\n * If omitted, defaults to 1.\r\n */\r\n particlesPerWave?: number;\r\n\r\n /** Particle lifetime in seconds. Each particle chooses a random value in [min, max]. */\r\n lifetime: { min: number; max: number };\r\n\r\n /**\r\n * If true, the emitter automatically emits according to its mode.\r\n * If false, particles can still be spawned using manual methods.\r\n */\r\n emitting?: boolean;\r\n\r\n /**\r\n * Clamp delta-time to avoid huge simulation jumps (tab pause, breakpoint, slow frame, etc).\r\n * This prevents spawning a massive burst and \"teleporting\" particles.\r\n */\r\n maxDeltaSeconds?: number;\r\n\r\n /**\r\n * Behaviours are modular systems applied to particles.\r\n * Typical responsibilities:\r\n * - initialize properties at spawn (velocity, scale, alpha, etc)\r\n * - update properties each frame (curves, gravity, drag, etc)\r\n *\r\n * Behaviours can also declare \"requires\" to enable ParticleContainer dynamicProperties.\r\n */\r\n behaviours?: Behaviour[];\r\n\r\n /**\r\n * If true, particles are added at index 0 (behind existing children).\r\n * Useful for layering (e.g. smoke behind sparks).\r\n */\r\n addAtBack?: boolean;\r\n\r\n /**\r\n * Optional custom ticker (e.g. your app ticker).\r\n * If omitted, uses Ticker.shared.\r\n */\r\n ticker?: Ticker;\r\n}\r\n\r\n/**\r\n * Internally we keep behaviours in a stable sorted array:\r\n * - prio: behaviour priority (lower runs earlier)\r\n * - order: insertion order to break ties (stable ordering)\r\n */\r\ntype SortedBehaviour = { b: Behaviour; order: number; prio: number };\r\n\r\n/**\r\n * ParticleContainer dynamicProperties config.\r\n * Pixi uses this to know which particle attributes must be re-uploaded to GPU each frame.\r\n */\r\ntype DynamicProps = ParticleProperties & Record<string, boolean>;\r\n\r\n/**\r\n * Emitter is a ParticleContainer that owns a pool of {@link PxParticle} instances.\r\n *\r\n * Core responsibilities:\r\n * - Keep a pool of particles to avoid allocations during gameplay.\r\n * - Emit particles according to the selected EmissionMode.\r\n * - Update active particles each tick (movement, behaviours, textures).\r\n * - Recycle dead particles back into the pool.\r\n */\r\nexport class Emitter extends ParticleContainer {\r\n /** Pool capacity / maximum concurrent particles. */\r\n private maxParticles: number;\r\n\r\n /** Current emission mode. */\r\n private mode: EmissionMode;\r\n\r\n /** Particles per second for mode=\"rate\". */\r\n public ratePerSecond?: number;\r\n\r\n /** Wave interval (seconds) for mode=\"wave\". */\r\n public waveInterval?: number;\r\n\r\n /** Particles per wave for mode=\"wave\". */\r\n public particlesPerWave?: number;\r\n\r\n /** Lifetime range (seconds) used for each spawned particle. */\r\n private lifetime: { min: number; max: number };\r\n\r\n /**\r\n * If true, emission is automatic (rate/wave).\r\n * If false, update still runs but no new particles are spawned.\r\n */\r\n public emitting?: boolean;\r\n\r\n /** Delta clamp to keep simulation stable on long frames. */\r\n private maxDeltaSeconds: number;\r\n\r\n /** Whether new particles should be added behind existing particles. */\r\n public addAtBack?: boolean;\r\n\r\n /** Ticker driving updates when not in manual mode. */\r\n private ticker: Ticker;\r\n\r\n /** Responsible for choosing textures and (optionally) updating animated textures. */\r\n private textureProvider: TextureProvider;\r\n\r\n /** Inactive particle pool (reused). */\r\n private readonly pool: PxParticle[] = [];\r\n\r\n /** Active particles currently simulated and rendered. */\r\n private readonly active: PxParticle[] = [];\r\n\r\n /**\r\n * Sorted behaviours (priority + stable insertion order).\r\n * Behaviours are applied in order every frame.\r\n */\r\n private behaviours: SortedBehaviour[] = [];\r\n private nextBehaviourOrder = 0;\r\n\r\n /** Accumulator used to compute spawn counts in rate mode. */\r\n private emitAcc = 0;\r\n\r\n /** Accumulator used to track time between waves in wave mode. */\r\n private waveAcc = 0;\r\n\r\n private updateEmitterBound?: (ticker: Ticker) => void;\r\n private tickerAttached = false;\r\n\r\n constructor(options: EmitterOptions, textureProvider: TextureProvider) {\r\n super({\r\n label: \"Emitter\",\r\n ...options.containerOptions,\r\n\r\n /**\r\n * Important optimization:\r\n * We enable only the dynamic properties that are actually needed\r\n * (based on behaviours + texture provider requirements).\r\n */\r\n dynamicProperties: Emitter.computeDynamicProperties(options.behaviours ?? [], textureProvider),\r\n });\r\n\r\n this.maxParticles = options.maxParticles;\r\n this.mode = options.mode;\r\n this.ratePerSecond = options.ratePerSecond;\r\n this.waveInterval = options.waveInterval;\r\n this.particlesPerWave = options.particlesPerWave;\r\n this.lifetime = options.lifetime;\r\n this.emitting = options.emitting;\r\n this.maxDeltaSeconds = options.maxDeltaSeconds ?? 0.1;\r\n this.addAtBack = options.addAtBack;\r\n\r\n this.textureProvider = textureProvider;\r\n\r\n // Register behaviours (sorted by priority + stable order).\r\n if (options.behaviours) {\r\n for (const behaviour of options.behaviours) {\r\n this.addBehaviour(behaviour);\r\n }\r\n }\r\n\r\n /**\r\n * Pre-allocate particles upfront:\r\n * - avoids runtime allocations / GC spikes\r\n * - allows \"maxParticles\" to be a hard cap\r\n *\r\n * Each pooled particle gets an initial texture (required by Pixi Particle).\r\n * The actual texture may be replaced on spawn by the provider.\r\n */\r\n for (let i = 0; i < this.maxParticles; i++) {\r\n const p = new PxParticle({ texture: this.textureProvider.initialTexture() });\r\n this.pool.push(p);\r\n }\r\n\r\n /**\r\n * In manual mode we do NOT attach to a ticker.\r\n * User code calls emitBurst/emitWave and also needs to call updateEmitter manually\r\n * (or you can provide a separate public update method if you prefer).\r\n *\r\n * NOTE: currently updateEmitter is still public and can be called manually.\r\n */\r\n this.ticker = options.ticker ?? Ticker.shared;\r\n if (this.mode !== \"manual\") {\r\n this.attachTicker();\r\n }\r\n }\r\n\r\n /**\r\n * Adds a behaviour and inserts it into the sorted execution order.\r\n * Priority controls order; ties are resolved by insertion order.\r\n */\r\n private addBehaviour(b: Behaviour): this {\r\n this.behaviours.push({\r\n b,\r\n order: this.nextBehaviourOrder++,\r\n prio: b.priority ?? 0,\r\n });\r\n\r\n // lower priority runs earlier\r\n this.behaviours.sort((a, c) => a.prio - c.prio || a.order - c.order);\r\n\r\n // Optional init hook for behaviour to cache references or precompute curves.\r\n b.init?.(this);\r\n return this;\r\n }\r\n\r\n /**\r\n * Ticker callback: advances simulation and handles emission.\r\n *\r\n * The emitter uses ticker.deltaMS (milliseconds between frames) converted to seconds.\r\n * We clamp dt to maxDeltaSeconds to avoid large jumps and excessive spawning.\r\n */\r\n public updateEmitter(ticker: Ticker): void {\r\n const maxDt = this.maxDeltaSeconds;\r\n let dt = ticker.deltaMS / 1000;\r\n\r\n // Clamp dt for stability (e.g. tab in background).\r\n if (dt > maxDt) dt = maxDt;\r\n if (dt <= 0) return;\r\n\r\n // Spawn new particles if enabled.\r\n if (this.emitting) this.emitParticles(dt);\r\n\r\n /**\r\n * Update particles & kill dead ones.\r\n * Iterate backwards so we can remove by index safely.\r\n */\r\n for (let i = this.active.length - 1; i >= 0; i--) {\r\n const p = this.active[i];\r\n\r\n // Texture provider may animate / swap textures per frame.\r\n this.textureProvider.update?.(p, dt);\r\n\r\n // Base integrator: apply velocity and angular velocity.\r\n // Behaviours can also modify velocity, position, rotation, etc.\r\n p.age += dt;\r\n p.x += p.vx * dt;\r\n p.y += p.vy * dt;\r\n p.rotation += p.angleV * dt;\r\n\r\n // Apply behaviours in sorted order.\r\n for (const entry of this.behaviours) entry.b.update?.(p, dt, this);\r\n\r\n // Kill particle if it exceeded its lifetime.\r\n if (p.age >= p.life) {\r\n this.killAtIndex(i);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Emits particles according to the emitter's mode.\r\n * This method is called automatically each tick when emitting=true.\r\n */\r\n private emitParticles(dt: number): void {\r\n if (this.mode === \"rate\") {\r\n const rate = this.ratePerSecond ?? 0;\r\n if (rate <= 0) return;\r\n\r\n /**\r\n * Accumulate fractional time and convert into \"how many particles should we emit\".\r\n * This produces stable emission even with variable frame rates.\r\n */\r\n this.emitAcc += dt;\r\n const want = Math.floor(this.emitAcc * rate);\r\n if (want <= 0) return;\r\n\r\n // Keep the remainder time after spawning `want` particles.\r\n this.emitAcc -= want / rate;\r\n\r\n for (let i = 0; i < want; i++) this.spawnOne();\r\n return;\r\n }\r\n\r\n if (this.mode === \"wave\") {\r\n const interval = this.waveInterval ?? 0.25;\r\n const perWave = this.particlesPerWave ?? 1;\r\n if (interval <= 0 || perWave <= 0) return;\r\n\r\n // Emit full waves when the accumulated time crosses the interval.\r\n this.waveAcc += dt;\r\n while (this.waveAcc >= interval) {\r\n this.waveAcc -= interval;\r\n for (let i = 0; i < perWave; i++) this.spawnOne();\r\n }\r\n return;\r\n }\r\n }\r\n\r\n /**\r\n * Spawns a single particle from the pool.\r\n *\r\n * IMPORTANT:\r\n * - If the pool is empty, spawning is skipped (hard cap).\r\n * - Texture is selected by TextureProvider at spawn time.\r\n * - Behaviours' onSpawn hooks initialize per-particle state.\r\n */\r\n private spawnOne(): void {\r\n const p = this.pool.pop();\r\n if (!p) return;\r\n\r\n if (this.textureProvider.textureForSpawn) p.texture = this.textureProvider.textureForSpawn(p);\r\n\r\n p.onSpawn();\r\n\r\n // Allow behaviours to initialize the particle for this spawn.\r\n for (const behaviour of this.behaviours) behaviour.b.onSpawn?.(p, this);\r\n\r\n // Add particle to the container (front or back).\r\n if (this.addAtBack) {\r\n this.addParticleAt(p, 0);\r\n } else {\r\n this.addParticle(p);\r\n }\r\n\r\n // Randomize lifetime in [min, max].\r\n const { min, max } = this.lifetime;\r\n p.life = min + Math.random() * Math.max(0, max - min);\r\n\r\n this.active.push(p);\r\n }\r\n\r\n /**\r\n * Kills and recycles a particle at a given index in the active list.\r\n *\r\n * Order of operations:\r\n * 1) behaviour kill hooks (reverse order, mirroring teardown)\r\n * 2) provider kill hook\r\n * 3) reset particle instance\r\n * 4) remove from container + active list\r\n * 5) return to pool\r\n */\r\n private killAtIndex(activeIndex: number): void {\r\n const p = this.active[activeIndex];\r\n\r\n // Call behaviours' teardown in reverse to match common init/apply ordering expectations.\r\n for (let i = this.behaviours.length - 1; i >= 0; i--) {\r\n this.behaviours[i].b.onKill?.(p, this);\r\n }\r\n\r\n this.textureProvider.onKill?.(p);\r\n\r\n p.onKill();\r\n\r\n this.removeParticle(p);\r\n\r\n /**\r\n * Remove from active list using \"swap remove\":\r\n * - O(1)\r\n * - does not preserve ordering (fine for particle sims)\r\n */\r\n const last = this.active.length - 1;\r\n if (activeIndex !== last) this.active[activeIndex] = this.active[last];\r\n this.active.pop();\r\n\r\n this.pool.push(p);\r\n }\r\n\r\n /**\r\n * Immediately kills all active particles and resets emission accumulators.\r\n * Useful when restarting an effect or changing scenes.\r\n */\r\n public clearParticles(): void {\r\n this.emitAcc = 0;\r\n this.waveAcc = 0;\r\n\r\n while (this.active.length > 0) {\r\n this.killAtIndex(this.active.length - 1);\r\n }\r\n }\r\n\r\n /**\r\n * Manual emission helpers.\r\n * Only relevant if mode === \"manual\" or emitting === false.\r\n */\r\n public emitBurst(count: number): void {\r\n for (let i = 0; i < count; i++) this.spawnOne();\r\n }\r\n\r\n public emitWave(): void {\r\n const n = this.particlesPerWave ?? 0;\r\n for (let i = 0; i < n; i++) this.spawnOne();\r\n }\r\n\r\n /**\r\n * Computes ParticleContainer dynamicProperties from:\r\n * - TextureProvider.requires\r\n * - each Behaviour.requires\r\n *\r\n * Keeping these minimal is important for performance:\r\n * enabling extra dynamic properties can increase per-frame GPU uploads.\r\n */\r\n private static computeDynamicProperties(behaviours: Behaviour[], provider: TextureProvider): DynamicProps {\r\n const props: DynamicProps = {\r\n position: false,\r\n rotation: false,\r\n vertex: false,\r\n uvs: false,\r\n color: false,\r\n };\r\n\r\n // Texture provider may require updates like uvs (animated textures) or vertex.\r\n if (provider.requires) {\r\n for (const key in provider.requires) {\r\n props[key] = true;\r\n }\r\n }\r\n\r\n // Behaviours declare which GPU-updated properties they touch over time.\r\n for (const b of behaviours) {\r\n if (!b.requires) continue;\r\n\r\n for (const key in b.requires) {\r\n props[key] = true;\r\n }\r\n }\r\n\r\n return props;\r\n }\r\n\r\n /**\r\n * Attaches the emitter update loop to the configured ticker.\r\n */\r\n private attachTicker(): void {\r\n if (this.tickerAttached) return;\r\n if (!this.updateEmitterBound) {\r\n this.updateEmitterBound = this.updateEmitter.bind(this);\r\n }\r\n\r\n if (!this.ticker) {\r\n this.ticker = Ticker.shared;\r\n }\r\n\r\n this.ticker.add(this.updateEmitterBound);\r\n this.tickerAttached = true;\r\n }\r\n\r\n /**\r\n * Detaches the emitter update loop from the ticker.\r\n */\r\n private detachTicker(): void {\r\n if (!this.tickerAttached) return;\r\n if (!this.ticker || !this.updateEmitterBound) return;\r\n this.ticker.remove(this.updateEmitterBound);\r\n this.tickerAttached = false;\r\n }\r\n\r\n /**\r\n * Changes the emission mode at runtime.\r\n *\r\n * - Switching to \"manual\" detaches the ticker.\r\n * - Switching to \"rate\" or \"wave\" attaches the ticker.\r\n *\r\n */\r\n public setMode(mode: EmissionMode): void {\r\n if (this.mode === mode) return;\r\n\r\n const wasManual = this.mode === \"manual\";\r\n const willBeManual = mode === \"manual\";\r\n\r\n this.mode = mode;\r\n\r\n if (!wasManual && willBeManual) {\r\n this.detachTicker();\r\n } else if (wasManual && !willBeManual) {\r\n this.attachTicker();\r\n }\r\n }\r\n\r\n public override destroy(options?: any): void {\r\n this.detachTicker();\r\n super.destroy(options);\r\n }\r\n}\r\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export * from "./behaviour";
|
|
2
|
+
export * from "./emitter";
|
|
3
|
+
export * from "./texture-provider";
|
|
4
|
+
export * from "./behaviours/alpha-behaviour";
|
|
5
|
+
export * from "./behaviours/alpha-curve-behaviour";
|
|
6
|
+
export * from "./behaviours/scale-curve-behaviour";
|
|
7
|
+
export * from "./behaviours/curved-behaviour/curve-key-frame";
|
|
8
|
+
export * from "./behaviours/curved-behaviour/curve-sampler";
|
|
9
|
+
export * from "./behaviours/movement-behaviours/gravity-behaviour";
|
|
10
|
+
export * from "./behaviours/movement-behaviours/movement-curve-behaviour";
|
|
11
|
+
export * from "./behaviours/movement-behaviours/radial-burst-behaviour";
|
|
12
|
+
export * from "./behaviours/spawn-behaviours/circle-spawn-behaviour";
|
|
13
|
+
export * from "./behaviours/spawn-behaviours/rectangle-spawn-behaviour";
|
|
14
|
+
export * from "./behaviours/static-behaviours/static-rotation-behaviour";
|
|
15
|
+
export * from "./behaviours/static-behaviours/static-scale-behaviour";
|
|
16
|
+
export * from "./texture-providers/animated-texture-provider";
|
|
17
|
+
export * from "./texture-providers/single-texture-provider";
|
|
18
|
+
export * from "./texture-providers/weighted-texture-provider";
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,oBAAoB,CAAC;AACnC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,oCAAoC,CAAC;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,oDAAoD,CAAC;AACnE,cAAc,2DAA2D,CAAC;AAC1E,cAAc,yDAAyD,CAAC;AACxE,cAAc,sDAAsD,CAAC;AACrE,cAAc,yDAAyD,CAAC;AACxE,cAAc,0DAA0D,CAAC;AACzE,cAAc,uDAAuD,CAAC;AACtE,cAAc,+CAA+C,CAAC;AAC9D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,+CAA+C,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export * from "./behaviour";
|
|
2
|
+
export * from "./emitter";
|
|
3
|
+
export * from "./texture-provider";
|
|
4
|
+
export * from "./behaviours/alpha-behaviour";
|
|
5
|
+
export * from "./behaviours/alpha-curve-behaviour";
|
|
6
|
+
export * from "./behaviours/scale-curve-behaviour";
|
|
7
|
+
export * from "./behaviours/curved-behaviour/curve-key-frame";
|
|
8
|
+
export * from "./behaviours/curved-behaviour/curve-sampler";
|
|
9
|
+
export * from "./behaviours/movement-behaviours/gravity-behaviour";
|
|
10
|
+
export * from "./behaviours/movement-behaviours/movement-curve-behaviour";
|
|
11
|
+
export * from "./behaviours/movement-behaviours/radial-burst-behaviour";
|
|
12
|
+
export * from "./behaviours/spawn-behaviours/circle-spawn-behaviour";
|
|
13
|
+
export * from "./behaviours/spawn-behaviours/rectangle-spawn-behaviour";
|
|
14
|
+
export * from "./behaviours/static-behaviours/static-rotation-behaviour";
|
|
15
|
+
export * from "./behaviours/static-behaviours/static-scale-behaviour";
|
|
16
|
+
export * from "./texture-providers/animated-texture-provider";
|
|
17
|
+
export * from "./texture-providers/single-texture-provider";
|
|
18
|
+
export * from "./texture-providers/weighted-texture-provider";
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,oBAAoB,CAAC;AACnC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,oCAAoC,CAAC;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,oDAAoD,CAAC;AACnE,cAAc,2DAA2D,CAAC;AAC1E,cAAc,yDAAyD,CAAC;AACxE,cAAc,sDAAsD,CAAC;AACrE,cAAc,yDAAyD,CAAC;AACxE,cAAc,0DAA0D,CAAC;AACzE,cAAc,uDAAuD,CAAC;AACtE,cAAc,+CAA+C,CAAC;AAC9D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,+CAA+C,CAAC","sourcesContent":["export * from \"./behaviour\";\r\nexport * from \"./emitter\";\r\nexport * from \"./texture-provider\";\r\nexport * from \"./behaviours/alpha-behaviour\";\r\nexport * from \"./behaviours/alpha-curve-behaviour\";\r\nexport * from \"./behaviours/scale-curve-behaviour\";\r\nexport * from \"./behaviours/curved-behaviour/curve-key-frame\";\r\nexport * from \"./behaviours/curved-behaviour/curve-sampler\";\r\nexport * from \"./behaviours/movement-behaviours/gravity-behaviour\";\r\nexport * from \"./behaviours/movement-behaviours/movement-curve-behaviour\";\r\nexport * from \"./behaviours/movement-behaviours/radial-burst-behaviour\";\r\nexport * from \"./behaviours/spawn-behaviours/circle-spawn-behaviour\";\r\nexport * from \"./behaviours/spawn-behaviours/rectangle-spawn-behaviour\";\r\nexport * from \"./behaviours/static-behaviours/static-rotation-behaviour\";\r\nexport * from \"./behaviours/static-behaviours/static-scale-behaviour\";\r\nexport * from \"./texture-providers/animated-texture-provider\";\r\nexport * from \"./texture-providers/single-texture-provider\";\r\nexport * from \"./texture-providers/weighted-texture-provider\";\r\n"]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Particle, ParticleOptions } from "pixi.js";
|
|
2
|
+
/**
|
|
3
|
+
* Internal state used by AnimatedTextureProvider.
|
|
4
|
+
*
|
|
5
|
+
* `t` tracks elapsed time (seconds) inside the animation.
|
|
6
|
+
* This allows providers to compute frame index based on lifetime progression.
|
|
7
|
+
*/
|
|
8
|
+
export type AnimatedParticleState = {
|
|
9
|
+
t: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* PxParticle extends PixiJS {@link Particle} with simulation state.
|
|
13
|
+
*
|
|
14
|
+
* Why this exists:
|
|
15
|
+
* - Pixi's Particle handles rendering efficiently.
|
|
16
|
+
* - We extend it to attach simulation data (velocity, lifetime, etc).
|
|
17
|
+
* - Instances are pooled and reused by the Emitter.
|
|
18
|
+
*
|
|
19
|
+
* IMPORTANT:
|
|
20
|
+
* Particles are never destroyed during runtime.
|
|
21
|
+
* They are recycled via onKill() and reused via onSpawn().
|
|
22
|
+
*/
|
|
23
|
+
export declare class PxParticle extends Particle {
|
|
24
|
+
/** Seconds since spawn. */
|
|
25
|
+
age: number;
|
|
26
|
+
/** Total lifetime in seconds (randomized per spawn by the emitter). */
|
|
27
|
+
life: number;
|
|
28
|
+
/** Velocity in pixels per second (X axis). */
|
|
29
|
+
vx: number;
|
|
30
|
+
/** Velocity in pixels per second (Y axis). */
|
|
31
|
+
vy: number;
|
|
32
|
+
/** Angular velocity in radians per second. */
|
|
33
|
+
angleV: number;
|
|
34
|
+
/**
|
|
35
|
+
* Optional provider-specific animation state.
|
|
36
|
+
* Used by AnimatedTextureProvider (if present).
|
|
37
|
+
*/
|
|
38
|
+
animatedParticleState?: AnimatedParticleState;
|
|
39
|
+
constructor(options: ParticleOptions);
|
|
40
|
+
/**
|
|
41
|
+
* Resets the particle into pooled (inactive) state.
|
|
42
|
+
*
|
|
43
|
+
* Called when:
|
|
44
|
+
* - The particle exceeds its lifetime
|
|
45
|
+
* - The emitter clears particles
|
|
46
|
+
*
|
|
47
|
+
* This must leave the particle in a clean state so it can safely be reused.
|
|
48
|
+
*/
|
|
49
|
+
onKill(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Prepares the particle for active simulation.
|
|
52
|
+
*
|
|
53
|
+
* Called when:
|
|
54
|
+
* - The emitter spawns this particle from the pool
|
|
55
|
+
*
|
|
56
|
+
* Resets all visual and simulation state to defaults.
|
|
57
|
+
* Behaviours will then modify properties as needed.
|
|
58
|
+
*/
|
|
59
|
+
onSpawn(): void;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=px-particle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"px-particle.d.ts","sourceRoot":"","sources":["../src/px-particle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAChC,CAAC,EAAE,MAAM,CAAC;CACb,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,qBAAa,UAAW,SAAQ,QAAQ;IACpC,2BAA2B;IACpB,GAAG,SAAK;IAEf,uEAAuE;IAChE,IAAI,SAAK;IAEhB,8CAA8C;IACvC,EAAE,SAAK;IAEd,8CAA8C;IACvC,EAAE,SAAK;IAEd,8CAA8C;IACvC,MAAM,SAAK;IAElB;;;OAGG;IACI,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;gBAEzC,OAAO,EAAE,eAAe;IAWpC;;;;;;;;OAQG;IACI,MAAM,IAAI,IAAI;IAWrB;;;;;;;;OAQG;IACI,OAAO,IAAI,IAAI;CAqBzB"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Particle } from "pixi.js";
|
|
2
|
+
/**
|
|
3
|
+
* PxParticle extends PixiJS {@link Particle} with simulation state.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists:
|
|
6
|
+
* - Pixi's Particle handles rendering efficiently.
|
|
7
|
+
* - We extend it to attach simulation data (velocity, lifetime, etc).
|
|
8
|
+
* - Instances are pooled and reused by the Emitter.
|
|
9
|
+
*
|
|
10
|
+
* IMPORTANT:
|
|
11
|
+
* Particles are never destroyed during runtime.
|
|
12
|
+
* They are recycled via onKill() and reused via onSpawn().
|
|
13
|
+
*/
|
|
14
|
+
export class PxParticle extends Particle {
|
|
15
|
+
constructor(options) {
|
|
16
|
+
// Center anchor by default
|
|
17
|
+
options.anchorX = 0.5;
|
|
18
|
+
options.anchorY = 0.5;
|
|
19
|
+
super(options);
|
|
20
|
+
/** Seconds since spawn. */
|
|
21
|
+
this.age = 0;
|
|
22
|
+
/** Total lifetime in seconds (randomized per spawn by the emitter). */
|
|
23
|
+
this.life = 1;
|
|
24
|
+
/** Velocity in pixels per second (X axis). */
|
|
25
|
+
this.vx = 0;
|
|
26
|
+
/** Velocity in pixels per second (Y axis). */
|
|
27
|
+
this.vy = 0;
|
|
28
|
+
/** Angular velocity in radians per second. */
|
|
29
|
+
this.angleV = 0;
|
|
30
|
+
// Start in pooled/inactive state.
|
|
31
|
+
this.onKill();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resets the particle into pooled (inactive) state.
|
|
35
|
+
*
|
|
36
|
+
* Called when:
|
|
37
|
+
* - The particle exceeds its lifetime
|
|
38
|
+
* - The emitter clears particles
|
|
39
|
+
*
|
|
40
|
+
* This must leave the particle in a clean state so it can safely be reused.
|
|
41
|
+
*/
|
|
42
|
+
onKill() {
|
|
43
|
+
this.age = 0;
|
|
44
|
+
this.life = 1;
|
|
45
|
+
this.vx = 0;
|
|
46
|
+
this.vy = 0;
|
|
47
|
+
this.angleV = 0;
|
|
48
|
+
this.animatedParticleState = undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Prepares the particle for active simulation.
|
|
52
|
+
*
|
|
53
|
+
* Called when:
|
|
54
|
+
* - The emitter spawns this particle from the pool
|
|
55
|
+
*
|
|
56
|
+
* Resets all visual and simulation state to defaults.
|
|
57
|
+
* Behaviours will then modify properties as needed.
|
|
58
|
+
*/
|
|
59
|
+
onSpawn() {
|
|
60
|
+
this.age = 0;
|
|
61
|
+
this.life = 1;
|
|
62
|
+
this.vx = 0;
|
|
63
|
+
this.vy = 0;
|
|
64
|
+
this.angleV = 0;
|
|
65
|
+
this.animatedParticleState = undefined;
|
|
66
|
+
// Reset visual state
|
|
67
|
+
this.alpha = 1;
|
|
68
|
+
this.tint = 0xffffff;
|
|
69
|
+
this.rotation = 0;
|
|
70
|
+
this.scaleX = 1;
|
|
71
|
+
this.scaleY = 1;
|
|
72
|
+
this.x = 0;
|
|
73
|
+
this.y = 0;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=px-particle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"px-particle.js","sourceRoot":"","sources":["../src/px-particle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAmB,MAAM,SAAS,CAAC;AAYpD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,UAAW,SAAQ,QAAQ;IAsBpC,YAAY,OAAwB;QAChC,2BAA2B;QAC3B,OAAO,CAAC,OAAO,GAAG,GAAG,CAAC;QACtB,OAAO,CAAC,OAAO,GAAG,GAAG,CAAC;QAEtB,KAAK,CAAC,OAAO,CAAC,CAAC;QA1BnB,2BAA2B;QACpB,QAAG,GAAG,CAAC,CAAC;QAEf,uEAAuE;QAChE,SAAI,GAAG,CAAC,CAAC;QAEhB,8CAA8C;QACvC,OAAE,GAAG,CAAC,CAAC;QAEd,8CAA8C;QACvC,OAAE,GAAG,CAAC,CAAC;QAEd,8CAA8C;QACvC,WAAM,GAAG,CAAC,CAAC;QAed,kCAAkC;QAClC,IAAI,CAAC,MAAM,EAAE,CAAC;IAClB,CAAC;IAED;;;;;;;;OAQG;IACI,MAAM;QACT,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC;QACb,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QAEd,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;QACZ,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;QACZ,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QAEhB,IAAI,CAAC,qBAAqB,GAAG,SAAS,CAAC;IAC3C,CAAC;IAED;;;;;;;;OAQG;IACI,OAAO;QACV,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC;QACb,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QAEd,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;QACZ,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;QACZ,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QAEhB,IAAI,CAAC,qBAAqB,GAAG,SAAS,CAAC;QAEvC,qBAAqB;QACrB,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC;QAErB,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;QAClB,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QAChB,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QAEhB,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACX,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IACf,CAAC;CACJ","sourcesContent":["import { Particle, ParticleOptions } from \"pixi.js\";\r\n\r\n/**\r\n * Internal state used by AnimatedTextureProvider.\r\n *\r\n * `t` tracks elapsed time (seconds) inside the animation.\r\n * This allows providers to compute frame index based on lifetime progression.\r\n */\r\nexport type AnimatedParticleState = {\r\n t: number; // seconds progressed in animated texture (sprite sheet)\r\n};\r\n\r\n/**\r\n * PxParticle extends PixiJS {@link Particle} with simulation state.\r\n *\r\n * Why this exists:\r\n * - Pixi's Particle handles rendering efficiently.\r\n * - We extend it to attach simulation data (velocity, lifetime, etc).\r\n * - Instances are pooled and reused by the Emitter.\r\n *\r\n * IMPORTANT:\r\n * Particles are never destroyed during runtime.\r\n * They are recycled via onKill() and reused via onSpawn().\r\n */\r\nexport class PxParticle extends Particle {\r\n /** Seconds since spawn. */\r\n public age = 0;\r\n\r\n /** Total lifetime in seconds (randomized per spawn by the emitter). */\r\n public life = 1;\r\n\r\n /** Velocity in pixels per second (X axis). */\r\n public vx = 0;\r\n\r\n /** Velocity in pixels per second (Y axis). */\r\n public vy = 0;\r\n\r\n /** Angular velocity in radians per second. */\r\n public angleV = 0;\r\n\r\n /**\r\n * Optional provider-specific animation state.\r\n * Used by AnimatedTextureProvider (if present).\r\n */\r\n public animatedParticleState?: AnimatedParticleState;\r\n\r\n constructor(options: ParticleOptions) {\r\n // Center anchor by default\r\n options.anchorX = 0.5;\r\n options.anchorY = 0.5;\r\n\r\n super(options);\r\n\r\n // Start in pooled/inactive state.\r\n this.onKill();\r\n }\r\n\r\n /**\r\n * Resets the particle into pooled (inactive) state.\r\n *\r\n * Called when:\r\n * - The particle exceeds its lifetime\r\n * - The emitter clears particles\r\n *\r\n * This must leave the particle in a clean state so it can safely be reused.\r\n */\r\n public onKill(): void {\r\n this.age = 0;\r\n this.life = 1;\r\n\r\n this.vx = 0;\r\n this.vy = 0;\r\n this.angleV = 0;\r\n\r\n this.animatedParticleState = undefined;\r\n }\r\n\r\n /**\r\n * Prepares the particle for active simulation.\r\n *\r\n * Called when:\r\n * - The emitter spawns this particle from the pool\r\n *\r\n * Resets all visual and simulation state to defaults.\r\n * Behaviours will then modify properties as needed.\r\n */\r\n public onSpawn(): void {\r\n this.age = 0;\r\n this.life = 1;\r\n\r\n this.vx = 0;\r\n this.vy = 0;\r\n this.angleV = 0;\r\n\r\n this.animatedParticleState = undefined;\r\n\r\n // Reset visual state\r\n this.alpha = 1;\r\n this.tint = 0xffffff;\r\n\r\n this.rotation = 0;\r\n this.scaleX = 1;\r\n this.scaleY = 1;\r\n\r\n this.x = 0;\r\n this.y = 0;\r\n }\r\n}\r\n"]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ParticleProperties, Texture } from "pixi.js";
|
|
2
|
+
import { PxParticle } from "./px-particle";
|
|
3
|
+
/**
|
|
4
|
+
* TextureProvider decides which {@link Texture} a particle uses.
|
|
5
|
+
*
|
|
6
|
+
* It is called in two different phases:
|
|
7
|
+
* 1) Pool creation: `initialTexture()` (required by Pixi to construct the particle)
|
|
8
|
+
* 2) Spawn / simulation: `textureForSpawn()` and optional `update()`
|
|
9
|
+
*
|
|
10
|
+
* Providers may be stateless (single texture) or stateful (animated textures, weighted random, etc).
|
|
11
|
+
* Keep provider methods lightweight: they can be called very frequently.
|
|
12
|
+
*/
|
|
13
|
+
export interface TextureProvider {
|
|
14
|
+
/**
|
|
15
|
+
* Dynamic property requirements for this provider.
|
|
16
|
+
*
|
|
17
|
+
* PixiJS ParticleContainer can skip updating GPU buffers unless you mark certain fields as dynamic.
|
|
18
|
+
* If your provider changes something over time that affects rendering (e.g. UVs for animation),
|
|
19
|
+
* declare it here so the emitter enables the correct `dynamicProperties`.
|
|
20
|
+
*
|
|
21
|
+
* Example:
|
|
22
|
+
* requires: { uvs: true }
|
|
23
|
+
*/
|
|
24
|
+
readonly requires?: ParticleProperties;
|
|
25
|
+
/**
|
|
26
|
+
* Called once per pooled particle, at pool initialization time.
|
|
27
|
+
*
|
|
28
|
+
* Pixi requires a texture to construct each Particle, so this must always return a valid Texture.
|
|
29
|
+
* The texture returned here is just an initial placeholder; it can be replaced on spawn.
|
|
30
|
+
*/
|
|
31
|
+
initialTexture(): Texture;
|
|
32
|
+
/**
|
|
33
|
+
* Called every time a particle is spawned.
|
|
34
|
+
*
|
|
35
|
+
* Must return a valid texture. May depend on:
|
|
36
|
+
* - random selection (weighted sets)
|
|
37
|
+
* - emitter state
|
|
38
|
+
* - particle state (reused pooled particle)
|
|
39
|
+
*
|
|
40
|
+
*/
|
|
41
|
+
textureForSpawn?(p: PxParticle): Texture;
|
|
42
|
+
/**
|
|
43
|
+
* Optional per-frame hook.
|
|
44
|
+
*
|
|
45
|
+
* Use this when the texture can change during the particle's life:
|
|
46
|
+
* - animated textures (flipbook)
|
|
47
|
+
* - texture swapping
|
|
48
|
+
*
|
|
49
|
+
* If you mutate anything that affects rendering (like UVs), ensure `requires`
|
|
50
|
+
* contains the correct dynamicProperties.
|
|
51
|
+
*/
|
|
52
|
+
update?(p: PxParticle, dt: number): void;
|
|
53
|
+
/**
|
|
54
|
+
* Optional recycle hook.
|
|
55
|
+
*
|
|
56
|
+
* Called when a particle is killed and returned to the pool.
|
|
57
|
+
* Use this to clear any provider-specific state stored on the particle
|
|
58
|
+
* (e.g. animation frame index, timers, cached references).
|
|
59
|
+
*/
|
|
60
|
+
onKill?(p: PxParticle): void;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=texture-provider.d.ts.map
|