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.
@@ -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);
@@ -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
@@ -2299,18 +2323,22 @@ let touchInputEnable = true;
2299
2323
 
2300
2324
  /** True if touch gamepad should appear on mobile devices
2301
2325
  * - Supports left analog stick, 4 face buttons and start button (button 9)
2326
+ * - setTouchGamepadButtonCount(1) to use face buttons as right analog stick
2327
+ * - Analog stick buttons 10 and 11 are also activated when virtual sticks are touched
2328
+
2302
2329
  * @type {boolean}
2303
2330
  * @default
2304
2331
  * @memberof Settings */
2305
2332
  let touchGamepadEnable = false;
2306
2333
 
2307
2334
  /** True if touch gamepad should have start button in the center
2335
+ * - Prevents activating if overlappng with virtual stick or buttons if they are enabled
2308
2336
  * - When the game is paused, any touch will press the button
2309
- * - This can function as a way to pause/unpause the game
2310
- * @type {boolean}
2337
+ * - Set size to enable the center button
2338
+ * @type {number}
2311
2339
  * @default
2312
2340
  * @memberof Settings */
2313
- let touchGamepadCenterButton = true;
2341
+ let touchGamepadCenterButtonSize = 300;
2314
2342
 
2315
2343
  /** Number of buttons on touch gamepad (0-4), if 1 also acts as right analog stick
2316
2344
  * @type {number}
@@ -2328,7 +2356,7 @@ let touchGamepadAnalog = true;
2328
2356
  * @type {number}
2329
2357
  * @default
2330
2358
  * @memberof Settings */
2331
- let touchGamepadSize = 99;
2359
+ let touchGamepadSize = 100;
2332
2360
 
2333
2361
  /** Transparency of touch gamepad overlay
2334
2362
  * @type {number}
@@ -2470,6 +2498,12 @@ function setCanvasPixelated(pixelated)
2470
2498
  * @memberof Settings */
2471
2499
  function setTilesPixelated(pixelated) { tilesPixelated = pixelated; }
2472
2500
 
2501
+ /** Set the canvas pixel ratio.
2502
+ * Pass a number for an explicit ratio, or call with no argument to track devicePixelRatio each frame.
2503
+ * @param {number} [pixelRatio]
2504
+ * @memberof Settings */
2505
+ function setCanvasPixelRatio(pixelRatio) { canvasPixelRatio = pixelRatio; }
2506
+
2473
2507
  /** Set default font used for text rendering
2474
2508
  * @param {string} font
2475
2509
  * @memberof Settings */
@@ -2590,11 +2624,12 @@ function setTouchInputEnable(enable) { touchInputEnable = enable; }
2590
2624
  * @memberof Settings */
2591
2625
  function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
2592
2626
 
2593
- /** True if touch gamepad should have start button in the center
2594
- * - This can function as a way to pause/unpause the game
2595
- * @param {boolean} enable
2627
+ /** Set if touch gamepad should have start button in the center
2628
+ * - Set size to enable the center button
2629
+ * - When the game is paused, any touch will press the button
2630
+ * @param {number} size
2596
2631
  * @memberof Settings */
2597
- function setTouchGamepadCenterButton(enable) { touchGamepadCenterButton = enable; }
2632
+ function setTouchGamepadCenterButtonSize(size) { touchGamepadCenterButtonSize = size; }
2598
2633
 
2599
2634
  /** Set number of buttons on touch gamepad (0-4), if 1 also acts as right analog stick
2600
2635
  * @param {number} count
@@ -2892,10 +2927,10 @@ class EngineObject
2892
2927
  // if already was touching, try to push away
2893
2928
  const deltaPos = oldPos.subtract(o.pos);
2894
2929
  const length = deltaPos.length();
2895
- const pushAwayAccel = .001; // push away if already overlapping
2896
- const velocity = length < .01 ? randVec2(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
2930
+ const pushAwayAccel = .001;
2931
+ const velocity = length < .001 ? vec2(0,1) : deltaPos.scale(pushAwayAccel/length);
2897
2932
  this.velocity = this.velocity.add(velocity);
2898
- if (o.mass) // push away if not fixed
2933
+ if (o.mass) // push away other object if not fixed
2899
2934
  o.velocity = o.velocity.subtract(velocity);
2900
2935
 
2901
2936
  debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00');
@@ -3132,6 +3167,7 @@ class EngineObject
3132
3167
  child.parent = this;
3133
3168
  child.localPos = localPos.copy();
3134
3169
  child.localAngle = localAngle;
3170
+ child.updateTransforms();
3135
3171
  return child;
3136
3172
  }
3137
3173
 
@@ -3324,6 +3360,7 @@ function tile(index=new Vector2, size=tileDefaultSize, texture=0, padding=tileDe
3324
3360
  const textureInfo = typeof texture === 'number' ?
3325
3361
  textureInfos[texture] : texture;
3326
3362
  ASSERT(textureInfo instanceof TextureInfo, 'tile texture is not loaded');
3363
+ ASSERT(textureInfo.size.x > 0, 'tile texture is not loaded');
3327
3364
 
3328
3365
  // get the position of the tile
3329
3366
  const sizePaddedX = size.x + padding*2;
@@ -4686,7 +4723,7 @@ function inputInit()
4686
4723
  document.addEventListener('mouseup', onMouseUp);
4687
4724
  document.addEventListener('mousemove', onMouseMove);
4688
4725
  document.addEventListener('mouseleave', onMouseLeave);
4689
- document.addEventListener('wheel', onMouseWheel);
4726
+ document.addEventListener('wheel', onMouseWheel, { passive: false });
4690
4727
  document.addEventListener('contextmenu', onContextMenu);
4691
4728
  document.addEventListener('blur', onBlur);
4692
4729
 
@@ -4705,7 +4742,7 @@ function inputInit()
4705
4742
  }
4706
4743
 
4707
4744
  // try to prevent default browser handling of input
4708
- if (!inputPreventDefault || !document.hasFocus() || !e.cancelable) return;
4745
+ if (!inputPreventDefault || !e.cancelable || !document.hasFocus()) return;
4709
4746
 
4710
4747
  // don't break browser shortcuts
4711
4748
  if (e.ctrlKey || e.metaKey || e.altKey) return;
@@ -4764,7 +4801,7 @@ function inputInit()
4764
4801
  mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
4765
4802
  mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
4766
4803
 
4767
- if (inputPreventDefault && document.hasFocus() && e.cancelable)
4804
+ if (inputPreventDefault && e.cancelable && document.hasFocus())
4768
4805
  e.preventDefault();
4769
4806
  }
4770
4807
  function onMouseUp(e)
@@ -4786,7 +4823,12 @@ function inputInit()
4786
4823
  mouseDeltaScreen = mouseDeltaScreen.add(movement);
4787
4824
  }
4788
4825
  function onMouseLeave() { mouseInWindow = false; } // mouse moved off window
4789
- function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
4826
+ function onMouseWheel(e)
4827
+ {
4828
+ mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
4829
+ if (inputPreventDefault && e.cancelable && document.hasFocus())
4830
+ e.preventDefault(); // prevent page scrolling
4831
+ }
4790
4832
  function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
4791
4833
  function onBlur() { inputClear(); } // reset input when focus is lost
4792
4834
 
@@ -4836,7 +4878,7 @@ function inputInit()
4836
4878
  wasTouching = touching;
4837
4879
 
4838
4880
  // prevent default handling like copy, magnifier lens, and scrolling
4839
- if (inputPreventDefault && document.hasFocus() && e.cancelable)
4881
+ if (inputPreventDefault && e.cancelable && document.hasFocus())
4840
4882
  e.preventDefault();
4841
4883
 
4842
4884
  // must return true so the document will get focus
@@ -4857,7 +4899,7 @@ function inputInit()
4857
4899
  if (touching)
4858
4900
  {
4859
4901
  touchGamepadTimer.set();
4860
- if (touchGamepadCenterButton && !wasTouching && paused)
4902
+ if (touchGamepadCenterButtonSize && !wasTouching && paused)
4861
4903
  {
4862
4904
  // touch anywhere to press start when paused
4863
4905
  touchGamepadButtons[9] = 1;
@@ -4882,6 +4924,7 @@ function inputInit()
4882
4924
  // virtual analog stick
4883
4925
  const delta = touchPos.subtract(stickCenter);
4884
4926
  touchGamepadSticks[0] = delta.scale(2/touchGamepadSize).clampLength();
4927
+ touchGamepadButtons[10] = 1; // also press a button when touching stick
4885
4928
  }
4886
4929
  else if (buttonCenter.distance(touchPos) < touchGamepadSize)
4887
4930
  {
@@ -4890,6 +4933,7 @@ function inputInit()
4890
4933
  // virtual right analog stick
4891
4934
  const delta = touchPos.subtract(buttonCenter);
4892
4935
  touchGamepadSticks[1] = delta.scale(2/touchGamepadSize).clampLength();
4936
+ touchGamepadButtons[11] = 1; // also press a button when touching right stick
4893
4937
  }
4894
4938
  // virtual face buttons
4895
4939
  let button = buttonCenter.subtract(touchPos).direction();
@@ -4906,8 +4950,7 @@ function inputInit()
4906
4950
  if (button < touchGamepadButtonCount)
4907
4951
  touchGamepadButtons[button] = 1;
4908
4952
  }
4909
- else if (touchGamepadCenterButton &&
4910
- startCenter.distance(touchPos) < touchGamepadSize)
4953
+ else if (startCenter.distance(touchPos) < touchGamepadCenterButtonSize)
4911
4954
  {
4912
4955
  // virtual start button in center
4913
4956
  touchGamepadButtons[9] = 1;
@@ -4956,6 +4999,18 @@ function inputUpdate()
4956
4999
  // update touch gamepad if enabled
4957
5000
  if (touchGamepadEnable && isTouchDevice)
4958
5001
  {
5002
+ if (debugGamepads)
5003
+ {
5004
+ const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
5005
+ const buttonCenter = touchGamepadButtonCenter();
5006
+ const startCenter = mainCanvasSize.scale(.5);
5007
+
5008
+ debugCircle(stickCenter, 2*touchGamepadSize, 'cyan', 0, false, true);
5009
+ debugCircle(buttonCenter, 2*touchGamepadSize, 'cyan', 0, false, true);
5010
+ if (touchGamepadCenterButtonSize)
5011
+ debugCircle(startCenter, 2*touchGamepadCenterButtonSize, 'cyan', 0, false, true);
5012
+ }
5013
+
4959
5014
  if (!touchGamepadTimer.isSet()) return;
4960
5015
 
4961
5016
  // read virtual analog stick
@@ -4983,7 +5038,7 @@ function inputUpdate()
4983
5038
 
4984
5039
  // read virtual gamepad buttons
4985
5040
  const data = inputData[1] ?? (inputData[1] = []);
4986
- for (let i=10; i--;)
5041
+ for (let i=12; i--;)
4987
5042
  {
4988
5043
  const wasDown = gamepadIsDown(i,0);
4989
5044
  data[i] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
@@ -4994,7 +5049,13 @@ function inputUpdate()
4994
5049
  }
4995
5050
 
4996
5051
  // return if gamepads are disabled or not supported
4997
- if (!gamepadsEnable || !navigator || !navigator.getGamepads) return;
5052
+ try {
5053
+ // protect against getGamepads disallowed security error
5054
+ if (!gamepadsEnable || !navigator?.getGamepads)
5055
+ return;
5056
+ } catch(e) {
5057
+ return;
5058
+ }
4998
5059
 
4999
5060
  // only poll gamepads when focused or in debug mode
5000
5061
  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.2></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.2></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.2></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.2",
4
4
  "description": "LittleJS - Tiny and Fast HTML5 Game Engine",
5
5
  "main": "dist/littlejs.esm.js",
6
6
  "types": "dist/littlejs.d.ts",
@@ -30,7 +30,8 @@
30
30
  },
31
31
  "homepage": "https://github.com/KilledByAPixel/LittleJS",
32
32
  "scripts": {
33
- "build": "node src/engineBuild.mjs"
33
+ "build": "node src/engineBuild.mjs",
34
+ "test": "node --test --import ./test/setup.mjs \"test/**/*.test.mjs\""
34
35
  },
35
36
  "devDependencies": {
36
37
  "bestzip": "^2.2.1",
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.2';
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,
@@ -79,7 +80,7 @@ export
79
80
  gamepadDirectionEmulateStick,
80
81
  inputWASDEmulateDirection,
81
82
  touchGamepadEnable,
82
- touchGamepadCenterButton,
83
+ touchGamepadCenterButtonSize,
83
84
  touchGamepadAnalog,
84
85
  touchGamepadSize,
85
86
  touchGamepadAlpha,
@@ -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,
@@ -126,7 +128,7 @@ export
126
128
  setGamepadDirectionEmulateStick,
127
129
  setInputWASDEmulateDirection,
128
130
  setTouchGamepadEnable,
129
- setTouchGamepadCenterButton,
131
+ setTouchGamepadCenterButtonSize,
130
132
  setTouchGamepadButtonCount,
131
133
  setTouchGamepadAnalog,
132
134
  setTouchGamepadSize,
@@ -166,10 +168,12 @@ export
166
168
  distanceAngle,
167
169
  lerpAngle,
168
170
  lerp,
171
+ percentLerp,
169
172
  smoothStep,
170
173
  nearestPowerOfTwo,
171
174
  isOverlapping,
172
175
  isIntersecting,
176
+ lineTest,
173
177
  oscillate,
174
178
 
175
179
  // Utilities