littlejsengine 1.17.1 → 1.17.10

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.
Files changed (49) hide show
  1. package/LICENSE +10 -10
  2. package/dist/littlejs.d.ts +254 -171
  3. package/dist/littlejs.esm.js +892 -628
  4. package/dist/littlejs.esm.min.js +1 -1
  5. package/dist/littlejs.js +889 -626
  6. package/dist/littlejs.min.js +1 -1
  7. package/dist/littlejs.release.js +885 -622
  8. package/examples/box2d/gameObjects.js +3 -3
  9. package/examples/electron/index.html +2 -2
  10. package/examples/index.html +10 -9
  11. package/examples/logo.png +0 -0
  12. package/examples/logo2.png +0 -0
  13. package/examples/module/build.bat +7 -0
  14. package/examples/module/build.js +121 -0
  15. package/examples/particles/index.html +1 -1
  16. package/examples/platformer/game.js +1 -1
  17. package/examples/platformer/gameEffects.js +5 -10
  18. package/examples/platformer/gameLevel.js +3 -3
  19. package/examples/puzzle/game.js +2 -2
  20. package/examples/shorts/base.html +4 -1
  21. package/examples/shorts/box2dPool.js +2 -2
  22. package/examples/shorts/box2dTileLayer.js +48 -0
  23. package/examples/shorts/colors.js +1 -1
  24. package/examples/shorts/fps.js +1 -1
  25. package/examples/shorts/music.js +5 -4
  26. package/examples/shorts/musicPlayer.js +4 -3
  27. package/examples/shorts/parallax.js +2 -2
  28. package/examples/shorts/tiltedView.js +1 -1
  29. package/examples/starter/index.html +2 -2
  30. package/package.json +5 -5
  31. package/plugins/box2d.js +166 -63
  32. package/plugins/pluginExport.js +1 -1
  33. package/plugins/postProcess.js +1 -1
  34. package/plugins/uiSystem.js +33 -20
  35. package/plugins/zzfxm.js +2 -2
  36. package/reference.md +9 -8
  37. package/src/engine.js +111 -134
  38. package/src/engineAudio.js +44 -58
  39. package/src/engineDebug.js +4 -4
  40. package/src/engineDraw.js +7 -4
  41. package/src/engineExport.js +2 -1
  42. package/src/engineInput.js +5 -5
  43. package/src/engineMath.js +9 -9
  44. package/src/engineObject.js +25 -25
  45. package/src/engineParticles.js +241 -118
  46. package/src/engineTileLayer.js +117 -88
  47. package/src/engineUtilities.js +105 -78
  48. package/src/engineWebGL.js +2 -2
  49. package/plugins/Box2D_License.txt +0 -17
@@ -1,19 +1,31 @@
1
1
  /**
2
2
  * LittleJS Particle System
3
+ * - A simple but fast and flexible particle system
4
+ * - Lightweight Particles are created and managed by ParticleEmitters
5
+ * - The particle design tool can be used to help create emitters
6
+ * @namespace Particles
3
7
  */
4
8
 
5
9
  'use strict';
6
10
 
7
11
  /**
8
- * @callback ParticleCallbackFunction - Function that processes a particle
12
+ * @callback ParticleCallback - Function that processes a particle
9
13
  * @param {Particle} particle
10
- * @memberof Engine
14
+ * @memberof Particles
15
+ */
16
+
17
+ /**
18
+ * @callback ParticleCollideCallback - Collide callback for particles
19
+ * @param {Particle} particle
20
+ * @param {number} tileData
21
+ * @param {Vector2} pos
22
+ * @memberof Particles
11
23
  */
12
24
 
13
25
  /**
14
26
  * Particle Emitter - Spawns particles with the given settings
15
27
  * @extends EngineObject
16
- * @memberof Engine
28
+ * @memberof Particles
17
29
  * @example
18
30
  * // create a particle emitter
19
31
  * let pos = vec2(2,3);
@@ -31,7 +43,7 @@
31
43
  class ParticleEmitter extends EngineObject
32
44
  {
33
45
  /** Create a particle system with the given settings
34
- * @param {Vector2} position - World space position of the emitter
46
+ * @param {Vector2} pos - World space position of the emitter
35
47
  * @param {number} [angle] - Angle to emit the particles
36
48
  * @param {number|Vector2} [emitSize] - World space size of the emitter (float for circle diameter, vec2 for rect)
37
49
  * @param {number} [emitTime] - How long to stay alive (0 is forever)
@@ -61,7 +73,7 @@ class ParticleEmitter extends EngineObject
61
73
  */
62
74
  constructor
63
75
  (
64
- position,
76
+ pos,
65
77
  angle,
66
78
  emitSize = 0,
67
79
  emitTime = 0,
@@ -90,12 +102,13 @@ class ParticleEmitter extends EngineObject
90
102
  localSpace = false
91
103
  )
92
104
  {
93
- super(position, vec2(), tileInfo, angle, undefined, renderOrder);
105
+ super(pos, vec2(), tileInfo, angle, undefined, renderOrder);
94
106
 
95
107
  // emitter settings
108
+ /** @property {boolean} - Should particles be emitted in a circle */
109
+ this.emitCircle = typeof emitSize === 'number';
96
110
  /** @property {number|Vector2} - World space size of the emitter (float for circle diameter, vec2 for rect) */
97
- this.emitSize = emitSize instanceof Vector2 ?
98
- emitSize.copy() : emitSize;
111
+ this.emitSize = typeof emitSize === 'number' ? vec2(emitSize) : emitSize.copy();
99
112
  /** @property {number} - How long to stay alive (0 is forever) */
100
113
  this.emitTime = emitTime;
101
114
  /** @property {number} - How many particles per second to spawn, does not emit if 0 */
@@ -146,26 +159,31 @@ class ParticleEmitter extends EngineObject
146
159
  this.localSpace = localSpace;
147
160
  /** @property {number} - If non zero the particle is drawn as a trail, stretched in the direction of velocity */
148
161
  this.trailScale = 0;
149
- /** @property {ParticleCallbackFunction} - Callback when particle is destroyed */
150
- this.particleDestroyCallback = undefined;
151
- /** @property {ParticleCallbackFunction} - Callback when particle is created */
162
+ /** @property {ParticleCallback} - Callback when particle is created */
152
163
  this.particleCreateCallback = undefined;
153
- /** @property {number} - Track particle emit time */
154
- this.emitTimeBuffer = 0;
164
+ /** @property {ParticleCallback} - Callback when particle is destroyed */
165
+ this.particleDestroyCallback = undefined;
166
+ /** @property {ParticleCollideCallback} - Callback when particle collides */
167
+ this.particleCollideCallback = undefined;
155
168
  /** @property {number} - Percentage of velocity to pass to particles (0-1) */
156
169
  this.velocityInheritance = 0;
170
+ /** @property {number} - Track particle emit time */
171
+ this.emitTimeBuffer = 0;
172
+ /** @property {Array<Particle>} - Array of particles for this emitter */
173
+ this.particles = [];
157
174
 
158
175
  // track previous position and angle
159
176
  this.previousAngle = this.angle;
160
177
  this.previousPos = this.pos.copy();
161
178
  }
162
179
 
163
- /** Emitters do not have physics */
164
- updatePhysics() {}
165
-
166
180
  /** Update the emitter to spawn particles, called automatically by engine once each frame */
167
181
  update()
168
182
  {
183
+ // physics sanity checks
184
+ ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
185
+ ASSERT(this.damping >= 0 && this.damping <= 1);
186
+
169
187
  if (this.velocityInheritance)
170
188
  {
171
189
  // pass emitter velocity to particles
@@ -179,7 +197,7 @@ class ParticleEmitter extends EngineObject
179
197
  }
180
198
 
181
199
  // update emitter
182
- if (!this.emitTime || this.getAliveTime() <= this.emitTime)
200
+ if (this.isActive())
183
201
  {
184
202
  // emit particles
185
203
  if (this.emitRate && particleEmitRateScale)
@@ -189,14 +207,21 @@ class ParticleEmitter extends EngineObject
189
207
  this.emitParticle();
190
208
  }
191
209
  }
192
- else
193
- this.destroy();
210
+ else if (this.particles.length === 0)
211
+ this.destroy(true);
212
+
213
+ // update and remove destroyed particles
214
+ this.particles = this.particles.filter((p)=>
215
+ {
216
+ p.update();
217
+ return !p.destroyed;
218
+ });
194
219
 
195
220
  if (debugParticles)
196
221
  {
197
222
  // show emitter bounds
198
- if (typeof this.emitSize === 'number')
199
- debugCircle(this.pos, this.emitSize/2, '#0f0');
223
+ if (this.emitCircle)
224
+ debugCircle(this.pos, this.emitSize.x/2, '#0f0');
200
225
  else
201
226
  debugRect(this.pos, this.emitSize, '#0f0', 0, this.angle);
202
227
  }
@@ -207,14 +232,15 @@ class ParticleEmitter extends EngineObject
207
232
  emitParticle()
208
233
  {
209
234
  // spawn a particle
210
- let pos = typeof this.emitSize === 'number' ? // check if number was used
211
- randInCircle(this.emitSize/2) // circle emitter
212
- : vec2(rand(-.5,.5), rand(-.5,.5)) // box emitter
235
+ let pos = this.emitCircle ? // check if circle emitter
236
+ randInCircle(this.emitSize.x/2) // circle emitter
237
+ : vec2(rand(-.5,.5), rand(-.5,.5)) // box emitter
213
238
  .multiply(this.emitSize).rotate(this.angle)
214
239
  let angle = rand(this.particleConeAngle, -this.particleConeAngle);
215
240
  if (!this.localSpace)
216
241
  {
217
- pos = this.pos.add(pos);
242
+ pos.x += this.pos.x;
243
+ pos.y += this.pos.y;
218
244
  angle += this.angle;
219
245
  }
220
246
 
@@ -234,25 +260,17 @@ class ParticleEmitter extends EngineObject
234
260
  const velocityAngle = this.localSpace ? coneAngle : this.angle + coneAngle;
235
261
 
236
262
  // build particle
237
- const particle = new Particle(pos, this.tileInfo, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, this.fadeRate, this.additive, this.trailScale, this.localSpace && this, this.particleDestroyCallback);
238
- particle.velocity = vec2().setAngle(velocityAngle, speed);
239
- particle.angleVelocity = angleSpeed;
263
+ const velocity = vec2(speed*sin(velocityAngle), speed*cos(velocityAngle));
264
+ let angleVelocity = angleSpeed;
240
265
  if (!this.localSpace && this.velocityInheritance > 0)
241
266
  {
242
267
  // apply emitter velocity to particle
243
- particle.velocity.x += this.velocity.x;
244
- particle.velocity.y += this.velocity.y;
245
- particle.angleVelocity += this.angleVelocity;
268
+ velocity.x += this.velocity.x;
269
+ velocity.y += this.velocity.y;
270
+ angleVelocity += this.angleVelocity;
246
271
  }
247
- particle.fadeRate = this.fadeRate;
248
- particle.damping = this.damping;
249
- particle.angleDamping = this.angleDamping;
250
- particle.restitution = this.restitution;
251
- particle.friction = this.friction;
252
- particle.gravityScale = this.gravityScale;
253
- particle.collideTiles = this.collideTiles;
254
- particle.renderOrder = this.renderOrder;
255
- particle.mirror = randBool();
272
+ const particle = new Particle(this, pos, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, velocity, angleVelocity);
273
+ this.particles.push(particle);
256
274
 
257
275
  // call particle create callback
258
276
  this.particleCreateCallback?.(particle);
@@ -261,140 +279,245 @@ class ParticleEmitter extends EngineObject
261
279
  return particle;
262
280
  }
263
281
 
264
- // Particle emitters are not rendered, only the particles are
265
- render() {}
282
+ /** Particle emitters do not have physics */
283
+ updatePhysics() {}
284
+
285
+ /** Render all particles for this emitter */
286
+ render()
287
+ {
288
+ // render all particles
289
+ for (const particle of this.particles)
290
+ particle.render();
291
+ }
292
+
293
+ /** is emitter actively spawning */
294
+ isActive() { return !this.emitTime || this.getAliveTime() < this.emitTime; }
295
+
296
+ /** Destroy the particle emitter
297
+ * @param {boolean} [immediate] - should particle emitters and other attached effects be allowed to die off */
298
+ destroy(immediate=false)
299
+ {
300
+ if (this.destroyed) return;
301
+
302
+ super.destroy(immediate);
303
+ if (!immediate && this.particles.length > 0)
304
+ {
305
+ // wait for particles to die off
306
+ this.destroyed = false;
307
+ this.emitTime = -1;
308
+ }
309
+ }
266
310
  }
267
311
 
268
312
  ///////////////////////////////////////////////////////////////////////////////
269
313
  /**
270
314
  * Particle Object - Created automatically by Particle Emitters
271
- * @extends EngineObject
272
- * @memberof Engine
315
+ * @memberof Particles
273
316
  */
274
- class Particle extends EngineObject
317
+ class Particle
275
318
  {
276
319
  /**
277
320
  * Create a particle with the passed in settings
278
321
  * Typically this is created automatically by a ParticleEmitter
279
- * @param {Vector2} position - World space position of the particle
280
- * @param {TileInfo} tileInfo - Tile info to render particles
281
- * @param {number} angle - Angle to rotate the particle
282
- * @param {Color} colorStart - Color at start of life
283
- * @param {Color} colorEnd - Color at end of life
284
- * @param {number} lifeTime - How long to live for
285
- * @param {number} sizeStart - Size at start of life
286
- * @param {number} sizeEnd - Size at end of life
287
- * @param {number} fadeRate - How quick to fade in/out
288
- * @param {boolean} additive - Does it use additive blend mode
289
- * @param {number} trailScale - If a trail, how long to make it
290
- * @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
291
- * @param {ParticleCallbackFunction} [destroyCallback] - Callback when particle dies
322
+ * @param {ParticleEmitter} emitter - The emitter that created this particle
323
+ * @param {Vector2} pos - World or local space position
324
+ * @param {number} angle - Angle of the particle
325
+ * @param {Color} colorStart - Color at start of life
326
+ * @param {Color} colorEnd - Color at end of life
327
+ * @param {number} lifeTime - How long to live for
328
+ * @param {number} sizeStart - Size at start of life
329
+ * @param {number} sizeEnd - Size at end of life
330
+ * @param {Vector2} [velocity] - Velocity of the particle
331
+ * @param {number} [angleVelocity] - Angular speed of the particle
292
332
  */
293
- constructor(position, tileInfo, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, fadeRate, additive, trailScale, localSpaceEmitter, destroyCallback
294
- )
333
+ constructor(emitter, pos, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, velocity = vec2(), angleVelocity = 0)
295
334
  {
296
- super(position, vec2(), tileInfo, angle);
297
-
298
- /** @property {Color} - Color at start of life */
335
+ /** @property {ParticleEmitter} */
336
+ this.emitter = emitter;
337
+ /** @property {Vector2} */
338
+ this.pos = pos;
339
+ /** @property {number} */
340
+ this.angle = angle;
341
+ /** @property {Vector2} */
342
+ this.size = vec2(sizeStart);
343
+ /** @property {Color} */
344
+ this.color = colorStart.copy();
345
+ /** @property {Color} */
299
346
  this.colorStart = colorStart;
300
- /** @property {Color} - Color at end of life */
347
+ /** @property {Color} */
301
348
  this.colorEnd = colorEnd;
302
- /** @property {number} - How long to live for */
349
+ /** @property {number} */
303
350
  this.lifeTime = lifeTime;
304
- /** @property {number} - Size at start of life */
351
+ /** @property {number} */
305
352
  this.sizeStart = sizeStart;
306
- /** @property {number} - Size at end of life */
353
+ /** @property {number} */
307
354
  this.sizeEnd = sizeEnd;
308
- /** @property {number} - How quick to fade in/out */
309
- this.fadeRate = fadeRate;
310
- /** @property {boolean} - Is it additive */
311
- this.additive = additive;
312
- /** @property {number} - If a trail, how long to make it */
313
- this.trailScale = trailScale;
314
- /** @property {ParticleEmitter} - Parent emitter if local space */
315
- this.localSpaceEmitter = localSpaceEmitter;
316
- /** @property {ParticleCallbackFunction} - Called when particle dies */
317
- this.destroyCallback = destroyCallback;
318
- // particles do not clamp speed by default
319
- this.clampSpeed = false;
355
+ /** @property {Vector2} */
356
+ this.velocity = velocity;
357
+ /** @property {number} */
358
+ this.angleVelocity = angleVelocity;
359
+ /** @property {number} */
360
+ this.spawnTime = time;
361
+ /** @property {boolean} */
362
+ this.mirror = randBool();
363
+ /** @property {EngineObject} */
364
+ this.groundObject = undefined;
365
+ /** @property {boolean} */
366
+ this.destroyed = false;
367
+ /** @property {TileInfo} */
368
+ this.tileInfo = emitter.tileInfo;
320
369
  }
321
370
 
322
- /** Update the object physics, called automatically by engine once each frame */
371
+ /** Update the particle */
323
372
  update()
324
373
  {
325
- if (this.collideTiles || this.collideSolidObjects)
374
+ // emitter properties
375
+ const emitter = this.emitter;
376
+ const damping = emitter.damping;
377
+ const angleDamping = emitter.angleDamping;
378
+ const restitution = emitter.restitution;
379
+ const friction = emitter.friction;
380
+ const gravityScale = emitter.gravityScale;
381
+ const collideTiles = emitter.collideTiles;
382
+ const collideCallback = emitter.particleCollideCallback;
383
+
384
+ // destroy particle when its time runs out
385
+ if (this.lifeTime > 0 && time - this.spawnTime > this.lifeTime)
326
386
  {
327
- // only apply max circular speed if particle can collide
328
- const length2 = this.velocity.lengthSquared();
329
- if (length2 > objectMaxSpeed*objectMaxSpeed)
330
- {
331
- const s = objectMaxSpeed / length2**.5;
332
- this.velocity.x *= s;
333
- this.velocity.y *= s;
334
- }
387
+ this.destroy();
388
+ return;
335
389
  }
336
390
 
337
- if (this.lifeTime > 0 && time - this.spawnTime > this.lifeTime)
391
+ // apply physics
392
+ const oldPos = this.pos.copy();
393
+ this.velocity.x *= damping;
394
+ this.velocity.y *= damping;
395
+ this.pos.x += this.velocity.x += gravity.x * gravityScale;
396
+ this.pos.y += this.velocity.y += gravity.y * gravityScale;
397
+ this.angle += this.angleVelocity *= angleDamping;
398
+
399
+ // don't do collision if solver disabled
400
+ if (!enablePhysicsSolver || !collideTiles) return;
401
+
402
+ // apply max circular speed to prevent going through collision
403
+ const length2 = this.velocity.lengthSquared();
404
+ if (length2 > objectMaxSpeed*objectMaxSpeed)
405
+ {
406
+ const s = objectMaxSpeed / length2**.5;
407
+ this.velocity.x *= s;
408
+ this.velocity.y *= s;
409
+ }
410
+
411
+ // check collision against tiles
412
+ this.groundObject = undefined;
413
+ const testCollision = collideCallback ? (pos)=>
414
+ {
415
+ const data = tileCollisionGetData(pos);
416
+ return data && collideCallback(this, data, pos);
417
+ } : (pos)=> tileCollisionGetData(pos) > 0;
418
+
419
+ if (testCollision(this.pos))
338
420
  {
339
- // destroy particle when its time runs out
340
- const c = this.colorEnd;
341
- this.color.set(c.r, c.g, c.b, c.a);
342
- this.size.set(this.sizeEnd, this.sizeEnd);
343
- this.destroyCallback?.(this);
344
- this.destroyed = 1;
421
+ // if already was stuck in collision, don't do anything
422
+ const hitLayer = tileCollisionTest(this.pos);
423
+ if (!testCollision(oldPos))
424
+ {
425
+ if (!collideCallback || collideCallback?.(this, hitLayer))
426
+ {
427
+ // test which side we bounced off (or both if a corner)
428
+ const isBlockedX = testCollision(vec2(this.pos.x, oldPos.y));
429
+ const isBlockedY = testCollision(vec2(oldPos.x, this.pos.y));
430
+ const hitRestitution = max(restitution, hitLayer.restitution);
431
+ const hitFriction = max(friction, hitLayer.friction);
432
+ if (isBlockedX)
433
+ {
434
+ // move to previous X position and bounce
435
+ this.pos.x = oldPos.x;
436
+ this.velocity.x *= -hitRestitution;
437
+ this.velocity.y *= hitFriction;
438
+ }
439
+ if (isBlockedY || !isBlockedX)
440
+ {
441
+ const wasFalling = this.velocity.y < 0 && gravity.y < 0 || this.velocity.y > 0 && gravity.y > 0;
442
+ if (wasFalling)
443
+ this.groundObject = hitLayer;
444
+
445
+ // move to previous Y position and bounce
446
+ this.pos.y = oldPos.y;
447
+ this.velocity.y *= -hitRestitution;
448
+ this.velocity.x *= hitFriction;
449
+ }
450
+ debugPhysics && debugRect(this.pos, this.size, '#f00');
451
+ }
452
+ }
345
453
  }
346
454
  }
347
455
 
348
- /** Render the particle, automatically called each frame, sorted by renderOrder */
456
+ /** Destroy this particle */
457
+ destroy()
458
+ {
459
+ const destroyCallback = this.emitter.particleDestroyCallback;
460
+ const c = this.colorEnd;
461
+ this.color.set(c.r, c.g, c.b, c.a);
462
+ this.size.set(this.sizeEnd, this.sizeEnd);
463
+ this.destroyed = true;
464
+ destroyCallback?.(this);
465
+ }
466
+
467
+ /** Render the particle, automatically called each frame */
349
468
  render()
350
469
  {
470
+ // emitter properties
471
+ const emitter = this.emitter;
472
+ const localSpace = emitter.localSpace;
473
+ const additive = emitter.additive;
474
+ const trailScale = emitter.trailScale;
475
+ const fadeRate = emitter.fadeRate / 2;
476
+
351
477
  // lerp color and size
352
478
  const p1 = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1, p2 = 1-p1;
353
479
  const radius = p2 * this.sizeStart + p1 * this.sizeEnd;
354
480
  const size = vec2(radius);
481
+ const alphaFade = p1 < fadeRate ? p1/fadeRate :
482
+ p1 > 1-fadeRate ? (1-p1)/fadeRate : 1;
355
483
  this.color.r = p2 * this.colorStart.r + p1 * this.colorEnd.r;
356
484
  this.color.g = p2 * this.colorStart.g + p1 * this.colorEnd.g;
357
485
  this.color.b = p2 * this.colorStart.b + p1 * this.colorEnd.b;
358
- this.color.a = p2 * this.colorStart.a + p1 * this.colorEnd.a;
359
-
360
- // fade alpha
361
- const fadeRate = this.fadeRate/2;
362
- this.color.a *= p1 < fadeRate ? p1/fadeRate :
363
- p1 > 1-fadeRate ? (1-p1)/fadeRate : 1;
486
+ this.color.a = (p2 * this.colorStart.a + p1 * this.colorEnd.a) * alphaFade;
364
487
 
365
488
  // draw the particle
366
- this.additive && setBlendMode(true);
489
+ additive && setBlendMode(true);
367
490
 
368
491
  // update the position and angle for drawing
369
- let pos = this.pos, angle = this.angle;
370
- if (this.localSpaceEmitter)
492
+ const pos = this.pos.copy();
493
+ let angle = this.angle;
494
+ if (localSpace)
371
495
  {
372
496
  // in local space of emitter
373
- const a = this.localSpaceEmitter.angle;
497
+ const a = emitter.angle;
374
498
  const c = cos(a), s = sin(a);
375
- pos = this.localSpaceEmitter.pos.add(
376
- new Vector2(pos.x*c - pos.y*s, pos.x*s + pos.y*c));
377
- angle += this.localSpaceEmitter.angle;
499
+ pos.set(emitter.pos.x + pos.x*c - pos.y*s,
500
+ emitter.pos.y + pos.x*s + pos.y*c);
501
+ angle += a;
378
502
  }
379
- if (this.trailScale)
503
+ if (trailScale)
380
504
  {
381
505
  // trail style particles
382
- const direction = this.localSpaceEmitter ?
383
- this.velocity.rotate(-this.localSpaceEmitter.angle) :
384
- this.velocity;
385
- const speed = direction.length();
506
+ const velocity = localSpace ?
507
+ this.velocity.rotate(-emitter.angle) : this.velocity;
508
+ const speed = velocity.length();
386
509
  if (speed)
387
510
  {
388
511
  // stretch in direction of motion
389
- const trailLength = speed * this.trailScale;
512
+ const trailLength = speed * trailScale;
390
513
  size.y = max(size.x, trailLength);
391
- angle = atan2(direction.x, direction.y);
514
+ angle = atan2(velocity.x, velocity.y);
392
515
  drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
393
516
  }
394
517
  }
395
518
  else
396
519
  drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
397
- this.additive && setBlendMode();
520
+ additive && setBlendMode();
398
521
  debugParticles && debugRect(pos, size, '#f005', 0, angle);
399
522
  }
400
523
  }