littlejsengine 1.18.15 → 1.18.18

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/src/engineMath.js CHANGED
@@ -924,10 +924,25 @@ class Color
924
924
  * @return {Color} */
925
925
  setFrom(c) { return this.set(c.r, c.g, c.b, c.a); }
926
926
 
927
+ /** Sets the alpha of this color and returns self
928
+ * @param {number} [a] - alpha
929
+ * @return {Color} */
930
+ setAlpha(a=1)
931
+ {
932
+ this.a = a;
933
+ ASSERT_COLOR_VALID(this);
934
+ return this;
935
+ }
936
+
927
937
  /** Returns a new color that is a copy of this
928
938
  * @return {Color} */
929
939
  copy() { return new Color(this.r, this.g, this.b, this.a); }
930
940
 
941
+ /** Returns a copy of this color with the alpha set
942
+ * @param {number} [a] - alpha
943
+ * @return {Color} */
944
+ withAlpha(a=1) { return new Color(this.r, this.g, this.b, a); }
945
+
931
946
  /** Returns a copy of this color plus the color passed in
932
947
  * @param {Color} c - other color
933
948
  * @return {Color} */
@@ -139,10 +139,10 @@ class EngineObject
139
139
  if (pa)
140
140
  {
141
141
  const c = cos(-pa), s = sin(-pa);
142
- this.pos = new Vector2(lx*c - ly*s + pp.x, lx*s + ly*c + pp.y);
142
+ this.pos.set(lx*c - ly*s + pp.x, lx*s + ly*c + pp.y);
143
143
  }
144
144
  else
145
- this.pos = new Vector2(lx + pp.x, ly + pp.y);
145
+ this.pos.set(lx + pp.x, ly + pp.y);
146
146
  this.angle = mirror*this.localAngle + pa;
147
147
  }
148
148
 
@@ -374,6 +374,9 @@ class EngineObject
374
374
  drawTile(this.pos, this.drawSize || this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
375
375
  }
376
376
 
377
+ /** Optional hook called during the light system plugin's lightmap pass to draw this object's lightmap contribution. Does nothing by default. */
378
+ renderLight() {}
379
+
377
380
  /** Destroy this object, destroy its children, detach its parent, and mark it for removal
378
381
  * @param {boolean} [immediate] - should attached effects be allowed to die off? */
379
382
  destroy(immediate=false)
@@ -62,10 +62,10 @@ class ParticleEmitter extends EngineObject
62
62
  * @param {number} [particleTime] - How long particles live
63
63
  * @param {number} [sizeStart] - How big are particles at start
64
64
  * @param {number} [sizeEnd] - How big are particles at end
65
- * @param {number} [speed] - How fast are particles when spawned
66
- * @param {number} [angleSpeed] - How fast are particles rotating
67
- * @param {number} [damping] - How much to dampen particle speed
68
- * @param {number} [angleDamping] - How much to dampen particle angular speed
65
+ * @param {number} [speed] - How fast are particles when spawned, in world units per frame (at 60fps, so multiply units/sec by 1/60)
66
+ * @param {number} [angleSpeed] - How fast are particles rotating, in radians per frame (at 60fps)
67
+ * @param {number} [damping] - How much to dampen particle speed, per-frame velocity multiplier (1 = no damping, .9 = lose 10% speed each frame)
68
+ * @param {number} [angleDamping] - How much to dampen particle angular speed, per-frame multiplier (1 = no damping)
69
69
  * @param {number} [gravityScale] - How much gravity effect particles
70
70
  * @param {number} [particleConeAngle] - Cone for start particle angle
71
71
  * @param {number} [fadeRate] - Fraction of life spent fading: half at fade-in (start), half at fade-out (end). e.g. .2 = 10% fade-in, 80% full opacity, 10% fade-out
@@ -140,13 +140,13 @@ class ParticleEmitter extends EngineObject
140
140
  this.sizeStart = sizeStart;
141
141
  /** @property {number} - How big are particles at end */
142
142
  this.sizeEnd = sizeEnd;
143
- /** @property {number} - How fast are particles when spawned */
143
+ /** @property {number} - Particle speed when spawned, in world units per frame (at 60fps) */
144
144
  this.speed = speed;
145
- /** @property {number} - How fast are particles rotating */
145
+ /** @property {number} - Particle angular speed when spawned, in radians per frame (at 60fps) */
146
146
  this.angleSpeed = angleSpeed;
147
- /** @property {number} - How much to dampen particle speed */
147
+ /** @property {number} - Per-frame velocity multiplier (1 = no damping, .9 = lose 10% speed each frame) */
148
148
  this.damping = damping;
149
- /** @property {number} - How much to dampen particle angular speed */
149
+ /** @property {number} - Per-frame angular velocity multiplier (1 = no damping) */
150
150
  this.angleDamping = angleDamping;
151
151
  /** @property {number} - How much gravity affects particles */
152
152
  this.gravityScale = gravityScale;
@@ -215,12 +215,16 @@ class ParticleEmitter extends EngineObject
215
215
  else if (this.particles.length === 0)
216
216
  this.destroy(true);
217
217
 
218
- // update and remove destroyed particles
219
- this.particles = this.particles.filter((p)=>
218
+ // update and remove destroyed particles in place to avoid per-frame array allocation
219
+ const particles = this.particles;
220
+ let alive = 0;
221
+ for (let i = 0; i < particles.length; ++i)
220
222
  {
223
+ const p = particles[i];
221
224
  p.update();
222
- return !p.destroyed;
223
- });
225
+ if (!p.destroyed) particles[alive++] = p;
226
+ }
227
+ particles.length = alive;
224
228
 
225
229
  if (debugParticles)
226
230
  {
@@ -492,9 +496,6 @@ class Particle
492
496
  this.color.b = p2 * this.colorStart.b + p1 * this.colorEnd.b;
493
497
  this.color.a = (p2 * this.colorStart.a + p1 * this.colorEnd.a) * alphaFade;
494
498
 
495
- // draw the particle
496
- additive && setBlendMode(true);
497
-
498
499
  // update the position and angle for drawing
499
500
  const pos = particleDrawPos.set(this.pos.x, this.pos.y);
500
501
  let angle = this.angle;
@@ -502,16 +503,19 @@ class Particle
502
503
  {
503
504
  // in local space of emitter
504
505
  const a = emitter.angle;
505
- const c = cos(a), s = sin(a);
506
+ const c = cos(-a), s = sin(-a);
506
507
  pos.set(emitter.pos.x + pos.x*c - pos.y*s,
507
508
  emitter.pos.y + pos.x*s + pos.y*c);
508
509
  angle += a;
509
510
  }
511
+
512
+ // draw the particle
513
+ additive && setAdditiveBlendMode();
510
514
  if (trailScale)
511
515
  {
512
516
  // trail style particles
513
- const velocity = localSpace ?
514
- this.velocity.rotate(-emitter.angle) : this.velocity;
517
+ const velocity = localSpace ?
518
+ this.velocity.rotate(emitter.angle) : this.velocity;
515
519
  const speed = velocity.length();
516
520
  if (speed)
517
521
  {
@@ -524,7 +528,7 @@ class Particle
524
528
  }
525
529
  else
526
530
  drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
527
- additive && setBlendMode();
531
+ additive && setAdditiveBlendMode(false);
528
532
  debugParticles && debugRect(pos, size, '#f005', 0, angle);
529
533
  }
530
534
  }
@@ -18,6 +18,7 @@ const debugPhysics = 0;
18
18
  const debugParticles = 0;
19
19
  const debugRaycast = 0;
20
20
  const debugGamepads = 0;
21
+ const debugSound = 0;
21
22
  const debugPointSize = .5;
22
23
 
23
24
  // debug commands are automatically removed from the final build
@@ -89,6 +89,8 @@ let canvasFixedSize = vec2();
89
89
  let canvasPixelated = false;
90
90
 
91
91
  /** Disables texture filtering for crisper pixel art
92
+ * - Leave true for pixel art so sprites stay sharp when scaled (uses NEAREST filtering)
93
+ * - Set false for smooth/high-resolution art to enable bilinear filtering and mipmaps
92
94
  * @type {boolean}
93
95
  * @default
94
96
  * @memberof Settings */
@@ -244,35 +246,78 @@ let touchInputEnable = true;
244
246
  * - Supports left analog stick, 4 face buttons and start button (button 9)
245
247
  * - setTouchGamepadButtonCount(1) to use face buttons as right analog stick
246
248
  * - Analog stick buttons 10 and 11 are also activated when virtual sticks are touched
247
-
249
+ * - Rendered as a full-viewport HTML/SVG overlay, so controls may sit outside the game canvas
248
250
  * @type {boolean}
249
251
  * @default
250
252
  * @memberof Settings */
251
253
  let touchGamepadEnable = false;
252
254
 
253
- /** True if touch gamepad should have start button in the center
254
- * - Prevents activating within 2*touchGamepadSize of the virtual stick or face buttons
255
- * (one radius for the visible control + one radius of buffer beyond its edge)
255
+ /** True if touches outside the gamepad controls should still drive mouse/touch input
256
+ * - When false (the default), enabling the touch gamepad suppresses touch-to-mouse input entirely
257
+ * - Set true to also pass touches outside the controls through to the game as mouse/touch input
258
+ * - Touches on the gamepad controls never drive the mouse regardless of this setting
259
+ * @type {boolean}
260
+ * @default
261
+ * @memberof Settings */
262
+ let touchGamepadPassthrough = false;
263
+
264
+ /** Size of center button if touch gamepad should have start button in the center
265
+ * - Prevents activating when pressed near virtual stick or face buttons
256
266
  * - When the game is paused, any touch will press the button
257
- * - Set size to enable the center button
267
+ * - Measured in viewport CSS pixels
258
268
  * @type {number}
259
269
  * @default
260
270
  * @memberof Settings */
261
- let touchGamepadCenterButtonSize = 300;
271
+ let touchGamepadCenterButtonSize = 0;
262
272
 
263
- /** Number of buttons on touch gamepad (0-4), if 1 also acts as right analog stick
273
+ /** Number of buttons on the right side of the touch gamepad (0-4), using gamepad buttons 0-3
274
+ * - A count of 1 is a single large button (the size of a stick)
275
+ * - Ignored when touchGamepadRightStick is set (the right side is a stick instead)
264
276
  * @type {number}
265
277
  * @default
266
278
  * @memberof Settings */
267
279
  let touchGamepadButtonCount = 4;
268
280
 
281
+ /** True if the touch gamepad should have a left analog stick (or dpad)
282
+ * - When false, the left side is face buttons (touchGamepadLeftButtonCount) or nothing
283
+ * @type {boolean}
284
+ * @default
285
+ * @memberof Settings */
286
+ let touchGamepadLeftStick = true;
287
+
288
+ /** Number of buttons on the left side of the touch gamepad (0-4), using gamepad buttons 4-7
289
+ * - Only used when touchGamepadLeftStick is false (otherwise the left side is a stick)
290
+ * - A count of 1 is a single large button (the size of a stick)
291
+ * @type {number}
292
+ * @default
293
+ * @memberof Settings */
294
+ let touchGamepadLeftButtonCount = 0;
295
+
296
+ /** True if the touch gamepad right side should be an analog stick (or dpad) instead of face buttons
297
+ * - When set, touchGamepadButtonCount is ignored and the right side is a stick
298
+ * - Uses an analog stick when touchGamepadAnalog is true, otherwise an 8 way dpad
299
+ * @type {boolean}
300
+ * @default
301
+ * @memberof Settings */
302
+ let touchGamepadRightStick = false;
303
+
269
304
  /** True if touch gamepad should be analog stick or false to use if 8 way dpad
270
305
  * @type {boolean}
271
306
  * @default
272
307
  * @memberof Settings */
273
308
  let touchGamepadAnalog = true;
274
309
 
275
- /** Size of virtual gamepad for touch devices in pixels
310
+ /** True if touch gamepad directional controls should float to where you press
311
+ * - Only affects analog sticks and dpads, not face buttons
312
+ * - Directional controls re-anchor to where you press within the bottom ~60% of their screen half; the top ~40% passes through to the game
313
+ * - The right side floats only when it acts as the right analog stick (touchGamepadRightStick is set)
314
+ * - A center button (touchGamepadCenterButtonSize) still works since it ignores touches near the sticks
315
+ * @type {boolean}
316
+ * @default
317
+ * @memberof Settings */
318
+ let touchGamepadFloating = false;
319
+
320
+ /** Size of virtual gamepad for touch devices in viewport CSS pixels
276
321
  * @type {number}
277
322
  * @default
278
323
  * @memberof Settings */
@@ -290,6 +335,13 @@ let touchGamepadAlpha = .3;
290
335
  * @memberof Settings */
291
336
  let touchGamepadDisplayTime = 3;
292
337
 
338
+ /** Duration in ms to vibrate when a touch gamepad face button or start button is pressed
339
+ * - Set to 0 to disable, also requires vibrateEnable and hardware support (ignored on iOS)
340
+ * @type {number}
341
+ * @default
342
+ * @memberof Settings */
343
+ let touchGamepadVibration = 0;
344
+
293
345
  /** Allow vibration hardware if it exists
294
346
  * @type {boolean}
295
347
  * @default
@@ -392,6 +444,7 @@ function setCanvasPixelated(pixelated)
392
444
  }
393
445
 
394
446
  /** Disables texture filtering for crisper pixel art
447
+ * - Leave true for pixel art; set false for smooth/high-resolution art
395
448
  * @param {boolean} pixelated
396
449
  * @memberof Settings */
397
450
  function setTilesPixelated(pixelated) { tilesPixelated = pixelated; }
@@ -522,6 +575,11 @@ function setTouchInputEnable(enable) { touchInputEnable = enable; }
522
575
  * @memberof Settings */
523
576
  function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
524
577
 
578
+ /** Set if touches outside the gamepad controls should still drive mouse/touch input
579
+ * @param {boolean} passthrough
580
+ * @memberof Settings */
581
+ function setTouchGamepadPassthrough(passthrough) { touchGamepadPassthrough = passthrough; }
582
+
525
583
  /** Set if touch gamepad should have start button in the center
526
584
  * - Set size to enable the center button
527
585
  * - When the game is paused, any touch will press the button
@@ -529,16 +587,57 @@ function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
529
587
  * @memberof Settings */
530
588
  function setTouchGamepadCenterButtonSize(size) { touchGamepadCenterButtonSize = size; }
531
589
 
532
- /** Set number of buttons on touch gamepad (0-4), if 1 also acts as right analog stick
590
+ /** Set number of buttons on the right side of the touch gamepad (0-4, gamepad buttons 0-3)
591
+ * @param {number} count
592
+ * @memberof Settings */
593
+ function setTouchGamepadButtonCount(count)
594
+ {
595
+ touchGamepadButtonCount = count;
596
+ if (count > 0)
597
+ touchGamepadRightStick = false;
598
+ }
599
+
600
+ /** Set if the touch gamepad should have a left analog stick (or dpad)
601
+ * @param {boolean} enable
602
+ * @memberof Settings */
603
+ function setTouchGamepadLeftStick(enable)
604
+ {
605
+ touchGamepadLeftStick = enable;
606
+ if (enable)
607
+ touchGamepadLeftButtonCount = 0;
608
+ }
609
+
610
+ /** Set number of buttons on the left side of the touch gamepad (0-4, gamepad buttons 4-7)
611
+ * - Only used when touchGamepadLeftStick is false
533
612
  * @param {number} count
534
613
  * @memberof Settings */
535
- function setTouchGamepadButtonCount(count) { touchGamepadButtonCount = count; }
614
+ function setTouchGamepadLeftButtonCount(count)
615
+ {
616
+ touchGamepadLeftButtonCount = count;
617
+ if (count > 0)
618
+ touchGamepadLeftStick = false;
619
+ }
620
+
621
+ /** Set if the touch gamepad right side is an analog stick (or dpad) instead of face buttons
622
+ * @param {boolean} rightStick
623
+ * @memberof Settings */
624
+ function setTouchGamepadRightStick(rightStick)
625
+ {
626
+ touchGamepadRightStick = rightStick;
627
+ if (rightStick)
628
+ touchGamepadButtonCount = 0;
629
+ }
536
630
 
537
631
  /** Set if touch gamepad should be analog stick or 8 way dpad
538
632
  * @param {boolean} analog
539
633
  * @memberof Settings */
540
634
  function setTouchGamepadAnalog(analog) { touchGamepadAnalog = analog; }
541
635
 
636
+ /** Set if touch gamepad directional controls should float to where you press
637
+ * @param {boolean} floating
638
+ * @memberof Settings */
639
+ function setTouchGamepadFloating(floating) { touchGamepadFloating = floating; }
640
+
542
641
  /** Set size of virtual gamepad for touch devices in pixels
543
642
  * @param {number} size
544
643
  * @memberof Settings */
@@ -554,6 +653,11 @@ function setTouchGamepadAlpha(alpha) { touchGamepadAlpha = alpha; }
554
653
  * @memberof Settings */
555
654
  function setTouchGamepadDisplayTime(time) { touchGamepadDisplayTime = time; }
556
655
 
656
+ /** Set duration in ms to vibrate when a touch gamepad face or start button is pressed (0 disables)
657
+ * @param {number} ms
658
+ * @memberof Settings */
659
+ function setTouchGamepadVibration(ms) { touchGamepadVibration = ms; }
660
+
557
661
  /** Set to allow vibration hardware if it exists
558
662
  * @param {boolean} enable
559
663
  * @memberof Settings */
@@ -675,6 +675,11 @@ class TileCollisionLayer extends TileLayer
675
675
  // check any tiles in the area for collision
676
676
  const posX = pos.x - this.pos.x;
677
677
  const posY = pos.y - this.pos.y;
678
+ // reject AABBs entirely past either edge; without this, the negative
679
+ // side leaks into row/col 0 because minX/minY clamp to 0 and the
680
+ // point-test floor below forces maxX/maxY up to 1
681
+ if (posX + size.x/2 < 0 || posX - size.x/2 > this.size.x) return false;
682
+ if (posY + size.y/2 < 0 || posY - size.y/2 > this.size.y) return false;
678
683
  const minX = max(posX - size.x/2|0, 0);
679
684
  const minY = max(posY - size.y/2|0, 0);
680
685
  // ensure at least one cell is visited even when size is 0 and pos