littlejsengine 1.18.0 → 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.
@@ -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.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);
@@ -1054,7 +1071,7 @@ function lineTest(posStart, posEnd, testFunction, normal)
1054
1071
  // get ray direction and length
1055
1072
  const dx = posEnd.x - posStart.x;
1056
1073
  const dy = posEnd.y - posStart.y;
1057
- const totalLength = hypot(dx, dy);
1074
+ const totalLength = (dx*dx + dy*dy)**.5;
1058
1075
  if (!totalLength) return;
1059
1076
 
1060
1077
  // current integer cell we are in
@@ -2158,6 +2175,13 @@ let canvasPixelated = false;
2158
2175
  * @memberof Settings */
2159
2176
  let tilesPixelated = true;
2160
2177
 
2178
+ /** Scale factor applied to the canvas backing store for native-resolution rendering.
2179
+ * Pass 1 for no scaling, a number for an explicit ratio, or undefined to track devicePixelRatio each frame.
2180
+ * @type {number|undefined}
2181
+ * @default
2182
+ * @memberof Settings */
2183
+ let canvasPixelRatio = 1;
2184
+
2161
2185
  /** Default font used for text rendering
2162
2186
  * @type {string}
2163
2187
  * @default
@@ -2470,6 +2494,12 @@ function setCanvasPixelated(pixelated)
2470
2494
  * @memberof Settings */
2471
2495
  function setTilesPixelated(pixelated) { tilesPixelated = pixelated; }
2472
2496
 
2497
+ /** Set the canvas pixel ratio.
2498
+ * Pass a number for an explicit ratio, or call with no argument to track devicePixelRatio each frame.
2499
+ * @param {number} [pixelRatio]
2500
+ * @memberof Settings */
2501
+ function setCanvasPixelRatio(pixelRatio) { canvasPixelRatio = pixelRatio; }
2502
+
2473
2503
  /** Set default font used for text rendering
2474
2504
  * @param {string} font
2475
2505
  * @memberof Settings */
@@ -2892,10 +2922,10 @@ class EngineObject
2892
2922
  // if already was touching, try to push away
2893
2923
  const deltaPos = oldPos.subtract(o.pos);
2894
2924
  const length = deltaPos.length();
2895
- const pushAwayAccel = .001; // push away if already overlapping
2896
- const velocity = length < .01 ? randVec2(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
2925
+ const pushAwayAccel = .001;
2926
+ const velocity = length < .001 ? vec2(0,1) : deltaPos.scale(pushAwayAccel/length);
2897
2927
  this.velocity = this.velocity.add(velocity);
2898
- if (o.mass) // push away if not fixed
2928
+ if (o.mass) // push away other object if not fixed
2899
2929
  o.velocity = o.velocity.subtract(velocity);
2900
2930
 
2901
2931
  debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00');
@@ -3324,6 +3354,7 @@ function tile(index=new Vector2, size=tileDefaultSize, texture=0, padding=tileDe
3324
3354
  const textureInfo = typeof texture === 'number' ?
3325
3355
  textureInfos[texture] : texture;
3326
3356
  ASSERT(textureInfo instanceof TextureInfo, 'tile texture is not loaded');
3357
+ ASSERT(textureInfo.size.x > 0, 'tile texture is not loaded');
3327
3358
 
3328
3359
  // get the position of the tile
3329
3360
  const sizePaddedX = size.x + padding*2;
@@ -4686,7 +4717,7 @@ function inputInit()
4686
4717
  document.addEventListener('mouseup', onMouseUp);
4687
4718
  document.addEventListener('mousemove', onMouseMove);
4688
4719
  document.addEventListener('mouseleave', onMouseLeave);
4689
- document.addEventListener('wheel', onMouseWheel);
4720
+ document.addEventListener('wheel', onMouseWheel, { passive: false });
4690
4721
  document.addEventListener('contextmenu', onContextMenu);
4691
4722
  document.addEventListener('blur', onBlur);
4692
4723
 
@@ -4705,7 +4736,7 @@ function inputInit()
4705
4736
  }
4706
4737
 
4707
4738
  // try to prevent default browser handling of input
4708
- if (!inputPreventDefault || !document.hasFocus() || !e.cancelable) return;
4739
+ if (!inputPreventDefault || !e.cancelable || !document.hasFocus()) return;
4709
4740
 
4710
4741
  // don't break browser shortcuts
4711
4742
  if (e.ctrlKey || e.metaKey || e.altKey) return;
@@ -4764,7 +4795,7 @@ function inputInit()
4764
4795
  mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
4765
4796
  mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
4766
4797
 
4767
- if (inputPreventDefault && document.hasFocus() && e.cancelable)
4798
+ if (inputPreventDefault && e.cancelable && document.hasFocus())
4768
4799
  e.preventDefault();
4769
4800
  }
4770
4801
  function onMouseUp(e)
@@ -4786,7 +4817,12 @@ function inputInit()
4786
4817
  mouseDeltaScreen = mouseDeltaScreen.add(movement);
4787
4818
  }
4788
4819
  function onMouseLeave() { mouseInWindow = false; } // mouse moved off window
4789
- function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
4820
+ function onMouseWheel(e)
4821
+ {
4822
+ mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
4823
+ if (inputPreventDefault && e.cancelable && document.hasFocus())
4824
+ e.preventDefault(); // prevent page scrolling
4825
+ }
4790
4826
  function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
4791
4827
  function onBlur() { inputClear(); } // reset input when focus is lost
4792
4828
 
@@ -4836,7 +4872,7 @@ function inputInit()
4836
4872
  wasTouching = touching;
4837
4873
 
4838
4874
  // prevent default handling like copy, magnifier lens, and scrolling
4839
- if (inputPreventDefault && document.hasFocus() && e.cancelable)
4875
+ if (inputPreventDefault && e.cancelable && document.hasFocus())
4840
4876
  e.preventDefault();
4841
4877
 
4842
4878
  // must return true so the document will get focus
@@ -4994,7 +5030,13 @@ function inputUpdate()
4994
5030
  }
4995
5031
 
4996
5032
  // return if gamepads are disabled or not supported
4997
- if (!gamepadsEnable || !navigator || !navigator.getGamepads) return;
5033
+ try {
5034
+ // protect against getGamepads disallowed security error
5035
+ if (!gamepadsEnable || !navigator?.getGamepads)
5036
+ return;
5037
+ } catch(e) {
5038
+ return;
5039
+ }
4998
5040
 
4999
5041
  // only poll gamepads when focused or in debug mode
5000
5042
  if (!debug && !document.hasFocus()) return;
@@ -24,7 +24,7 @@ LJS.setTilesPixelated(false);
24
24
  ///////////////////////////////////////////////////////////////////////////////
25
25
  // game variables
26
26
 
27
- const maxScenes = 11;
27
+ const sceneCount = 12;
28
28
  const startScene = 0;
29
29
  export let spriteAtlas, groundObject, mouseJoint, repeatSpawnTimer = new LJS.Timer;
30
30
  const sound_click = new LJS.Sound([.2,.1,,,,.01,,,,,,,,,,,,,,,-500]);
@@ -125,7 +125,7 @@ function gameUpdate()
125
125
  {
126
126
  // change scene
127
127
  const upPressed = LJS.keyWasPressed('ArrowUp');
128
- setScene(LJS.mod(Scenes.scene + (upPressed?1:-1), maxScenes));
128
+ setScene(LJS.mod(Scenes.scene + (upPressed?1:-1), sceneCount));
129
129
  }
130
130
  }
131
131
 
@@ -446,6 +446,80 @@ export class ClothObject extends LJS.Box2dStaticObject
446
446
  }
447
447
  }
448
448
 
449
+ ///////////////////////////////////////////////////////////////////////////////
450
+
451
+ // ragdoll with head, arms, and legs connected to torso by revolute joints
452
+ export class RagdollObject extends LJS.Box2dObject
453
+ {
454
+ constructor(pos, scale=1, color=LJS.randColor())
455
+ {
456
+ const headSize = .8 * scale;
457
+ const torsoWidth = 1.2 * scale;
458
+ const torsoHeight = 2 * scale;
459
+ const limbWidth = .4 * scale;
460
+ const upperArmLen = 1 * scale;
461
+ const lowerArmLen = 1 * scale;
462
+ const upperLegLen = 1.1 * scale;
463
+ const lowerLegLen = 1.1 * scale;
464
+
465
+ // torso is the main body
466
+ super(pos, vec2(torsoWidth, torsoHeight), 0, 0, color);
467
+ this.addBox(vec2(torsoWidth, torsoHeight), vec2(), 0, density, friction, restitution);
468
+ this.limbs = [];
469
+
470
+ const addLimb = (offset, size, isCircle=false) =>
471
+ {
472
+ const o = new LJS.Box2dObject(pos.add(offset), size, 0, 0, color);
473
+ if (isCircle)
474
+ o.addCircle(size.x, vec2(), density, friction, restitution);
475
+ else
476
+ o.addBox(size, vec2(), 0, density, friction, restitution);
477
+ this.limbs.push(o);
478
+ return o;
479
+ };
480
+ const addJoint = (a, b, anchor, lower, upper) =>
481
+ {
482
+ const joint = new LJS.Box2dRevoluteJoint(a, b, anchor);
483
+ joint.enableLimit();
484
+ joint.setLimits(lower, upper);
485
+ };
486
+
487
+ // head
488
+ const head = addLimb(vec2(0, torsoHeight/2 + headSize/2 + .1*scale), vec2(headSize), true);
489
+ addJoint(this, head, pos.add(vec2(0, torsoHeight/2)), -.5, .5);
490
+
491
+ // arms
492
+ const shoulderY = torsoHeight/2 - .2*scale;
493
+ for (const side of [-1, 1])
494
+ {
495
+ const shoulder = pos.add(vec2(side*torsoWidth/2, shoulderY));
496
+ const upperArm = addLimb(vec2(side*(torsoWidth/2 + upperArmLen/2), shoulderY), vec2(upperArmLen, limbWidth));
497
+ addJoint(this, upperArm, shoulder, -2, 2);
498
+
499
+ const elbow = shoulder.add(vec2(side*upperArmLen, 0));
500
+ const lowerArm = addLimb(vec2(side*(torsoWidth/2 + upperArmLen + lowerArmLen/2), shoulderY), vec2(lowerArmLen, limbWidth));
501
+ addJoint(upperArm, lowerArm, elbow, -2.5, 2.5);
502
+ }
503
+
504
+ // legs
505
+ for (const side of [-1, 1])
506
+ {
507
+ const hip = pos.add(vec2(side*torsoWidth/4, -torsoHeight/2));
508
+ const upperLeg = addLimb(vec2(side*torsoWidth/4, -torsoHeight/2 - upperLegLen/2), vec2(limbWidth, upperLegLen));
509
+ addJoint(this, upperLeg, hip, -.5, 2.5);
510
+
511
+ const knee = hip.add(vec2(0, -upperLegLen));
512
+ const lowerLeg = addLimb(vec2(side*torsoWidth/4, -torsoHeight/2 - upperLegLen - lowerLegLen/2), vec2(limbWidth, lowerLegLen));
513
+ addJoint(upperLeg, lowerLeg, knee, -2.5, 2.5);
514
+ }
515
+ }
516
+ destroy()
517
+ {
518
+ this.limbs.forEach(o => o.destroy());
519
+ super.destroy();
520
+ }
521
+ }
522
+
449
523
  ///////////////////////////////////////////////////////////////////////////////
450
524
  // Effects
451
525
 
@@ -184,4 +184,11 @@ export function loadScene(_scene)
184
184
  for (let i=3;i--;)
185
185
  new GameObjects.SoftBodyObject(vec2(20, 3+i*7), vec2(6-i), vec2(9-i), LJS.randColor());
186
186
  }
187
+ if (scene == 11)
188
+ {
189
+ sceneName = 'Ragdolls';
190
+ new GameObjects.RagdollObject(vec2(20,5));
191
+ new GameObjects.RagdollObject(vec2(10,8), 2);
192
+ GameObjects.spawnPyramid(vec2(30,0), 6);
193
+ }
187
194
  }
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html><meta charset=utf-8><body>
2
- <script src=../../dist/littlejs.js?1.18.0></script>
2
+ <script src=../../dist/littlejs.js?1.18.1></script>
3
3
  <script src=../../dist/box2d.wasm.js></script>
4
4
 
5
5
  <!-- LittleJS Engine Source
@@ -7,7 +7,7 @@
7
7
  </head><body>
8
8
 
9
9
  <!-- LittleJS Engine -->
10
- <script src=../../dist/littlejs.js?1.18.0></script>
10
+ <script src=../../dist/littlejs.js?1.18.1></script>
11
11
 
12
12
  <!-- LittleJS Engine Source
13
13
  <script src=../../src/engine.js></script>
@@ -32,4 +32,4 @@
32
32
  -->
33
33
 
34
34
  <!-- Add your game scripts here -->
35
- <script src=game.js?1.18.0></script>
35
+ <script src=game.js?1.18.1></script>
@@ -69,7 +69,7 @@ function gameUpdate()
69
69
 
70
70
  // right click = drop test object
71
71
  if (LJS.mouseIsDown(2))
72
- new TestObject(LJS.mousePos);
72
+ new TestObject(LJS.mousePos.add(LJS.randInCircle()));
73
73
 
74
74
  // mouse hold = add objects
75
75
  if (LJS.mouseIsDown(0))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "littlejsengine",
3
- "version": "1.18.0",
3
+ "version": "1.18.1",
4
4
  "description": "LittleJS - Tiny and Fast HTML5 Game Engine",
5
5
  "main": "dist/littlejs.esm.js",
6
6
  "types": "dist/littlejs.d.ts",
package/src/engine.js CHANGED
@@ -32,7 +32,7 @@ const engineName = 'LittleJS';
32
32
  * @type {string}
33
33
  * @default
34
34
  * @memberof Engine */
35
- const engineVersion = '1.18.0';
35
+ const engineVersion = '1.18.1';
36
36
 
37
37
  /** Frames per second to update
38
38
  * @type {number}
@@ -315,12 +315,17 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
315
315
  }
316
316
  else
317
317
  {
318
+ // apply device pixel ratio for crisp rendering
319
+ const dpr = canvasPixelRatio ?? (devicePixelRatio || 1);
320
+ const viewWidth = innerWidth * dpr | 0;
321
+ const viewHeight = innerHeight * dpr | 0;
322
+
318
323
  // get main canvas size based on window size
319
- mainCanvasSize.x = min(innerWidth, canvasMaxSize.x);
320
- mainCanvasSize.y = min(innerHeight, canvasMaxSize.y);
324
+ mainCanvasSize.x = min(viewWidth, canvasMaxSize.x);
325
+ mainCanvasSize.y = min(viewHeight, canvasMaxSize.y);
321
326
 
322
327
  // responsive aspect ratio with native resolution
323
- const innerAspect = innerWidth / innerHeight;
328
+ const innerAspect = viewWidth / viewHeight;
324
329
  ASSERT(canvasMinAspect <= canvasMaxAspect);
325
330
  if (canvasMaxAspect && innerAspect > canvasMaxAspect)
326
331
  {
@@ -334,6 +339,17 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
334
339
  const h = mainCanvasSize.x / canvasMinAspect | 0;
335
340
  mainCanvasSize.y = min(h, canvasMaxSize.y);
336
341
  }
342
+
343
+ // set CSS display size so backing store renders at viewport size
344
+ const cssW = (mainCanvasSize.x / dpr | 0) + 'px';
345
+ const cssH = (mainCanvasSize.y / dpr | 0) + 'px';
346
+ mainCanvas.style.width = cssW;
347
+ mainCanvas.style.height = cssH;
348
+ if (glCanvas)
349
+ {
350
+ glCanvas.style.width = cssW;
351
+ glCanvas.style.height = cssH;
352
+ }
337
353
  }
338
354
 
339
355
  // clear main canvas and set size
@@ -553,8 +569,9 @@ function drawEngineLogo(t)
553
569
 
554
570
  // LittleJS Logo and Splash Screen
555
571
  const x = mainContext;
556
- const w = mainCanvas.width = innerWidth;
557
- const h = mainCanvas.height = innerHeight;
572
+ const dpr = canvasPixelRatio ?? (devicePixelRatio || 1);
573
+ const w = mainCanvas.width = innerWidth * dpr;
574
+ const h = mainCanvas.height = innerHeight * dpr;
558
575
  {
559
576
  // background
560
577
  const p3 = percent(t, 1, .8);
@@ -61,7 +61,7 @@ const license = '// LittleJS Engine - MIT License - Copyright 2021 Frank Force\n
61
61
  '// https://github.com/KilledByAPixel/LittleJS\n\n';
62
62
 
63
63
  console.log(asciiArt);
64
- console.log('🚂 Choo Choo... Building LittleJS Engine!');
64
+ console.log('Choo Choo... Building LittleJS Engine!');
65
65
  const startTime = Date.now();
66
66
 
67
67
  try
@@ -266,6 +266,8 @@ function debugShowErrors()
266
266
 
267
267
  function debugInit()
268
268
  {
269
+ if (showEngineVersion)
270
+ console.warn("LittleJS DEBUG build loaded. Use the release build for production.");
269
271
  }
270
272
 
271
273
  function debugUpdate()
package/src/engineDraw.js CHANGED
@@ -110,6 +110,7 @@ function tile(index=new Vector2, size=tileDefaultSize, texture=0, padding=tileDe
110
110
  const textureInfo = typeof texture === 'number' ?
111
111
  textureInfos[texture] : texture;
112
112
  ASSERT(textureInfo instanceof TextureInfo, 'tile texture is not loaded');
113
+ ASSERT(textureInfo.size.x > 0, 'tile texture is not loaded');
113
114
 
114
115
  // get the position of the tile
115
116
  const sizePaddedX = size.x + padding*2;
@@ -59,6 +59,7 @@ export
59
59
  canvasFixedSize,
60
60
  canvasPixelated,
61
61
  tilesPixelated,
62
+ canvasPixelRatio,
62
63
  fontDefault,
63
64
  showSplashScreen,
64
65
  headlessMode,
@@ -105,6 +106,7 @@ export
105
106
  setCanvasFixedSize,
106
107
  setCanvasPixelated,
107
108
  setTilesPixelated,
109
+ setCanvasPixelRatio,
108
110
  setFontDefault,
109
111
  setShowSplashScreen,
110
112
  setHeadlessMode,
@@ -321,7 +321,7 @@ function inputInit()
321
321
  document.addEventListener('mouseup', onMouseUp);
322
322
  document.addEventListener('mousemove', onMouseMove);
323
323
  document.addEventListener('mouseleave', onMouseLeave);
324
- document.addEventListener('wheel', onMouseWheel);
324
+ document.addEventListener('wheel', onMouseWheel, { passive: false });
325
325
  document.addEventListener('contextmenu', onContextMenu);
326
326
  document.addEventListener('blur', onBlur);
327
327
 
@@ -340,7 +340,7 @@ function inputInit()
340
340
  }
341
341
 
342
342
  // try to prevent default browser handling of input
343
- if (!inputPreventDefault || !document.hasFocus() || !e.cancelable) return;
343
+ if (!inputPreventDefault || !e.cancelable || !document.hasFocus()) return;
344
344
 
345
345
  // don't break browser shortcuts
346
346
  if (e.ctrlKey || e.metaKey || e.altKey) return;
@@ -399,7 +399,7 @@ function inputInit()
399
399
  mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
400
400
  mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
401
401
 
402
- if (inputPreventDefault && document.hasFocus() && e.cancelable)
402
+ if (inputPreventDefault && e.cancelable && document.hasFocus())
403
403
  e.preventDefault();
404
404
  }
405
405
  function onMouseUp(e)
@@ -421,7 +421,12 @@ function inputInit()
421
421
  mouseDeltaScreen = mouseDeltaScreen.add(movement);
422
422
  }
423
423
  function onMouseLeave() { mouseInWindow = false; } // mouse moved off window
424
- function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
424
+ function onMouseWheel(e)
425
+ {
426
+ mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
427
+ if (inputPreventDefault && e.cancelable && document.hasFocus())
428
+ e.preventDefault(); // prevent page scrolling
429
+ }
425
430
  function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
426
431
  function onBlur() { inputClear(); } // reset input when focus is lost
427
432
 
@@ -471,7 +476,7 @@ function inputInit()
471
476
  wasTouching = touching;
472
477
 
473
478
  // prevent default handling like copy, magnifier lens, and scrolling
474
- if (inputPreventDefault && document.hasFocus() && e.cancelable)
479
+ if (inputPreventDefault && e.cancelable && document.hasFocus())
475
480
  e.preventDefault();
476
481
 
477
482
  // must return true so the document will get focus
@@ -629,7 +634,13 @@ function inputUpdate()
629
634
  }
630
635
 
631
636
  // return if gamepads are disabled or not supported
632
- if (!gamepadsEnable || !navigator || !navigator.getGamepads) return;
637
+ try {
638
+ // protect against getGamepads disallowed security error
639
+ if (!gamepadsEnable || !navigator?.getGamepads)
640
+ return;
641
+ } catch(e) {
642
+ return;
643
+ }
633
644
 
634
645
  // only poll gamepads when focused or in debug mode
635
646
  if (!debug && !document.hasFocus()) return;
package/src/engineMath.js CHANGED
@@ -327,7 +327,7 @@ function lineTest(posStart, posEnd, testFunction, normal)
327
327
  // get ray direction and length
328
328
  const dx = posEnd.x - posStart.x;
329
329
  const dy = posEnd.y - posStart.y;
330
- const totalLength = hypot(dx, dy);
330
+ const totalLength = (dx*dx + dy*dy)**.5;
331
331
  if (!totalLength) return;
332
332
 
333
333
  // current integer cell we are in
@@ -212,10 +212,10 @@ class EngineObject
212
212
  // if already was touching, try to push away
213
213
  const deltaPos = oldPos.subtract(o.pos);
214
214
  const length = deltaPos.length();
215
- const pushAwayAccel = .001; // push away if already overlapping
216
- const velocity = length < .01 ? randVec2(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
215
+ const pushAwayAccel = .001;
216
+ const velocity = length < .001 ? vec2(0,1) : deltaPos.scale(pushAwayAccel/length);
217
217
  this.velocity = this.velocity.add(velocity);
218
- if (o.mass) // push away if not fixed
218
+ if (o.mass) // push away other object if not fixed
219
219
  o.velocity = o.velocity.subtract(velocity);
220
220
 
221
221
  debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00');
@@ -82,6 +82,13 @@ let canvasPixelated = false;
82
82
  * @memberof Settings */
83
83
  let tilesPixelated = true;
84
84
 
85
+ /** Scale factor applied to the canvas backing store for native-resolution rendering.
86
+ * Pass 1 for no scaling, a number for an explicit ratio, or undefined to track devicePixelRatio each frame.
87
+ * @type {number|undefined}
88
+ * @default
89
+ * @memberof Settings */
90
+ let canvasPixelRatio = 1;
91
+
85
92
  /** Default font used for text rendering
86
93
  * @type {string}
87
94
  * @default
@@ -394,6 +401,12 @@ function setCanvasPixelated(pixelated)
394
401
  * @memberof Settings */
395
402
  function setTilesPixelated(pixelated) { tilesPixelated = pixelated; }
396
403
 
404
+ /** Set the canvas pixel ratio.
405
+ * Pass a number for an explicit ratio, or call with no argument to track devicePixelRatio each frame.
406
+ * @param {number} [pixelRatio]
407
+ * @memberof Settings */
408
+ function setCanvasPixelRatio(pixelRatio) { canvasPixelRatio = pixelRatio; }
409
+
397
410
  /** Set default font used for text rendering
398
411
  * @param {string} font
399
412
  * @memberof Settings */