- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
import pool from "./../system/pooling.js";
import ParticleEmitterSettings from "./settings.js";
import { randomFloat } from "./../math/math.js";
import Container from "./../renderable/container.js";
/**
* @ignore
*/
function createDefaultParticleTexture(w = 8, h = 8) {
let defaultParticleTexture = pool.pull("CanvasTexture", w, h, { offscreenCanvas: true });
defaultParticleTexture.context.fillStyle = "#fff";
defaultParticleTexture.context.fillRect(0, 0, w, h);
return defaultParticleTexture;
}
/**
* @classdesc
* Particle Emitter Object.
* @augments Container
*/
export default class ParticleEmitter extends Container {
/**
* @param {number} x - x position of the particle emitter
* @param {number} y - y position of the particle emitter
* @param {ParticleEmitterSettings} [settings=ParticleEmitterSettings] - the settings for the particle emitter.
* @example
* // Create a particle emitter at position 100, 100
* let emitter = new ParticleEmitter(100, 100, {
* width: 16,
* height : 16,
* tint: "#f00",
* totalParticles: 32,
* angle: 0,
* angleVariation: 6.283185307179586,
* maxLife: 5,
* speed: 3
* });
*
* // Add the emitter to the game world
* me.game.world.addChild(emitter);
*
* // Launch all particles one time and stop, like a explosion
* emitter.burstParticles();
*
* // Launch constantly the particles, like a fountain
* emitter.streamParticles();
*
* // At the end, remove emitter from the game world
* // call this in onDestroyEvent function
* me.game.world.removeChild(emitter);
*/
constructor(x, y, settings = {}) {
// call the super constructor
super(
x, y,
settings.width | 1,
settings.height | 1
);
/**
* the current (active) emitter settings
* @public
* @type {ParticleEmitterSettings}
* @name settings
* @memberof ParticleEmitter
*/
this.settings = {};
// center the emitter around the given coordinates
this.centerOn(x, y);
// Emitter is Stream, launch particles constantly
/** @ignore */
this._stream = false;
// Frequency timer (in ms) for emitter launch new particles
// used only in stream emitter
/** @ignore */
this._frequencyTimer = 0;
// Time of live (in ms) for emitter launch new particles
// used only in stream emitter
/** @ignore */
this._durationTimer = 0;
// Emitter is emitting particles
/** @ignore */
this._enabled = false;
// Emitter will always update
this.alwaysUpdate = true;
// don't sort the particles by z-index
this.autoSort = false;
// count the updates
this._updateCount = 0;
// internally store how much time was skipped when frames are skipped
this._dt = 0;
//this.anchorPoint.set(0, 0);
// Reset the emitter to defaults
this.reset(settings);
}
/**
* Reset the emitter with particle emitter settings.
* @param {ParticleEmitterSettings} settings - [optional] object with emitter settings. See {@link ParticleEmitterSettings}
*/
reset(settings = {}) {
Object.assign(this.settings, ParticleEmitterSettings, settings);
if (typeof this.settings.image === "undefined") {
this._defaultParticle = createDefaultParticleTexture(settings.textureSize, settings.textureSize);
this.settings.image = this._defaultParticle.canvas;
}
this.floating = this.settings.floating;
this.isDirty = true;
}
/**
* returns a random point on the x axis within the bounds of this emitter
* @returns {number}
*/
getRandomPointX() {
return randomFloat(0, this.getBounds().width);
}
/**
* returns a random point on the y axis within the bounds this emitter
* @returns {number}
*/
getRandomPointY() {
return randomFloat(0, this.getBounds().height);
}
// Add count particles in the game world
/** @ignore */
addParticles(count) {
for (let i = 0; i < count; i++) {
// Add particle to the container
this.addChild(pool.pull("Particle", this), this.pos.z);
}
this.isDirty = true;
}
/**
* Emitter is of type stream and is launching particles
* @returns {boolean} Emitter is Stream and is launching particles
*/
isRunning() {
return this._enabled && this._stream;
}
/**
* Launch particles from emitter constantly (e.g. for stream)
* @param {number} [duration] - time that the emitter releases particles in ms
*/
streamParticles(duration) {
this._enabled = true;
this._stream = true;
this.settings.frequency = Math.max(1, this.settings.frequency);
this._durationTimer = (typeof duration === "number") ? duration : this.settings.duration;
}
/**
* Stop the emitter from generating new particles (used only if emitter is Stream)
*/
stopStream() {
this._enabled = false;
}
/**
* Launch all particles from emitter and stop (e.g. for explosion)
* @param {number} [total] - number of particles to launch
*/
burstParticles(total) {
this._enabled = true;
this._stream = false;
this.addParticles((typeof total === "number") ? total : this.settings.totalParticles);
this._enabled = false;
}
/**
* @ignore
*/
update(dt) {
// skip frames if necessary
if (++this._updateCount > this.settings.framesToSkip) {
this._updateCount = 0;
}
if (this._updateCount > 0) {
this._dt += dt;
return this.isDirty;
}
// apply skipped delta time
dt += this._dt;
this._dt = 0;
// Update particles
this.isDirty |= super.update(dt);
// Launch new particles, if emitter is Stream
if ((this._enabled) && (this._stream)) {
// Check if the emitter has duration set
if (this._durationTimer !== Infinity) {
this._durationTimer -= dt;
if (this._durationTimer <= 0) {
this.stopStream();
return this.isDirty;
}
}
// Increase the emitter launcher timer
this._frequencyTimer += dt;
// Check for new particles launch
const particlesCount = this.children.length;
if ((particlesCount < this.settings.totalParticles) && (this._frequencyTimer >= this.settings.frequency)) {
if ((particlesCount + this.settings.maxParticles) <= this.settings.totalParticles) {
this.addParticles(this.settings.maxParticles);
}
else {
this.addParticles(this.settings.totalParticles - particlesCount);
}
this._frequencyTimer = 0;
this.isDirty = true;
}
}
return this.isDirty;
}
/**
* Destroy function
* @ignore
*/
destroy() {
// call the parent destroy method
super.destroy(arguments);
// clean emitter specific Properties
if (typeof this._defaultParticle !== "undefined") {
pool.push(this._defaultParticle);
this._defaultParticle = undefined;
}
this.settings.image = undefined;
this.settings = undefined;
}
}