littlejsengine 1.17.15 → 1.18.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.
package/dist/littlejs.js CHANGED
@@ -35,7 +35,7 @@ const engineName = 'LittleJS';
35
35
  * @type {string}
36
36
  * @default
37
37
  * @memberof Engine */
38
- const engineVersion = '1.17.15';
38
+ const engineVersion = '1.18.1';
39
39
 
40
40
  /** Frames per second to update
41
41
  * @type {number}
@@ -318,12 +318,17 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
318
318
  }
319
319
  else
320
320
  {
321
+ // apply device pixel ratio for crisp rendering
322
+ const dpr = canvasPixelRatio ?? (devicePixelRatio || 1);
323
+ const viewWidth = innerWidth * dpr | 0;
324
+ const viewHeight = innerHeight * dpr | 0;
325
+
321
326
  // get main canvas size based on window size
322
- mainCanvasSize.x = min(innerWidth, canvasMaxSize.x);
323
- mainCanvasSize.y = min(innerHeight, canvasMaxSize.y);
327
+ mainCanvasSize.x = min(viewWidth, canvasMaxSize.x);
328
+ mainCanvasSize.y = min(viewHeight, canvasMaxSize.y);
324
329
 
325
330
  // responsive aspect ratio with native resolution
326
- const innerAspect = innerWidth / innerHeight;
331
+ const innerAspect = viewWidth / viewHeight;
327
332
  ASSERT(canvasMinAspect <= canvasMaxAspect);
328
333
  if (canvasMaxAspect && innerAspect > canvasMaxAspect)
329
334
  {
@@ -337,6 +342,17 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
337
342
  const h = mainCanvasSize.x / canvasMinAspect | 0;
338
343
  mainCanvasSize.y = min(h, canvasMaxSize.y);
339
344
  }
345
+
346
+ // set CSS display size so backing store renders at viewport size
347
+ const cssW = (mainCanvasSize.x / dpr | 0) + 'px';
348
+ const cssH = (mainCanvasSize.y / dpr | 0) + 'px';
349
+ mainCanvas.style.width = cssW;
350
+ mainCanvas.style.height = cssH;
351
+ if (glCanvas)
352
+ {
353
+ glCanvas.style.width = cssW;
354
+ glCanvas.style.height = cssH;
355
+ }
340
356
  }
341
357
 
342
358
  // clear main canvas and set size
@@ -556,8 +572,9 @@ function drawEngineLogo(t)
556
572
 
557
573
  // LittleJS Logo and Splash Screen
558
574
  const x = mainContext;
559
- const w = mainCanvas.width = innerWidth;
560
- const h = mainCanvas.height = innerHeight;
575
+ const dpr = canvasPixelRatio ?? (devicePixelRatio || 1);
576
+ const w = mainCanvas.width = innerWidth * dpr;
577
+ const h = mainCanvas.height = innerHeight * dpr;
561
578
  {
562
579
  // background
563
580
  const p3 = percent(t, 1, .8);
@@ -951,6 +968,8 @@ function debugShowErrors()
951
968
 
952
969
  function debugInit()
953
970
  {
971
+ if (showEngineVersion)
972
+ console.warn("LittleJS DEBUG build loaded. Use the release build for production.");
954
973
  }
955
974
 
956
975
  function debugUpdate()
@@ -961,8 +980,6 @@ function debugUpdate()
961
980
  debugOverlay = !debugOverlay;
962
981
  if (debugOverlay)
963
982
  {
964
- if (keyWasPressed('Digit0'))
965
- debugWatermark = !debugWatermark;
966
983
  if (keyWasPressed('Digit1'))
967
984
  debugPhysics = !debugPhysics, debugParticles = false;
968
985
  if (keyWasPressed('Digit2'))
@@ -1250,7 +1267,7 @@ function debugRenderPost()
1250
1267
  return;
1251
1268
  }
1252
1269
 
1253
- if (!debugWatermark) return;
1270
+ if (!debugWatermark && !debugOverlay) return;
1254
1271
 
1255
1272
  // update fps display
1256
1273
  mainContext.textAlign = 'right';
@@ -1734,7 +1751,7 @@ function lineTest(posStart, posEnd, testFunction, normal)
1734
1751
  // get ray direction and length
1735
1752
  const dx = posEnd.x - posStart.x;
1736
1753
  const dy = posEnd.y - posStart.y;
1737
- const totalLength = hypot(dx, dy);
1754
+ const totalLength = (dx*dx + dy*dy)**.5;
1738
1755
  if (!totalLength) return;
1739
1756
 
1740
1757
  // current integer cell we are in
@@ -2838,6 +2855,13 @@ let canvasPixelated = false;
2838
2855
  * @memberof Settings */
2839
2856
  let tilesPixelated = true;
2840
2857
 
2858
+ /** Scale factor applied to the canvas backing store for native-resolution rendering.
2859
+ * Pass 1 for no scaling, a number for an explicit ratio, or undefined to track devicePixelRatio each frame.
2860
+ * @type {number|undefined}
2861
+ * @default
2862
+ * @memberof Settings */
2863
+ let canvasPixelRatio = 1;
2864
+
2841
2865
  /** Default font used for text rendering
2842
2866
  * @type {string}
2843
2867
  * @default
@@ -3016,6 +3040,12 @@ let touchGamepadSize = 99;
3016
3040
  * @memberof Settings */
3017
3041
  let touchGamepadAlpha = .3;
3018
3042
 
3043
+ /** How long to display the touch gamepad on screen in seconds, set to 0 to always display
3044
+ * @type {number}
3045
+ * @default
3046
+ * @memberof Settings */
3047
+ let touchGamepadDisplayTime = 3;
3048
+
3019
3049
  /** Allow vibration hardware if it exists
3020
3050
  * @type {boolean}
3021
3051
  * @default
@@ -3144,6 +3174,12 @@ function setCanvasPixelated(pixelated)
3144
3174
  * @memberof Settings */
3145
3175
  function setTilesPixelated(pixelated) { tilesPixelated = pixelated; }
3146
3176
 
3177
+ /** Set the canvas pixel ratio.
3178
+ * Pass a number for an explicit ratio, or call with no argument to track devicePixelRatio each frame.
3179
+ * @param {number} [pixelRatio]
3180
+ * @memberof Settings */
3181
+ function setCanvasPixelRatio(pixelRatio) { canvasPixelRatio = pixelRatio; }
3182
+
3147
3183
  /** Set default font used for text rendering
3148
3184
  * @param {string} font
3149
3185
  * @memberof Settings */
@@ -3290,6 +3326,11 @@ function setTouchGamepadSize(size) { touchGamepadSize = size; }
3290
3326
  * @memberof Settings */
3291
3327
  function setTouchGamepadAlpha(alpha) { touchGamepadAlpha = alpha; }
3292
3328
 
3329
+ /** Set how long to display the touch gamepad on screen in seconds, set to 0 to always display
3330
+ * @param {number} time
3331
+ * @memberof Settings */
3332
+ function setTouchGamepadDisplayTime(time) { touchGamepadDisplayTime = time; }
3333
+
3293
3334
  /** Set to allow vibration hardware if it exists
3294
3335
  * @param {boolean} enable
3295
3336
  * @memberof Settings */
@@ -3561,10 +3602,10 @@ class EngineObject
3561
3602
  // if already was touching, try to push away
3562
3603
  const deltaPos = oldPos.subtract(o.pos);
3563
3604
  const length = deltaPos.length();
3564
- const pushAwayAccel = .001; // push away if already overlapping
3565
- const velocity = length < .01 ? randVec2(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
3605
+ const pushAwayAccel = .001;
3606
+ const velocity = length < .001 ? vec2(0,1) : deltaPos.scale(pushAwayAccel/length);
3566
3607
  this.velocity = this.velocity.add(velocity);
3567
- if (o.mass) // push away if not fixed
3608
+ if (o.mass) // push away other object if not fixed
3568
3609
  o.velocity = o.velocity.subtract(velocity);
3569
3610
 
3570
3611
  debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00');
@@ -3993,6 +4034,7 @@ function tile(index=new Vector2, size=tileDefaultSize, texture=0, padding=tileDe
3993
4034
  const textureInfo = typeof texture === 'number' ?
3994
4035
  textureInfos[texture] : texture;
3995
4036
  ASSERT(textureInfo instanceof TextureInfo, 'tile texture is not loaded');
4037
+ ASSERT(textureInfo.size.x > 0, 'tile texture is not loaded');
3996
4038
 
3997
4039
  // get the position of the tile
3998
4040
  const sizePaddedX = size.x + padding*2;
@@ -5355,7 +5397,7 @@ function inputInit()
5355
5397
  document.addEventListener('mouseup', onMouseUp);
5356
5398
  document.addEventListener('mousemove', onMouseMove);
5357
5399
  document.addEventListener('mouseleave', onMouseLeave);
5358
- document.addEventListener('wheel', onMouseWheel);
5400
+ document.addEventListener('wheel', onMouseWheel, { passive: false });
5359
5401
  document.addEventListener('contextmenu', onContextMenu);
5360
5402
  document.addEventListener('blur', onBlur);
5361
5403
 
@@ -5374,7 +5416,7 @@ function inputInit()
5374
5416
  }
5375
5417
 
5376
5418
  // try to prevent default browser handling of input
5377
- if (!inputPreventDefault || !document.hasFocus() || !e.cancelable) return;
5419
+ if (!inputPreventDefault || !e.cancelable || !document.hasFocus()) return;
5378
5420
 
5379
5421
  // don't break browser shortcuts
5380
5422
  if (e.ctrlKey || e.metaKey || e.altKey) return;
@@ -5433,7 +5475,7 @@ function inputInit()
5433
5475
  mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
5434
5476
  mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
5435
5477
 
5436
- if (inputPreventDefault && document.hasFocus() && e.cancelable)
5478
+ if (inputPreventDefault && e.cancelable && document.hasFocus())
5437
5479
  e.preventDefault();
5438
5480
  }
5439
5481
  function onMouseUp(e)
@@ -5455,7 +5497,12 @@ function inputInit()
5455
5497
  mouseDeltaScreen = mouseDeltaScreen.add(movement);
5456
5498
  }
5457
5499
  function onMouseLeave() { mouseInWindow = false; } // mouse moved off window
5458
- function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
5500
+ function onMouseWheel(e)
5501
+ {
5502
+ mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
5503
+ if (inputPreventDefault && e.cancelable && document.hasFocus())
5504
+ e.preventDefault(); // prevent page scrolling
5505
+ }
5459
5506
  function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
5460
5507
  function onBlur() { inputClear(); } // reset input when focus is lost
5461
5508
 
@@ -5505,7 +5552,7 @@ function inputInit()
5505
5552
  wasTouching = touching;
5506
5553
 
5507
5554
  // prevent default handling like copy, magnifier lens, and scrolling
5508
- if (inputPreventDefault && document.hasFocus() && e.cancelable)
5555
+ if (inputPreventDefault && e.cancelable && document.hasFocus())
5509
5556
  e.preventDefault();
5510
5557
 
5511
5558
  // must return true so the document will get focus
@@ -5663,7 +5710,13 @@ function inputUpdate()
5663
5710
  }
5664
5711
 
5665
5712
  // return if gamepads are disabled or not supported
5666
- if (!gamepadsEnable || !navigator || !navigator.getGamepads) return;
5713
+ try {
5714
+ // protect against getGamepads disallowed security error
5715
+ if (!gamepadsEnable || !navigator?.getGamepads)
5716
+ return;
5717
+ } catch(e) {
5718
+ return;
5719
+ }
5667
5720
 
5668
5721
  // only poll gamepads when focused or in debug mode
5669
5722
  if (!debug && !document.hasFocus()) return;
@@ -5761,10 +5814,10 @@ function inputRender()
5761
5814
  function touchGamepadRender()
5762
5815
  {
5763
5816
  if (!touchInputEnable || !isTouchDevice || headlessMode) return;
5764
- if (!touchGamepadEnable || !touchGamepadTimer.isSet()) return;
5817
+ if (!touchGamepadEnable || !touchGamepadTimer.isSet() && touchGamepadDisplayTime) return;
5765
5818
 
5766
5819
  // fade off when not touching or paused
5767
- const alpha = percent(touchGamepadTimer.get(), 4, 3);
5820
+ const alpha = touchGamepadDisplayTime ? percent(touchGamepadTimer.get(), touchGamepadDisplayTime+1, touchGamepadDisplayTime) : 1;
5768
5821
  if (!alpha || paused) return;
5769
5822
 
5770
5823
  // setup the canvas
@@ -9335,7 +9388,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
9335
9388
  * - Buttons
9336
9389
  * - Checkboxes
9337
9390
  * - Images
9338
- * - Scrollbars
9391
+ * - Sliders
9339
9392
  * - Video
9340
9393
  * @namespace UISystem
9341
9394
  */
@@ -10532,7 +10585,7 @@ class UICheckbox extends UIObject
10532
10585
  ASSERT(isString(text), 'ui checkbox must be a string');
10533
10586
  ASSERT(isColor(color), 'ui checkbox color must be a color');
10534
10587
 
10535
- /** @property {boolean} - Current percentage value of this scrollbar 0-1 */
10588
+ /** @property {boolean} - Current percentage value of this slider 0-1 */
10536
10589
  this.checked = checked;
10537
10590
  // set properties
10538
10591
  this.text = text;
@@ -10567,13 +10620,13 @@ class UICheckbox extends UIObject
10567
10620
 
10568
10621
  ///////////////////////////////////////////////////////////////////////////////
10569
10622
  /**
10570
- * UIScrollbar - A UI object that acts as a scrollbar
10623
+ * UISlider - A UI object that acts as a slider or scrollbar
10571
10624
  * @extends UIObject
10572
10625
  * @memberof UISystem
10573
10626
  */
10574
- class UIScrollbar extends UIObject
10627
+ class UISlider extends UIObject
10575
10628
  {
10576
- /** Create a UIScrollbar object
10629
+ /** Create a UISlider object
10577
10630
  * @param {Vector2} [pos]
10578
10631
  * @param {Vector2} [size]
10579
10632
  * @param {number} [value]
@@ -10585,14 +10638,14 @@ class UIScrollbar extends UIObject
10585
10638
  {
10586
10639
  super(pos, size);
10587
10640
 
10588
- ASSERT(isNumber(value), 'ui scrollbar value must be a number');
10589
- ASSERT(isString(text), 'ui scrollbar must be a string');
10590
- ASSERT(isColor(color), 'ui scrollbar color must be a color');
10591
- ASSERT(isColor(handleColor), 'ui scrollbar handleColor must be a color');
10641
+ ASSERT(isNumber(value), 'ui slider value must be a number');
10642
+ ASSERT(isString(text), 'ui slider must be a string');
10643
+ ASSERT(isColor(color), 'ui slider color must be a color');
10644
+ ASSERT(isColor(handleColor), 'ui slider handleColor must be a color');
10592
10645
 
10593
- /** @property {number} - Current percentage value of this scrollbar 0-1 */
10646
+ /** @property {number} - Current percentage value of this slider 0-1 */
10594
10647
  this.value = value;
10595
- /** @property {Color} - Color for the handle part of the scrollbar */
10648
+ /** @property {Color} - Color for the handle part of the slider */
10596
10649
  this.handleColor = handleColor.copy();
10597
10650
  /** @property {boolean} - Should it fill up like a progress bar? */
10598
10651
  this.fillMode = false;
@@ -10611,7 +10664,7 @@ class UIScrollbar extends UIObject
10611
10664
  const oldValue = this.value;
10612
10665
  if (this.isActiveObject())
10613
10666
  {
10614
- // handle horizontal or vertical scrollbar
10667
+ // handle horizontal or vertical slider
10615
10668
  const isHorizontal = this.size.x > this.size.y;
10616
10669
  const handleSize = isHorizontal ? this.size.y : this.size.x;
10617
10670
  const barSize = isHorizontal ? this.size.x : this.size.y;
@@ -10639,7 +10692,7 @@ class UIScrollbar extends UIObject
10639
10692
  {
10640
10693
  super.render();
10641
10694
 
10642
- // handle horizontal or vertical scrollbar
10695
+ // handle horizontal or vertical slider
10643
10696
  const isHorizontal = this.size.x > this.size.y;
10644
10697
  const barWidth = isHorizontal ? this.size.x : this.size.y;
10645
10698
  const handleWidth = isHorizontal ? this.size.y : this.size.x;
@@ -10657,7 +10710,7 @@ class UIScrollbar extends UIObject
10657
10710
  }
10658
10711
  else
10659
10712
  {
10660
- // draw the scrollbar handle
10713
+ // draw the slider handle
10661
10714
  const value = clamp(isHorizontal ? this.value : 1 - this.value);
10662
10715
  const p = (barWidth - handleWidth) * (value - .5);
10663
10716
  const pos = this.pos.add(isHorizontal ? vec2(p, 0) : vec2(0, p));
@@ -10666,7 +10719,7 @@ class UIScrollbar extends UIObject
10666
10719
  uiSystem.drawRect(pos, drawSize, color, this.lineWidth, this.lineColor, this.cornerRadius, this.gradientColor);
10667
10720
  }
10668
10721
 
10669
- // draw the text scaled to fit on the scrollbar
10722
+ // draw the text scaled to fit on the slider
10670
10723
  const textSize = this.getTextSize();
10671
10724
  uiSystem.drawText(this.text, this.pos, textSize,
10672
10725
  this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
@@ -10941,7 +10994,7 @@ class Box2dObject extends EngineObject
10941
10994
  // draw non-edge fixtures
10942
10995
  this.getFixtureList().forEach((fixture)=>
10943
10996
  {
10944
- const shape = box2d.castObjectType(fixture.GetShape());
10997
+ const shape = box2d.castShapeObject(fixture.GetShape());
10945
10998
  if (shape.GetType() !== box2d.instance.b2Shape.e_edge)
10946
10999
  {
10947
11000
  box2d.drawFixture(fixture, this.pos, this.angle, color, lineColor, lineWidth, useWebGL, context);
@@ -11581,7 +11634,7 @@ class Box2dJoint
11581
11634
  constructor(jointDef)
11582
11635
  {
11583
11636
  /** @property {Object} - The Box2d joint */
11584
- this.box2dJoint = box2d.castObjectType(box2d.world.CreateJoint(jointDef));
11637
+ this.box2dJoint = box2d.castJointObject(box2d.world.CreateJoint(jointDef));
11585
11638
  }
11586
11639
 
11587
11640
  /** Destroy this joint */
@@ -11866,7 +11919,7 @@ class Box2dRevoluteJoint extends Box2dJoint
11866
11919
 
11867
11920
  /** Enable/disable the joint limit
11868
11921
  * @param {boolean} [enable] */
11869
- enableLimit(enable=true) { return this.box2dJoint.enableLimit(enable); }
11922
+ enableLimit(enable=true) { return this.box2dJoint.EnableLimit(enable); }
11870
11923
 
11871
11924
  /** Get the lower joint limit
11872
11925
  * @return {number} */
@@ -12024,7 +12077,7 @@ class Box2dPrismaticJoint extends Box2dJoint
12024
12077
 
12025
12078
  /** Enable/disable the joint limit
12026
12079
  * @param {boolean} [enable] */
12027
- enableLimit(enable=true) { return this.box2dJoint.enableLimit(enable); }
12080
+ enableLimit(enable=true) { return this.box2dJoint.EnableLimit(enable); }
12028
12081
 
12029
12082
  /** Get the lower joint limit
12030
12083
  * @return {number} */
@@ -12638,7 +12691,7 @@ class Box2dPlugin
12638
12691
  * @param {CanvasRenderingContext2D} [context] */
12639
12692
  drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, useWebgl, context)
12640
12693
  {
12641
- const shape = box2d.castObjectType(fixture.GetShape());
12694
+ const shape = box2d.castShapeObject(fixture.GetShape());
12642
12695
  switch (shape.GetType())
12643
12696
  {
12644
12697
  case box2d.instance.b2Shape.e_polygon:
@@ -12696,9 +12749,9 @@ class Box2dPlugin
12696
12749
  * @param {Object} o */
12697
12750
  isNull(o) { return !box2d.instance.getPointer(o); }
12698
12751
 
12699
- /** casts a box2d object to its correct type
12752
+ /** casts a box2d object to a shape type
12700
12753
  * @param {Object} o */
12701
- castObjectType(o)
12754
+ castShapeObject(o)
12702
12755
  {
12703
12756
  switch (o.GetType())
12704
12757
  {
@@ -12710,6 +12763,17 @@ class Box2dPlugin
12710
12763
  return box2d.instance.castObject(o, box2d.instance.b2PolygonShape);
12711
12764
  case box2d.instance.b2Shape.e_chain:
12712
12765
  return box2d.instance.castObject(o, box2d.instance.b2ChainShape);
12766
+ }
12767
+
12768
+ ASSERT(false, 'Unknown box2d object type');
12769
+ }
12770
+
12771
+ /** casts a box2d object to a joint type
12772
+ * @param {Object} o */
12773
+ castJointObject(o)
12774
+ {
12775
+ switch (o.GetType())
12776
+ {
12713
12777
  case box2d.instance.e_revoluteJoint:
12714
12778
  return box2d.instance.castObject(o, box2d.instance.b2RevoluteJoint);
12715
12779
  case box2d.instance.e_prismaticJoint: