• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

excaliburjs / Excalibur / 14804036802

02 May 2025 09:58PM UTC coverage: 5.927% (-83.4%) from 89.28%
14804036802

Pull #3404

github

web-flow
Merge 5c103d7f8 into 0f2ccaeb2
Pull Request #3404: feat: added Graph module to Math

234 of 8383 branches covered (2.79%)

229 of 246 new or added lines in 1 file covered. (93.09%)

13145 existing lines in 208 files now uncovered.

934 of 15759 relevant lines covered (5.93%)

4.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/engine/Particles/ParticleEmitter.ts
1
import { Engine } from '../Engine';
2
import { Actor } from '../Actor';
3
import { vec } from '../Math/vector';
4
import { Random } from '../Math/Random';
5
import { CollisionType } from '../Collision/CollisionType';
6
import { randomInRange } from '../Math/util';
7
import { EmitterType } from './EmitterType';
8
import { Particle, ParticleTransform, ParticleEmitterArgs, ParticleConfig } from './Particles';
9
import { RentalPool } from '../Util/RentalPool';
10

11
/**
12
 * Using a particle emitter is a great way to create interesting effects
13
 * in your game, like smoke, fire, water, explosions, etc. `ParticleEmitter`
14
 * extend {@apilink Actor} allowing you to use all of the features that come with.
15
 *
16
 * These particles are simulated on the CPU in JavaScript
17
 */
18
export class ParticleEmitter extends Actor {
UNCOV
19
  private _particlesToEmit: number = 0;
×
20

UNCOV
21
  private _particlePool = new RentalPool(
×
UNCOV
22
    () => new Particle({}),
×
23
    (p) => p,
×
24
    500
25
  );
26

UNCOV
27
  public numParticles: number = 0;
×
28

29
  /**
30
   * Random number generator
31
   */
32
  public random: Random;
33

34
  /**
35
   * Gets or sets the isEmitting flag
36
   */
UNCOV
37
  public isEmitting: boolean = true;
×
38

39
  /**
40
   * Gets or sets the backing deadParticle collection
41
   */
UNCOV
42
  public deadParticles: Particle[] = [];
×
43

44
  /**
45
   * Gets or sets the emission rate for particles (particles/sec)
46
   */
UNCOV
47
  public emitRate: number = 1; //particles/sec
×
48

49
  /**
50
   * Gets or sets the emitter type for the particle emitter
51
   */
UNCOV
52
  public emitterType: EmitterType = EmitterType.Rectangle;
×
53

54
  /**
55
   * Gets or sets the emitter radius, only takes effect when the {@apilink emitterType} is {@apilink EmitterType.Circle}
56
   */
UNCOV
57
  public radius: number = 0;
×
58

UNCOV
59
  public particle: ParticleConfig = {
×
60
    /**
61
     * Gets or sets the life of each particle in milliseconds
62
     */
63
    life: 2000,
64
    transform: ParticleTransform.Global,
65
    graphic: undefined,
66
    opacity: 1,
67
    angularVelocity: 0,
68
    focus: undefined,
69
    focusAccel: undefined,
70
    randomRotation: false
71
  };
72

73
  /**
74
   * @param config particle emitter options bag
75
   */
76
  constructor(config: ParticleEmitterArgs) {
UNCOV
77
    super({ width: config.width ?? 0, height: config.height ?? 0 });
×
78

UNCOV
79
    const { particle, x, y, z, pos, isEmitting, emitRate, emitterType, radius, random } = { ...config };
×
80

UNCOV
81
    this.particle = { ...this.particle, ...particle };
×
82

UNCOV
83
    this.pos = pos ?? vec(x ?? 0, y ?? 0);
×
UNCOV
84
    this.z = z ?? 0;
×
UNCOV
85
    this.isEmitting = isEmitting ?? this.isEmitting;
×
UNCOV
86
    this.emitRate = emitRate ?? this.emitRate;
×
UNCOV
87
    this.emitterType = emitterType ?? this.emitterType;
×
UNCOV
88
    this.radius = radius ?? this.radius;
×
89

UNCOV
90
    this.body.collisionType = CollisionType.PreventCollision;
×
91

UNCOV
92
    this.random = random ?? new Random();
×
93
  }
94

95
  public removeParticle(particle: Particle) {
UNCOV
96
    this.deadParticles.push(particle);
×
97
  }
98

UNCOV
99
  private _activeParticles: Particle[] = [];
×
100

101
  /**
102
   * Causes the emitter to emit particles
103
   * @param particleCount  Number of particles to emit right now
104
   */
105
  public emitParticles(particleCount: number) {
UNCOV
106
    if (particleCount <= 0) {
×
107
      return;
×
108
    }
UNCOV
109
    particleCount = particleCount | 0; // coerce to int
×
UNCOV
110
    for (let i = 0; i < particleCount; i++) {
×
UNCOV
111
      const p = this._createParticle();
×
UNCOV
112
      if (this?.scene?.world) {
×
UNCOV
113
        if (this.particle.transform === ParticleTransform.Global) {
×
UNCOV
114
          this.scene.world.add(p);
×
115
        } else {
UNCOV
116
          this.addChild(p);
×
117
        }
118
      }
UNCOV
119
      this._activeParticles.push(p);
×
120
    }
121
  }
122

123
  public clearParticles() {
UNCOV
124
    for (let i = 0; i < this._activeParticles.length; i++) {
×
UNCOV
125
      this.removeParticle(this._activeParticles[i]);
×
126
    }
127
  }
128

129
  // Creates a new particle given the constraints of the emitter
130
  private _createParticle(): Particle {
UNCOV
131
    let ranX = 0;
×
UNCOV
132
    let ranY = 0;
×
133

UNCOV
134
    const angle = randomInRange(this.particle.minAngle || 0, this.particle.maxAngle || Math.PI * 2, this.random);
×
UNCOV
135
    const vel = randomInRange(this.particle.minSpeed || 0, this.particle.maxSpeed || 0, this.random);
×
UNCOV
136
    const size = this.particle.startSize || randomInRange(this.particle.minSize || 5, this.particle.maxSize || 5, this.random);
×
UNCOV
137
    const dx = vel * Math.cos(angle);
×
UNCOV
138
    const dy = vel * Math.sin(angle);
×
139

UNCOV
140
    if (this.emitterType === EmitterType.Rectangle) {
×
141
      ranX = randomInRange(0, this.width, this.random);
×
142
      ranY = randomInRange(0, this.height, this.random);
×
UNCOV
143
    } else if (this.emitterType === EmitterType.Circle) {
×
UNCOV
144
      const radius = randomInRange(0, this.radius, this.random);
×
UNCOV
145
      ranX = radius * Math.cos(angle);
×
UNCOV
146
      ranY = radius * Math.sin(angle);
×
147
    }
148

UNCOV
149
    const p = this._particlePool.rent();
×
UNCOV
150
    p.configure({
×
151
      life: this.particle.life,
152
      opacity: this.particle.opacity,
153
      beginColor: this.particle.beginColor,
154
      endColor: this.particle.endColor,
155
      pos: vec(ranX, ranY),
156
      vel: vec(dx, dy),
157
      acc: this.particle.acc,
158
      angularVelocity: this.particle.angularVelocity,
159
      startSize: this.particle.startSize,
160
      endSize: this.particle.endSize,
161
      size: size,
162
      graphic: this.particle.graphic,
163
      fade: this.particle.fade
164
    });
UNCOV
165
    p.registerEmitter(this);
×
UNCOV
166
    if (this.particle.randomRotation) {
×
167
      p.transform.rotation = randomInRange(0, Math.PI * 2, this.random);
×
168
    }
UNCOV
169
    if (this.particle.focus) {
×
170
      p.focus = this.particle.focus.add(vec(this.pos.x, this.pos.y));
×
171
      p.focusAccel = this.particle.focusAccel;
×
172
    }
UNCOV
173
    return p;
×
174
  }
175

176
  public update(engine: Engine, elapsed: number) {
UNCOV
177
    super.update(engine, elapsed);
×
178

UNCOV
179
    if (this.isEmitting) {
×
UNCOV
180
      this._particlesToEmit += this.emitRate * (elapsed / 1000);
×
UNCOV
181
      if (this._particlesToEmit > 1.0) {
×
UNCOV
182
        this.emitParticles(Math.floor(this._particlesToEmit));
×
UNCOV
183
        this._particlesToEmit = this._particlesToEmit - Math.floor(this._particlesToEmit);
×
184
      }
185
    }
186

187
    // deferred removal
UNCOV
188
    for (let i = 0; i < this.deadParticles.length; i++) {
×
UNCOV
189
      if (this?.scene?.world) {
×
UNCOV
190
        this.scene.world.remove(this.deadParticles[i], false);
×
UNCOV
191
        this._particlePool.return(this.deadParticles[i]);
×
192
      }
UNCOV
193
      const index = this._activeParticles.indexOf(this.deadParticles[i]);
×
UNCOV
194
      if (index > -1) {
×
UNCOV
195
        this._activeParticles.splice(index, 1);
×
196
      }
197
    }
UNCOV
198
    this.deadParticles.length = 0;
×
199
  }
200
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc