[READ-ONLY] Mirror of https://github.com/flo-bit/pixi-particles. particle system for pixi.js flo-bit.github.io/pixi-particles/
javascript particle-system particles pixijs
0

Configure Feed

Select the types of activity you want to include in your feed.

pixi-particles / particles.js
8.5 kB 343 lines
1import * as PIXI from "https://cdn.jsdelivr.net/npm/pixi.js@7.x/dist/pixi.min.mjs"; 2 3import Noise from "../every-noise/noise.js"; 4import Utils from "../js-utils/utils.js"; 5import Vector from "../js-utils/vector.js"; 6 7/** 8 * A particle emitter 9 * 10 * @class ParticleEmitter 11 * 12 * @param {object} opts 13 * @param {string} opts.type - "point", "circle", "box", "line" 14 * 15 * @param {number} opts.size - size of circle or box 16 * 17 * @param {number} opts.width - width of box 18 * @param {number} opts.height - height of box 19 * 20 * @param {Vector} opts.center - center of emitter 21 * 22 * @param {Vector} opts.a - start of line 23 * @param {Vector} opts.b - end of line 24 * 25 * @param {number} opts.particlesPerSecond - number of particles to spawn per second 26 * 27 * @param {object} opts.particleSettings - settings to pass to Particle constructor 28 * 29 */ 30class ParticleEmitter { 31 constructor(opts) { 32 opts = opts || {}; 33 this.type = opts.type ?? "point"; 34 35 this.size = new Vector( 36 opts.width ?? opts.w ?? opts.size ?? 10, 37 opts.height ?? opts.h ?? opts.size ?? 10 38 ); 39 40 this.center = opts.center ?? new Vector(0, 0); 41 this.a = opts.a; 42 this.b = opts.b; 43 if (this.a && this.b) { 44 this.vec = this.b.clone().sub(this.a); 45 } 46 47 this.particlesPerSecond = opts.particlesPerSecond ?? 100; 48 49 this.particleSettings = opts.particleSettings ?? opts.settings ?? {}; 50 51 this.dt = 0; 52 } 53 54 getPos() { 55 if (this.type == "point") { 56 return this.center; 57 } else if (this.type == "circle") { 58 let angle = Math.random() * Math.PI * 2; 59 let r = (Math.random() * this.size.x) / 2; 60 return this.center.clone().add(Math.cos(angle) * r, Math.sin(angle) * r); 61 } else if ( 62 this.type == "box" || 63 this.type == "square" || 64 this.type == "rect" 65 ) { 66 let x = (Math.random() - 0.5) * this.size.x; 67 let y = (Math.random() - 0.5) * this.size.y; 68 return this.center.clone().add(x, y); 69 } else if (this.type == "line") { 70 let t = Math.random(); 71 return this.b.clone().sub(this.a).mult(t, t).add(this.a); 72 } 73 } 74 update(dt) { 75 // spawn new particles 76 this.dt += dt; 77 78 let count = Math.floor(this.particlesPerSecond * this.dt); 79 if (count == 0) return; 80 81 this.dt -= count / this.particlesPerSecond; 82 83 for (let i = 0; i < count; i++) { 84 let pos = this.getPos(); 85 this.particleSettings.x = pos.x; 86 this.particleSettings.y = pos.y; 87 this.spawn(this.particleSettings); 88 } 89 } 90} 91 92/** 93 * A particle 94 * 95 * @class Particle 96 * 97 * @param {object} opts 98 * 99 * @param {number|function} opts.x - x position 100 * @param {number|function} opts.y - y position 101 * 102 * @param {number|function} opts.vx - x velocity 103 * @param {number|function} opts.vy - y velocity 104 * 105 * @param {number|function} opts.life - lifetime in seconds 106 * 107 * @param {number|function} opts.size - size of particle relative to max size (1.0 = max size) 108 * 109 * @param {number} opts.color - color of particle 110 * @param {number} opts.alpha - alpha value of particle 111 * 112 * @param {boolean} opts.shouldShrink - should particle shrink over time 113 * @param {boolean} opts.shouldDisappear - should particle disappear over time 114 * 115 * @param {boolean} opts.applyForces - should particle be affected by forces 116 * @param {number} opts.drag - drag coefficient 117 * 118 * @param {function} opts.check - function to check if particle should be removed (return true to remove) 119 */ 120class Particle extends PIXI.Sprite { 121 static makeTexture(renderer, size, shape) { 122 size = size ?? 1; 123 const graphic = new PIXI.Graphics(); 124 graphic.beginFill(0xffffff); 125 if (shape == "square" || shape == "rect") { 126 graphic.drawRect(-size, -size, size * 2, size * 2); 127 } else { 128 graphic.drawCircle(0, 0, size); 129 } 130 graphic.endFill(); 131 let texture = renderer.generateTexture(graphic); 132 return texture; 133 } 134 135 constructor(opts) { 136 opts = opts || {}; 137 super(opts.texture); 138 139 this.opts = opts; 140 this._alive = true; 141 this.life = 0; 142 this.shouldShrink = opts.shouldShrink ?? false; 143 this.shouldDisappear = opts.shouldDisappear ?? false; 144 145 this.size = Utils.getNumber(opts.size, this, 1); 146 this.scale.x = this.scale.y = this.size; 147 148 this.lifetimeSeconds = Utils.getNumber(opts.life, this, 0); 149 150 this.x = Utils.getNumber(opts.x, this, 0); 151 this.y = Utils.getNumber(opts.y, this, 0); 152 153 this.vx = Utils.getNumber(opts.vx, this, 0); 154 this.vy = Utils.getNumber(opts.vy, this, 0); 155 this.v = new Vector(this.vx, this.vy); 156 157 this.tint = Utils.getNumber(opts.color ?? opts.tint, this, 0xffffff); 158 159 this.applyForces = opts.applyForces ?? false; 160 161 this.alpha = Utils.getNumber(opts.alpha, this, 1); 162 163 this.check = opts.check; 164 165 this.drag = opts.drag; 166 } 167 168 set color(c) { 169 this.tint = c; 170 } 171 get color() { 172 return this.tint; 173 } 174 175 get alive() { 176 return this._alive; 177 } 178 set alive(value) { 179 this._alive = value; 180 this.alpha = value ? 1 : 0; 181 } 182 183 reset(settings) { 184 for (let k of Object.keys(settings)) { 185 particle[k] = settings[k]; 186 } 187 } 188 189 applyForce(force) { 190 if (!this.applyForces) return; 191 192 let value = force(this); 193 if (value == undefined || value == null) return; 194 this.v.add(value); 195 } 196 197 update(dt) { 198 if (!this.alive) return; 199 200 this.life += dt; 201 if (this.lifetimeSeconds > 0 && this.life > this.lifetimeSeconds) { 202 this.alive = false; 203 return; 204 } 205 206 if (this.shouldShrink) { 207 this.scale.x = this.scale.y = 208 this.size * (1 - this.life / this.lifetimeSeconds); 209 } 210 if (this.shouldDisappear) { 211 this.alpha = 1 - this.life / this.lifetimeSeconds; 212 } 213 if (this.drag != undefined) { 214 this.v.x *= this.drag; 215 this.v.y *= this.drag; 216 } 217 218 this.x += this.v.x * dt; 219 this.y += this.v.y * dt; 220 221 if (this.check && this.check(this)) { 222 this.alive = false; 223 } 224 } 225} 226 227/** 228 * A particle container 229 * 230 * @class ParticleContainer 231 * 232 * @param {object} opts 233 * @param {number} opts.maxCount - maximum number of particles 234 * @param {number} opts.maxSize - maximum size of particles 235 * @param {string} opts.shape - shape of particles (circle or square) 236 * 237 * @param {PIXI.Renderer} opts.renderer - renderer to use for generating textures 238 * 239 */ 240class ParticleContainer extends PIXI.ParticleContainer { 241 constructor(opts) { 242 opts = opts || {}; 243 let maxParticleCount = opts.maxCount ?? 10000; 244 super( 245 maxParticleCount, 246 { 247 vertices: true, 248 position: true, 249 rotation: true, 250 tint: true, 251 }, 252 undefined, 253 true 254 ); 255 256 this.particleTexture = 257 opts.texture ?? 258 Particle.makeTexture( 259 opts.renderer, 260 opts.maxSize ?? opts.size, 261 opts.shape 262 ); 263 264 this.emitters = []; 265 if (opts.emitters) { 266 for (let e of opts.emitters) { 267 this.createEmitter(e); 268 } 269 } 270 271 this.forces = []; 272 if (opts.forces) { 273 for (let f of opts.forces) { 274 this.forces.push(f); 275 } 276 } 277 } 278 createEmitter(options) { 279 let emitter = new ParticleEmitter(options); 280 emitter.spawn = this.spawn.bind(this); 281 this.emitters.push(emitter); 282 return emitter; 283 } 284 spawn(opts) { 285 opts = opts || {}; 286 let count = Utils.getNumber(opts.count, this); 287 if (count == undefined) count = 1; 288 for (let i = 0; i < count; i++) { 289 this.createParticle(opts); 290 } 291 } 292 createParticle(settings) { 293 settings.texture = this.particleTexture; 294 let particle = new Particle(settings); 295 this.addChild(particle); 296 return particle; 297 } 298 299 /** 300 * function does the following: 301 * 302 * - updates all emitters 303 * - updates all particles 304 * - applies forces to all particles 305 * - removes dead particles 306 * 307 * @param {*} dt 308 */ 309 update(dt) { 310 // go through all emitters and add new particles to the queue 311 for (let emitter of this.emitters) { 312 emitter.update(dt); 313 } 314 315 // update all living particles 316 for (let i = this.children.length - 1; i >= 0; i--) { 317 let particle = this.children[i]; 318 319 // if particle is dead, remove it 320 if (!particle.alive) { 321 this.children.splice(i, 1); 322 continue; 323 } 324 particle.update(dt); 325 326 // apply forces 327 for (let force of this.forces) { 328 particle.applyForce(force); 329 } 330 } 331 } 332} 333 334export { 335 ParticleContainer as Particles, 336 Particle, 337 ParticleEmitter, 338 Utils, 339 Noise, 340 Vector, 341 PIXI, 342}; 343export default ParticleContainer;