littlejsengine 1.18.0 → 1.18.2

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.18.0';
38
+ const engineVersion = '1.18.2';
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()
@@ -1732,7 +1751,7 @@ function lineTest(posStart, posEnd, testFunction, normal)
1732
1751
  // get ray direction and length
1733
1752
  const dx = posEnd.x - posStart.x;
1734
1753
  const dy = posEnd.y - posStart.y;
1735
- const totalLength = hypot(dx, dy);
1754
+ const totalLength = (dx*dx + dy*dy)**.5;
1736
1755
  if (!totalLength) return;
1737
1756
 
1738
1757
  // current integer cell we are in
@@ -2836,6 +2855,13 @@ let canvasPixelated = false;
2836
2855
  * @memberof Settings */
2837
2856
  let tilesPixelated = true;
2838
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
+
2839
2865
  /** Default font used for text rendering
2840
2866
  * @type {string}
2841
2867
  * @default
@@ -2977,18 +3003,22 @@ let touchInputEnable = true;
2977
3003
 
2978
3004
  /** True if touch gamepad should appear on mobile devices
2979
3005
  * - Supports left analog stick, 4 face buttons and start button (button 9)
3006
+ * - setTouchGamepadButtonCount(1) to use face buttons as right analog stick
3007
+ * - Analog stick buttons 10 and 11 are also activated when virtual sticks are touched
3008
+
2980
3009
  * @type {boolean}
2981
3010
  * @default
2982
3011
  * @memberof Settings */
2983
3012
  let touchGamepadEnable = false;
2984
3013
 
2985
3014
  /** True if touch gamepad should have start button in the center
3015
+ * - Prevents activating if overlappng with virtual stick or buttons if they are enabled
2986
3016
  * - When the game is paused, any touch will press the button
2987
- * - This can function as a way to pause/unpause the game
2988
- * @type {boolean}
3017
+ * - Set size to enable the center button
3018
+ * @type {number}
2989
3019
  * @default
2990
3020
  * @memberof Settings */
2991
- let touchGamepadCenterButton = true;
3021
+ let touchGamepadCenterButtonSize = 300;
2992
3022
 
2993
3023
  /** Number of buttons on touch gamepad (0-4), if 1 also acts as right analog stick
2994
3024
  * @type {number}
@@ -3006,7 +3036,7 @@ let touchGamepadAnalog = true;
3006
3036
  * @type {number}
3007
3037
  * @default
3008
3038
  * @memberof Settings */
3009
- let touchGamepadSize = 99;
3039
+ let touchGamepadSize = 100;
3010
3040
 
3011
3041
  /** Transparency of touch gamepad overlay
3012
3042
  * @type {number}
@@ -3148,6 +3178,12 @@ function setCanvasPixelated(pixelated)
3148
3178
  * @memberof Settings */
3149
3179
  function setTilesPixelated(pixelated) { tilesPixelated = pixelated; }
3150
3180
 
3181
+ /** Set the canvas pixel ratio.
3182
+ * Pass a number for an explicit ratio, or call with no argument to track devicePixelRatio each frame.
3183
+ * @param {number} [pixelRatio]
3184
+ * @memberof Settings */
3185
+ function setCanvasPixelRatio(pixelRatio) { canvasPixelRatio = pixelRatio; }
3186
+
3151
3187
  /** Set default font used for text rendering
3152
3188
  * @param {string} font
3153
3189
  * @memberof Settings */
@@ -3268,11 +3304,12 @@ function setTouchInputEnable(enable) { touchInputEnable = enable; }
3268
3304
  * @memberof Settings */
3269
3305
  function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
3270
3306
 
3271
- /** True if touch gamepad should have start button in the center
3272
- * - This can function as a way to pause/unpause the game
3273
- * @param {boolean} enable
3307
+ /** Set if touch gamepad should have start button in the center
3308
+ * - Set size to enable the center button
3309
+ * - When the game is paused, any touch will press the button
3310
+ * @param {number} size
3274
3311
  * @memberof Settings */
3275
- function setTouchGamepadCenterButton(enable) { touchGamepadCenterButton = enable; }
3312
+ function setTouchGamepadCenterButtonSize(size) { touchGamepadCenterButtonSize = size; }
3276
3313
 
3277
3314
  /** Set number of buttons on touch gamepad (0-4), if 1 also acts as right analog stick
3278
3315
  * @param {number} count
@@ -3570,10 +3607,10 @@ class EngineObject
3570
3607
  // if already was touching, try to push away
3571
3608
  const deltaPos = oldPos.subtract(o.pos);
3572
3609
  const length = deltaPos.length();
3573
- const pushAwayAccel = .001; // push away if already overlapping
3574
- const velocity = length < .01 ? randVec2(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
3610
+ const pushAwayAccel = .001;
3611
+ const velocity = length < .001 ? vec2(0,1) : deltaPos.scale(pushAwayAccel/length);
3575
3612
  this.velocity = this.velocity.add(velocity);
3576
- if (o.mass) // push away if not fixed
3613
+ if (o.mass) // push away other object if not fixed
3577
3614
  o.velocity = o.velocity.subtract(velocity);
3578
3615
 
3579
3616
  debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00');
@@ -3810,6 +3847,7 @@ class EngineObject
3810
3847
  child.parent = this;
3811
3848
  child.localPos = localPos.copy();
3812
3849
  child.localAngle = localAngle;
3850
+ child.updateTransforms();
3813
3851
  return child;
3814
3852
  }
3815
3853
 
@@ -4002,6 +4040,7 @@ function tile(index=new Vector2, size=tileDefaultSize, texture=0, padding=tileDe
4002
4040
  const textureInfo = typeof texture === 'number' ?
4003
4041
  textureInfos[texture] : texture;
4004
4042
  ASSERT(textureInfo instanceof TextureInfo, 'tile texture is not loaded');
4043
+ ASSERT(textureInfo.size.x > 0, 'tile texture is not loaded');
4005
4044
 
4006
4045
  // get the position of the tile
4007
4046
  const sizePaddedX = size.x + padding*2;
@@ -5364,7 +5403,7 @@ function inputInit()
5364
5403
  document.addEventListener('mouseup', onMouseUp);
5365
5404
  document.addEventListener('mousemove', onMouseMove);
5366
5405
  document.addEventListener('mouseleave', onMouseLeave);
5367
- document.addEventListener('wheel', onMouseWheel);
5406
+ document.addEventListener('wheel', onMouseWheel, { passive: false });
5368
5407
  document.addEventListener('contextmenu', onContextMenu);
5369
5408
  document.addEventListener('blur', onBlur);
5370
5409
 
@@ -5383,7 +5422,7 @@ function inputInit()
5383
5422
  }
5384
5423
 
5385
5424
  // try to prevent default browser handling of input
5386
- if (!inputPreventDefault || !document.hasFocus() || !e.cancelable) return;
5425
+ if (!inputPreventDefault || !e.cancelable || !document.hasFocus()) return;
5387
5426
 
5388
5427
  // don't break browser shortcuts
5389
5428
  if (e.ctrlKey || e.metaKey || e.altKey) return;
@@ -5442,7 +5481,7 @@ function inputInit()
5442
5481
  mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
5443
5482
  mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
5444
5483
 
5445
- if (inputPreventDefault && document.hasFocus() && e.cancelable)
5484
+ if (inputPreventDefault && e.cancelable && document.hasFocus())
5446
5485
  e.preventDefault();
5447
5486
  }
5448
5487
  function onMouseUp(e)
@@ -5464,7 +5503,12 @@ function inputInit()
5464
5503
  mouseDeltaScreen = mouseDeltaScreen.add(movement);
5465
5504
  }
5466
5505
  function onMouseLeave() { mouseInWindow = false; } // mouse moved off window
5467
- function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
5506
+ function onMouseWheel(e)
5507
+ {
5508
+ mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
5509
+ if (inputPreventDefault && e.cancelable && document.hasFocus())
5510
+ e.preventDefault(); // prevent page scrolling
5511
+ }
5468
5512
  function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
5469
5513
  function onBlur() { inputClear(); } // reset input when focus is lost
5470
5514
 
@@ -5514,7 +5558,7 @@ function inputInit()
5514
5558
  wasTouching = touching;
5515
5559
 
5516
5560
  // prevent default handling like copy, magnifier lens, and scrolling
5517
- if (inputPreventDefault && document.hasFocus() && e.cancelable)
5561
+ if (inputPreventDefault && e.cancelable && document.hasFocus())
5518
5562
  e.preventDefault();
5519
5563
 
5520
5564
  // must return true so the document will get focus
@@ -5535,7 +5579,7 @@ function inputInit()
5535
5579
  if (touching)
5536
5580
  {
5537
5581
  touchGamepadTimer.set();
5538
- if (touchGamepadCenterButton && !wasTouching && paused)
5582
+ if (touchGamepadCenterButtonSize && !wasTouching && paused)
5539
5583
  {
5540
5584
  // touch anywhere to press start when paused
5541
5585
  touchGamepadButtons[9] = 1;
@@ -5560,6 +5604,7 @@ function inputInit()
5560
5604
  // virtual analog stick
5561
5605
  const delta = touchPos.subtract(stickCenter);
5562
5606
  touchGamepadSticks[0] = delta.scale(2/touchGamepadSize).clampLength();
5607
+ touchGamepadButtons[10] = 1; // also press a button when touching stick
5563
5608
  }
5564
5609
  else if (buttonCenter.distance(touchPos) < touchGamepadSize)
5565
5610
  {
@@ -5568,6 +5613,7 @@ function inputInit()
5568
5613
  // virtual right analog stick
5569
5614
  const delta = touchPos.subtract(buttonCenter);
5570
5615
  touchGamepadSticks[1] = delta.scale(2/touchGamepadSize).clampLength();
5616
+ touchGamepadButtons[11] = 1; // also press a button when touching right stick
5571
5617
  }
5572
5618
  // virtual face buttons
5573
5619
  let button = buttonCenter.subtract(touchPos).direction();
@@ -5584,8 +5630,7 @@ function inputInit()
5584
5630
  if (button < touchGamepadButtonCount)
5585
5631
  touchGamepadButtons[button] = 1;
5586
5632
  }
5587
- else if (touchGamepadCenterButton &&
5588
- startCenter.distance(touchPos) < touchGamepadSize)
5633
+ else if (startCenter.distance(touchPos) < touchGamepadCenterButtonSize)
5589
5634
  {
5590
5635
  // virtual start button in center
5591
5636
  touchGamepadButtons[9] = 1;
@@ -5634,6 +5679,18 @@ function inputUpdate()
5634
5679
  // update touch gamepad if enabled
5635
5680
  if (touchGamepadEnable && isTouchDevice)
5636
5681
  {
5682
+ if (debugGamepads)
5683
+ {
5684
+ const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
5685
+ const buttonCenter = touchGamepadButtonCenter();
5686
+ const startCenter = mainCanvasSize.scale(.5);
5687
+
5688
+ debugCircle(stickCenter, 2*touchGamepadSize, 'cyan', 0, false, true);
5689
+ debugCircle(buttonCenter, 2*touchGamepadSize, 'cyan', 0, false, true);
5690
+ if (touchGamepadCenterButtonSize)
5691
+ debugCircle(startCenter, 2*touchGamepadCenterButtonSize, 'cyan', 0, false, true);
5692
+ }
5693
+
5637
5694
  if (!touchGamepadTimer.isSet()) return;
5638
5695
 
5639
5696
  // read virtual analog stick
@@ -5661,7 +5718,7 @@ function inputUpdate()
5661
5718
 
5662
5719
  // read virtual gamepad buttons
5663
5720
  const data = inputData[1] ?? (inputData[1] = []);
5664
- for (let i=10; i--;)
5721
+ for (let i=12; i--;)
5665
5722
  {
5666
5723
  const wasDown = gamepadIsDown(i,0);
5667
5724
  data[i] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
@@ -5672,7 +5729,13 @@ function inputUpdate()
5672
5729
  }
5673
5730
 
5674
5731
  // return if gamepads are disabled or not supported
5675
- if (!gamepadsEnable || !navigator || !navigator.getGamepads) return;
5732
+ try {
5733
+ // protect against getGamepads disallowed security error
5734
+ if (!gamepadsEnable || !navigator?.getGamepads)
5735
+ return;
5736
+ } catch(e) {
5737
+ return;
5738
+ }
5676
5739
 
5677
5740
  // only poll gamepads when focused or in debug mode
5678
5741
  if (!debug && !document.hasFocus()) return;