littlejsengine 1.16.2 → 1.17.1

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 (47) hide show
  1. package/dist/littlejs.d.ts +200 -166
  2. package/dist/littlejs.esm.js +1231 -1086
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +888 -735
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +884 -731
  7. package/examples/box2d/gameObjects.js +1 -1
  8. package/examples/breakout/game.js +5 -6
  9. package/examples/electron/index.html +2 -2
  10. package/examples/electron/tiles.png +0 -0
  11. package/examples/htmlMenu/tiles.png +0 -0
  12. package/examples/index.html +1 -1
  13. package/examples/logo.png +0 -0
  14. package/examples/module/tiles.png +0 -0
  15. package/examples/platformer/gameEffects.js +23 -22
  16. package/examples/platformer/gameLevel.js +24 -25
  17. package/examples/shorts/base.html +1 -1
  18. package/examples/shorts/clock.js +3 -3
  19. package/examples/shorts/fontImage.js +4 -3
  20. package/examples/shorts/parallax.js +1 -1
  21. package/examples/shorts/sequencer.js +1 -1
  22. package/examples/shorts/shapes.js +1 -1
  23. package/examples/shorts/texture.js +10 -6
  24. package/examples/shorts/tiles.png +0 -0
  25. package/examples/shorts/tiltedView.js +2 -0
  26. package/examples/starter/index.html +2 -2
  27. package/examples/starter/tiles.png +0 -0
  28. package/examples/typescript/tiles.png +0 -0
  29. package/examples/uiSystem/game.js +1 -1
  30. package/examples/uiSystem/tiles.png +0 -0
  31. package/package.json +1 -1
  32. package/plugins/postProcess.js +47 -28
  33. package/plugins/uiSystem.js +15 -15
  34. package/reference.md +1 -1
  35. package/src/engine.js +174 -174
  36. package/src/engineAudio.js +4 -6
  37. package/src/engineDebug.js +4 -4
  38. package/src/engineDraw.js +179 -122
  39. package/src/engineExport.js +326 -334
  40. package/src/engineFont.png +0 -0
  41. package/src/engineInput.js +14 -20
  42. package/src/engineMath.js +15 -104
  43. package/src/engineObject.js +26 -25
  44. package/src/engineParticles.js +2 -2
  45. package/src/engineTileLayer.js +196 -170
  46. package/src/engineUtilities.js +100 -4
  47. package/src/engineWebGL.js +123 -72
@@ -55,6 +55,10 @@ let inputPreventDefault = true;
55
55
  * @memberof Input */
56
56
  let gamepadPrimary = 0;
57
57
 
58
+ /** True if a touch device has been detected
59
+ * @memberof Input */
60
+ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
61
+
58
62
  /** Prevents input continuing to the default browser handling
59
63
  * This is useful to disable for html menus so the browser can handle input normally
60
64
  * @param {boolean} preventDefault
@@ -253,10 +257,6 @@ function gamepadStickCount(gamepad=gamepadPrimary)
253
257
  return gamepadStickData[gamepad]?.length ?? 0;
254
258
  }
255
259
 
256
- /** True if a touch device has been detected
257
- * @memberof Input */
258
- const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
259
-
260
260
  ///////////////////////////////////////////////////////////////////////////////
261
261
 
262
262
  /** Pulse the vibration hardware if it exists
@@ -265,7 +265,7 @@ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
265
265
  function vibrate(pattern=100)
266
266
  {
267
267
  ASSERT(isNumber(pattern) || isArray(pattern), 'pattern must be a number or array');
268
- vibrateEnable && !headlessMode && navigator && navigator.vibrate && navigator.vibrate(pattern);
268
+ vibrateEnable && !headlessMode && navigator?.vibrate?.(pattern);
269
269
  }
270
270
 
271
271
  /** Cancel any ongoing vibration
@@ -383,8 +383,8 @@ function inputInit()
383
383
  }
384
384
  function onMouseUp(e)
385
385
  {
386
- if (isTouchDevice && touchInputEnable)
387
- return;
386
+ if (isTouchDevice && touchInputEnable) return;
387
+
388
388
  inputData[0][e.button] = (inputData[0][e.button]&2) | 4;
389
389
  }
390
390
  function onMouseMove(e)
@@ -570,8 +570,7 @@ function inputUpdate()
570
570
  // update touch gamepad if enabled
571
571
  if (touchGamepadEnable && isTouchDevice)
572
572
  {
573
- if (!touchGamepadTimer.isSet())
574
- return;
573
+ if (!touchGamepadTimer.isSet()) return;
575
574
 
576
575
  // read virtual analog stick
577
576
  gamepadPrimary = 0; // touch gamepad uses index 0
@@ -609,12 +608,10 @@ function inputUpdate()
609
608
  }
610
609
 
611
610
  // return if gamepads are disabled or not supported
612
- if (!gamepadsEnable || !navigator || !navigator.getGamepads)
613
- return;
611
+ if (!gamepadsEnable || !navigator || !navigator.getGamepads) return;
614
612
 
615
613
  // only poll gamepads when focused or in debug mode
616
- if (!debug && !document.hasFocus())
617
- return;
614
+ if (!debug && !document.hasFocus()) return;
618
615
 
619
616
  // poll gamepads
620
617
  const maxGamepads = 8;
@@ -651,8 +648,7 @@ function inputUpdate()
651
648
  data[j] = button.pressed ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
652
649
 
653
650
  // check for any input on this gamepad, analog must be full press
654
- if (button.pressed)
655
- if (!button.value || button.value > .9)
651
+ if (button.pressed && (!button.value || button.value > .9))
656
652
  hadInput = true;
657
653
  }
658
654
 
@@ -681,7 +677,7 @@ function inputUpdate()
681
677
  }
682
678
 
683
679
  // copy dpad to left analog stick when pressed
684
- if (gamepadDirectionEmulateStick && !dpad.isZero())
680
+ if (gamepadDirectionEmulateStick && (dpad.x || dpad.y))
685
681
  sticks[0] = dpad.clampLength();
686
682
  }
687
683
 
@@ -710,13 +706,11 @@ function inputRender()
710
706
  function touchGamepadRender()
711
707
  {
712
708
  if (!touchInputEnable || !isTouchDevice || headlessMode) return;
713
- if (!touchGamepadEnable || !touchGamepadTimer.isSet())
714
- return;
709
+ if (!touchGamepadEnable || !touchGamepadTimer.isSet()) return;
715
710
 
716
711
  // fade off when not touching or paused
717
712
  const alpha = percent(touchGamepadTimer.get(), 4, 3);
718
- if (!alpha || paused)
719
- return;
713
+ if (!alpha || paused) return;
720
714
 
721
715
  // setup the canvas
722
716
  const context = mainContext;
package/src/engineMath.js CHANGED
@@ -1,10 +1,9 @@
1
1
  /**
2
- * LittleJS Utility Classes and Functions
2
+ * LittleJS Math Classes and Functions
3
3
  * - General purpose math library
4
- * - Vector2 - fast, simple, easy 2D vector class
5
- * - Color - holds a rgba color with some math functions
6
- * - Timer - tracks time automatically
7
4
  * - RandomGenerator - seeded random number generator
5
+ * - Vector2 - fast, simple, easy 2D vector class
6
+ * - Color - holds a rgba color with math functions
8
7
  * @namespace Math
9
8
  */
10
9
 
@@ -194,11 +193,11 @@ function nearestPowerOfTwo(value) { return 2**ceil(log2(value)); }
194
193
 
195
194
  /** Returns true if two axis aligned bounding boxes are overlapping
196
195
  * this can be used for simple collision detection between objects
197
- * @param {Vector2} posA - Center of box A
198
- * @param {Vector2} sizeA - Size of box A
199
- * @param {Vector2} posB - Center of box B
200
- * @param {Vector2} [sizeB=(0,0)] - Size of box B, uses a point if undefined
201
- * @return {boolean} - True if overlapping
196
+ * @param {Vector2} posA - Center of box A
197
+ * @param {Vector2} sizeA - Size of box A
198
+ * @param {Vector2} posB - Center of box B
199
+ * @param {Vector2} [sizeB=vec2()] - Size of box B, uses a point if undefined
200
+ * @return {boolean} - True if overlapping
202
201
  * @memberof Math */
203
202
  function isOverlapping(posA, sizeA, posB, sizeB=vec2())
204
203
  {
@@ -272,7 +271,7 @@ function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
272
271
  * @param {any} s
273
272
  * @return {boolean}
274
273
  * @memberof Math */
275
- function isString(s) { return s !== undefined && s !== null && typeof s.toString() === 'string'; }
274
+ function isString(s) { return s != null && typeof s?.toString() === 'string'; }
276
275
 
277
276
  /**
278
277
  * Check if object is an array
@@ -416,8 +415,8 @@ function randInCircle(radius=1, minRadius=0)
416
415
  { return radius > 0 ? randVec2(radius * rand(minRadius / radius, 1)**.5) : new Vector2; }
417
416
 
418
417
  /** Returns a random color between the two passed in colors, combine components if linear
419
- * @param {Color} [colorA=(1,1,1,1)]
420
- * @param {Color} [colorB=(0,0,0,1)]
418
+ * @param {Color} [colorA=WHITE]
419
+ * @param {Color} [colorB=BLACK]
421
420
  * @param {boolean} [linear]
422
421
  * @return {Color}
423
422
  * @memberof Random */
@@ -496,8 +495,8 @@ class RandomGenerator
496
495
  { return vec2(this.float(valueA, valueB), this.float(valueA, valueB)); }
497
496
 
498
497
  /** Returns a random color between the two passed in colors, combine components if linear
499
- * @param {Color} [colorA=(1,1,1,1)]
500
- * @param {Color} [colorB=(0,0,0,1)]
498
+ * @param {Color} [colorA=WHITE]
499
+ * @param {Color} [colorB=BLACK]
501
500
  * @param {boolean} [linear]
502
501
  * @return {Color} */
503
502
  randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
@@ -541,7 +540,7 @@ class RandomGenerator
541
540
  * a = vec2(5); // set a to (5, 5)
542
541
  * b = vec2(); // set b to (0, 0)
543
542
  * @memberof Math */
544
- function vec2(x=0, y) { return new Vector2(x, y === undefined ? x : y); }
543
+ function vec2(x=0, y) { return new Vector2(x, y ?? x); }
545
544
 
546
545
  /**
547
546
  * Check if object is a valid Vector2
@@ -749,10 +748,6 @@ class Vector2
749
748
  * @return {number} */
750
749
  area() { return abs(this.x * this.y); }
751
750
 
752
- /** Returns true if this vector is (0,0)
753
- * @return {boolean} */
754
- isZero() { return !this.x && !this.y; }
755
-
756
751
  /** Returns a new vector that is p percent between this and the vector passed in
757
752
  * @param {Vector2} v - other vector
758
753
  * @param {number} percent
@@ -1114,88 +1109,4 @@ const PURPLE = debugProtectConstant(rgb(.5,0,1));
1114
1109
  /** Color - Magenta #ff00ff
1115
1110
  * @type {Color}
1116
1111
  * @memberof Math */
1117
- const MAGENTA = debugProtectConstant(rgb(1,0,1));
1118
-
1119
- ///////////////////////////////////////////////////////////////////////////////
1120
-
1121
- /**
1122
- * Timer object tracks how long has passed since it was set
1123
- * @memberof Engine
1124
- * @example
1125
- * let a = new Timer; // creates a timer that is not set
1126
- * a.set(3); // sets the timer to 3 seconds
1127
- *
1128
- * let b = new Timer(1); // creates a timer with 1 second left
1129
- * b.unset(); // unset the timer
1130
- */
1131
- class Timer
1132
- {
1133
- /** Create a timer object set time passed in
1134
- * @param {number} [timeLeft] - How much time left before the timer
1135
- * @param {boolean} [useRealTime] - Should the timer keep running even when the game is paused? (useful for UI) */
1136
- constructor(timeLeft, useRealTime=false)
1137
- {
1138
- ASSERT(timeLeft === undefined || isNumber(timeLeft), 'Constructed Timer is invalid.', timeLeft);
1139
- this.useRealTime = useRealTime;
1140
- const globalTime = this.getGlobalTime();
1141
- this.time = timeLeft === undefined ? undefined : globalTime + timeLeft;
1142
- this.setTime = timeLeft;
1143
- }
1144
-
1145
- /** Set the timer with seconds passed in
1146
- * @param {number} [timeLeft] - How much time left before the timer is elapsed in seconds */
1147
- set(timeLeft=0)
1148
- {
1149
- ASSERT(isNumber(timeLeft), 'Timer is invalid.', timeLeft);
1150
- const globalTime = this.getGlobalTime();
1151
- this.time = globalTime + timeLeft;
1152
- this.setTime = timeLeft;
1153
- }
1154
-
1155
- /** Set if the timer should keep running even when the game is paused
1156
- * @param {boolean} [useRealTime] */
1157
- setUseRealTime(useRealTime=true)
1158
- {
1159
- ASSERT(!this.isSet(), 'Cannot change global time setting while timer is set.');
1160
- this.useRealTime = useRealTime;
1161
- }
1162
-
1163
- /** Unset the timer */
1164
- unset() { this.time = undefined; }
1165
-
1166
- /** Returns true if set
1167
- * @return {boolean} */
1168
- isSet() { return this.time !== undefined; }
1169
-
1170
- /** Returns true if set and has not elapsed
1171
- * @return {boolean} */
1172
- active() { return this.getGlobalTime() < this.time; }
1173
-
1174
- /** Returns true if set and elapsed
1175
- * @return {boolean} */
1176
- elapsed() { return this.getGlobalTime() >= this.time; }
1177
-
1178
- /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
1179
- * @return {number} */
1180
- get() { return this.isSet()? this.getGlobalTime() - this.time : 0; }
1181
-
1182
- /** Get percentage elapsed based on time it was set to, returns 0 if not set
1183
- * @return {number} */
1184
- getPercent() { return this.isSet()? 1-percent(this.time - this.getGlobalTime(), 0, this.setTime) : 0; }
1185
-
1186
- /** Get the time this timer was set to, returns 0 if not set
1187
- * @return {number} */
1188
- getSetTime() { return this.isSet() ? this.setTime : 0; }
1189
-
1190
- /** Get the current global time this timer is based on
1191
- * @return {number} */
1192
- getGlobalTime() { return this.useRealTime ? timeReal : time; }
1193
-
1194
- /** Returns this timer expressed as a string
1195
- * @return {string} */
1196
- toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
1197
-
1198
- /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
1199
- * @return {number} */
1200
- valueOf() { return this.get(); }
1201
- }
1112
+ const MAGENTA = debugProtectConstant(rgb(1,0,1));
@@ -33,10 +33,10 @@
33
33
  class EngineObject
34
34
  {
35
35
  /** Create an engine object and adds it to the list of objects
36
- * @param {Vector2} [pos=(0,0)] - World space position of the object
37
- * @param {Vector2} [size=(1,1)] - World space size of the object
38
- * @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
39
- * @param {number} [angle] - Angle the object is rotated by
36
+ * @param {Vector2} [pos=vec2()] - World space position of the object
37
+ * @param {Vector2} [size=vec2(1)] - World space size of the object
38
+ * @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
39
+ * @param {number} [angle] - Angle the object is rotated by
40
40
  * @param {Color} [color=WHITE] - Color to apply to tile when rendered
41
41
  * @param {number} [renderOrder] - Objects sorted by renderOrder before being rendered
42
42
  */
@@ -164,15 +164,16 @@ class EngineObject
164
164
  // physics sanity checks
165
165
  ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
166
166
  ASSERT(this.damping >= 0 && this.damping <= 1);
167
- if (!enablePhysicsSolver || !this.mass) // don't do collision for static objects
168
- return;
167
+
168
+ // don't do collision for static objects or if solver disabled
169
+ if (!enablePhysicsSolver || !this.mass) return;
169
170
 
170
171
  const wasFalling = this.velocity.y < 0 && gravity.y < 0 || this.velocity.y > 0 && gravity.y > 0;
171
172
  if (this.groundObject)
172
173
  {
173
174
  // apply friction in local space of ground object
174
175
  const friction = max(this.friction, this.groundObject.friction);
175
- const groundSpeed = this.groundObject.velocity ? this.groundObject.velocity.x : 0;
176
+ const groundSpeed = this.groundObject.velocity.x;
176
177
  this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * friction;
177
178
  this.groundObject = undefined;
178
179
  }
@@ -183,19 +184,19 @@ class EngineObject
183
184
  const epsilon = .001; // necessary to push slightly outside of the collision
184
185
  for (const o of engineObjectsCollide)
185
186
  {
187
+ // skip destroyed, child objects, or self collision
188
+ if (o.destroyed || o.parent || o === this) continue;
189
+
186
190
  // non solid objects don't collide with each other
187
- if ((!this.isSolid && !o.isSolid) || o.destroyed || o.parent || o === this)
188
- continue;
191
+ if (!this.isSolid && !o.isSolid) continue;
189
192
 
190
193
  // check collision
191
- if (!this.isOverlappingObject(o))
192
- continue;
194
+ if (!this.isOverlappingObject(o)) continue;
193
195
 
194
196
  // notify objects of collision and check if should be resolved
195
197
  const collide1 = this.collideWithObject(o);
196
198
  const collide2 = o.collideWithObject(this);
197
- if (!collide1 || !collide2)
198
- continue;
199
+ if (!collide1 || !collide2) continue;
199
200
 
200
201
  if (isOverlapping(oldPos, this.size, o.pos, o.size))
201
202
  {
@@ -297,7 +298,7 @@ class EngineObject
297
298
  const delta = y - this.pos.y;
298
299
  if (delta < maxMoveUp)
299
300
  if (!tileCollisionTest(vec2(this.pos.x, y), this.size, this))
300
- {
301
+ {
301
302
  this.pos.y = y;
302
303
  debugPhysics && debugRect(this.pos, this.size, '#ff0');
303
304
  return;
@@ -357,10 +358,10 @@ class EngineObject
357
358
 
358
359
  // disconnect from parent and destroy children
359
360
  this.destroyed = 1;
360
- this.parent && this.parent.removeChild(this);
361
+ this.parent?.removeChild(this);
361
362
  for (const child of this.children)
362
363
  {
363
- child.parent = 0;
364
+ child.parent = undefined;
364
365
  child.destroy();
365
366
  }
366
367
  }
@@ -381,15 +382,15 @@ class EngineObject
381
382
  * @param {Vector2} vec - world space vector */
382
383
  worldToLocalVector(vec) { return vec.rotate(-this.angle); }
383
384
 
384
- /** Called to check if a tile collision should be resolved
385
+ /** Called to check if a tile collision should be resolved. Return true for physics to resolve the collision or false to ignore and resolve it manually.
385
386
  * @param {number} tileData - the value of the tile at the position
386
- * @param {Vector2} pos - tile where the collision occurred
387
- * @return {boolean} - true if the collision should be resolved */
387
+ * @param {Vector2} pos - tile where the collision occurred
388
+ * @return {boolean} - true if the collision should be resolved by modifying it's position and velocity */
388
389
  collideWithTile(tileData, pos) { return tileData > 0; }
389
390
 
390
- /** Called to check if a object collision should be resolved
391
+ /** Called by the engine to check if an object collision should be resolved. Return true for physics to resolve the collision or false to ignore and resolve it manually.
391
392
  * @param {EngineObject} object - the object to test against
392
- * @return {boolean} - true if the collision should be resolved
393
+ * @return {boolean} - true if the collision should be resolved by modifying it's position and velocity
393
394
  */
394
395
  collideWithObject(object) { return true; }
395
396
 
@@ -430,9 +431,9 @@ class EngineObject
430
431
  * @return {number} -1 if this.mirror is true, or 1 if not mirrored */
431
432
  getMirrorSign() { return this.mirror ? -1 : 1; }
432
433
 
433
- /** Attaches a child to this with a local transform, returns child for chaining
434
+ /** Attaches a child to this with a local transform, returns child for chaining
434
435
  * @param {EngineObject} child
435
- * @param {Vector2} [localPos=(0,0)]
436
+ * @param {Vector2} [localPos=vec2()]
436
437
  * @param {number} [localAngle]
437
438
  * @return {EngineObject} The child object added */
438
439
  addChild(child, localPos=vec2(), localAngle=0)
@@ -456,7 +457,7 @@ class EngineObject
456
457
  const index = this.children.indexOf(child);
457
458
  ASSERT(index >= 0, 'child not found in children array');
458
459
  index >= 0 && this.children.splice(index, 1);
459
- child.parent = 0;
460
+ child.parent = undefined;
460
461
  }
461
462
 
462
463
  /** Check if overlapping another engine object
@@ -468,7 +469,7 @@ class EngineObject
468
469
 
469
470
  /** Check if overlapping a point or aligned bounding box
470
471
  * @param {Vector2} pos - Center of box
471
- * @param {Vector2} [size=(0,0)] - Size of box, uses a point if undefined
472
+ * @param {Vector2} [size=vec2()] - Size of box, uses a point if undefined
472
473
  * @return {boolean} */
473
474
  isOverlapping(pos, size=vec2())
474
475
  { return isOverlapping(this.pos, this.size, pos, size); }
@@ -255,7 +255,7 @@ class ParticleEmitter extends EngineObject
255
255
  particle.mirror = randBool();
256
256
 
257
257
  // call particle create callback
258
- this.particleCreateCallback && this.particleCreateCallback(particle);
258
+ this.particleCreateCallback?.(particle);
259
259
 
260
260
  // return the newly created particle
261
261
  return particle;
@@ -340,7 +340,7 @@ class Particle extends EngineObject
340
340
  const c = this.colorEnd;
341
341
  this.color.set(c.r, c.g, c.b, c.a);
342
342
  this.size.set(this.sizeEnd, this.sizeEnd);
343
- this.destroyCallback && this.destroyCallback(this);
343
+ this.destroyCallback?.(this);
344
344
  this.destroyed = 1;
345
345
  }
346
346
  }