littlejsengine 1.18.26 → 1.18.27

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.26';
38
+ const engineVersion = '1.18.27';
39
39
 
40
40
  /** Frames per second to update
41
41
  * @type {number}
@@ -2346,6 +2346,14 @@ let gamepadsEnable = true;
2346
2346
  * @memberof Settings */
2347
2347
  let gamepadDirectionEmulateStick = true;
2348
2348
 
2349
+ /** If true, axes that do not rest near center are ignored on gamepads without
2350
+ * standard mapping. Steering wheels and flight sticks report pedal and throttle
2351
+ * axes that rest at full deflection, which otherwise reads as a stick held down.
2352
+ * @type {boolean}
2353
+ * @default
2354
+ * @memberof Settings */
2355
+ let gamepadAxisFilterEnable = true;
2356
+
2349
2357
  /** If true the WASD keys are also routed to the direction keys (for better accessibility)
2350
2358
  * @type {boolean}
2351
2359
  * @default
@@ -2684,6 +2692,11 @@ function setGamepadsEnable(enable) { gamepadsEnable = enable; }
2684
2692
  * @memberof Settings */
2685
2693
  function setGamepadDirectionEmulateStick(enable) { gamepadDirectionEmulateStick = enable; }
2686
2694
 
2695
+ /** Set if axes that do not rest near center are ignored on non-standard gamepads
2696
+ * @param {boolean} enable
2697
+ * @memberof Settings */
2698
+ function setGamepadAxisFilterEnable(enable) { gamepadAxisFilterEnable = enable; }
2699
+
2687
2700
  /** Set if true the WASD keys are also routed to the direction keys
2688
2701
  * @param {boolean} enable
2689
2702
  * @memberof Settings */
@@ -3428,6 +3441,12 @@ let workReadCanvas;
3428
3441
  * @memberof Draw */
3429
3442
  let workReadContext;
3430
3443
 
3444
+ /** Extra canvas to composite behind the engine canvases when combining canvases
3445
+ * Set by plugins that render to their own canvas below the LittleJS canvases
3446
+ * @type {HTMLCanvasElement}
3447
+ * @memberof Draw */
3448
+ let backgroundCanvas;
3449
+
3431
3450
  /** The size of the main canvas (and other secondary canvases)
3432
3451
  * @type {Vector2}
3433
3452
  * @memberof Draw */
@@ -4579,6 +4598,13 @@ function setAdditiveBlendMode(additive=true)
4579
4598
  drawContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
4580
4599
  }
4581
4600
 
4601
+ /** Set an extra canvas to composite behind the engine canvases when combining
4602
+ * Plugins that insert their own canvas below the LittleJS canvases should set
4603
+ * this so it appears in screenshots and video capture
4604
+ * @param {HTMLCanvasElement} [canvas]
4605
+ * @memberof Draw */
4606
+ function setBackgroundCanvas(canvas) { backgroundCanvas = canvas; }
4607
+
4582
4608
  /** Combines LittleJS canvases onto the main canvas
4583
4609
  * This is necessary for things like screenshots and video
4584
4610
  * @memberof Draw */
@@ -4591,6 +4617,8 @@ function combineCanvases()
4591
4617
  // leaving workContext.fillStyle transparent can't silently no-op this
4592
4618
  workContext.fillStyle = '#000';
4593
4619
  workContext.fillRect(0,0,w,h);
4620
+ if (backgroundCanvas)
4621
+ workContext.drawImage(backgroundCanvas, 0, 0, w, h);
4594
4622
  glCopyToContext(workContext);
4595
4623
  workContext.drawImage(mainCanvas, 0, 0);
4596
4624
  mainContext.drawImage(workCanvas, 0, 0);
@@ -4989,6 +5017,7 @@ function inputClear()
4989
5017
  touchGamepadStickPointerId.length = 0; // release floating sticks so they re-anchor
4990
5018
  gamepadStickData.length = 0;
4991
5019
  gamepadDpadData.length = 0;
5020
+ gamepadAxisCentered.length = 0;
4992
5021
  }
4993
5022
 
4994
5023
  ///////////////////////////////////////////////////////////////////////////////
@@ -5226,6 +5255,11 @@ const inputData = [[]];
5226
5255
 
5227
5256
  // gamepad internal variables
5228
5257
  const gamepadStickData = [], gamepadDpadData = [], gamepadHadInput = [];
5258
+ // per gamepad, how many consecutive frames each axis has rested inside the
5259
+ // dead zone, used to tell stick axes from axes that rest at full deflection
5260
+ const gamepadAxisCentered = [];
5261
+ // how long an axis must rest inside the dead zone before it counts as a stick
5262
+ const gamepadAxisCenteredFrames = 15;
5229
5263
 
5230
5264
  // touch gamepad internal variables
5231
5265
  const touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadSticks = [];
@@ -5474,7 +5508,7 @@ function inputUpdate()
5474
5508
  // gamepad: any button held or stick moved
5475
5509
  let gamepadActive = false;
5476
5510
  for (let s = gamepadStickCount(); s-- && !gamepadActive;)
5477
- gamepadActive = gamepadStick(s).lengthSquared() > .04;
5511
+ gamepadActive = gamepadStick(s).lengthSquared() > .2;
5478
5512
  for (let b = 17; b-- && !gamepadActive;)
5479
5513
  gamepadActive = gamepadIsDown(b);
5480
5514
 
@@ -5502,12 +5536,12 @@ function inputUpdate()
5502
5536
  // gamepads are updated by engine every frame automatically
5503
5537
  function gamepadsUpdate()
5504
5538
  {
5539
+ const deadZoneMin=.3, deadZoneMax=.8;
5505
5540
  const applyDeadZones = (v)=>
5506
5541
  {
5507
- const min=.3, max=.8;
5508
5542
  const deadZone = (v)=>
5509
- v > min ? percent(v, min, max) :
5510
- v < -min ? -percent(-v, min, max) : 0;
5543
+ v > deadZoneMin ? percent(v, deadZoneMin, deadZoneMax) :
5544
+ v < -deadZoneMin ? -percent(-v, deadZoneMin, deadZoneMax) : 0;
5511
5545
  return vec2(deadZone(v.x), deadZone(-v.y)).clampLength();
5512
5546
  };
5513
5547
 
@@ -5591,6 +5625,7 @@ function inputUpdate()
5591
5625
  gamepadStickData[i] = undefined;
5592
5626
  gamepadDpadData[i] = undefined;
5593
5627
  gamepadHadInput[i] = undefined;
5628
+ gamepadAxisCentered[i] = undefined;
5594
5629
  continue;
5595
5630
  }
5596
5631
 
@@ -5599,8 +5634,30 @@ function inputUpdate()
5599
5634
  const dpad = gamepadDpadData[i] ?? (gamepadDpadData[i] = vec2());
5600
5635
 
5601
5636
  // read analog sticks
5637
+ // gamepads without standard mapping (steering wheels, flight sticks)
5638
+ // can report axes that rest at full deflection instead of center,
5639
+ // which would otherwise read as a stick held down forever, so only
5640
+ // trust an axis once it has rested inside the dead zone for a moment
5641
+ const isStandard = gamepad.mapping === 'standard';
5642
+ const centered = gamepadAxisCentered[i] ?? (gamepadAxisCentered[i] = []);
5643
+ const readAxis = (j)=>
5644
+ {
5645
+ const v = gamepad.axes[j];
5646
+ if (isStandard && j < 4)
5647
+ return v; // spec guarantees axes 0-3 are the two sticks
5648
+ if (!gamepadAxisFilterEnable)
5649
+ return v;
5650
+
5651
+ // once an axis has proven it rests at center it stays trusted,
5652
+ // otherwise moving it would immediately disqualify it again
5653
+ const frames = centered[j] | 0;
5654
+ if (frames > gamepadAxisCenteredFrames)
5655
+ return v;
5656
+ centered[j] = abs(v) < deadZoneMin ? frames + 1 : 0;
5657
+ return 0;
5658
+ };
5602
5659
  for (let j = 0; j < gamepad.axes.length-1; j+=2)
5603
- sticks[j>>1] = applyDeadZones(vec2(gamepad.axes[j],gamepad.axes[j+1]));
5660
+ sticks[j>>1] = applyDeadZones(vec2(readAxis(j), readAxis(j+1)));
5604
5661
 
5605
5662
  // read buttons
5606
5663
  let hadInput = false;
@@ -16117,6 +16174,9 @@ class ThreeJSPlugin
16117
16174
  rootElement.insertBefore(threeCanvas, rootElement.firstChild);
16118
16175
  threeCanvas.style.cssText = mainCanvas.style.cssText;
16119
16176
 
16177
+ // composite the 3D canvas into screenshots and video capture
16178
+ setBackgroundCanvas(threeCanvas);
16179
+
16120
16180
  // render automatically each frame after the engine renders
16121
16181
  engineAddPlugin(undefined, ()=> this.render());
16122
16182
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "littlejsengine",
3
- "version": "1.18.26",
3
+ "version": "1.18.27",
4
4
  "description": "LittleJS - Tiny and Fast HTML5 Game Engine",
5
5
  "main": "dist/littlejs.esm.js",
6
6
  "types": "dist/littlejs.d.ts",
@@ -39,6 +39,7 @@
39
39
  "scripts": {
40
40
  "build": "node src/engineBuild.mjs",
41
41
  "build-docs": "node tools/buildDocs.mjs",
42
+ "build-release": "node tools/buildRelease.mjs",
42
43
  "test": "node --test --import ./test/setup.mjs \"test/**/*.test.mjs\""
43
44
  },
44
45
  "engines": {
@@ -57,6 +57,9 @@ class ThreeJSPlugin
57
57
  rootElement.insertBefore(threeCanvas, rootElement.firstChild);
58
58
  threeCanvas.style.cssText = mainCanvas.style.cssText;
59
59
 
60
+ // composite the 3D canvas into screenshots and video capture
61
+ setBackgroundCanvas(threeCanvas);
62
+
60
63
  // render automatically each frame after the engine renders
61
64
  engineAddPlugin(undefined, ()=> this.render());
62
65
  }
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.26';
35
+ const engineVersion = '1.18.27';
36
36
 
37
37
  /** Frames per second to update
38
38
  * @type {number}
package/src/engineDraw.js CHANGED
@@ -57,6 +57,12 @@ let workReadCanvas;
57
57
  * @memberof Draw */
58
58
  let workReadContext;
59
59
 
60
+ /** Extra canvas to composite behind the engine canvases when combining canvases
61
+ * Set by plugins that render to their own canvas below the LittleJS canvases
62
+ * @type {HTMLCanvasElement}
63
+ * @memberof Draw */
64
+ let backgroundCanvas;
65
+
60
66
  /** The size of the main canvas (and other secondary canvases)
61
67
  * @type {Vector2}
62
68
  * @memberof Draw */
@@ -1208,6 +1214,13 @@ function setAdditiveBlendMode(additive=true)
1208
1214
  drawContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
1209
1215
  }
1210
1216
 
1217
+ /** Set an extra canvas to composite behind the engine canvases when combining
1218
+ * Plugins that insert their own canvas below the LittleJS canvases should set
1219
+ * this so it appears in screenshots and video capture
1220
+ * @param {HTMLCanvasElement} [canvas]
1221
+ * @memberof Draw */
1222
+ function setBackgroundCanvas(canvas) { backgroundCanvas = canvas; }
1223
+
1211
1224
  /** Combines LittleJS canvases onto the main canvas
1212
1225
  * This is necessary for things like screenshots and video
1213
1226
  * @memberof Draw */
@@ -1220,6 +1233,8 @@ function combineCanvases()
1220
1233
  // leaving workContext.fillStyle transparent can't silently no-op this
1221
1234
  workContext.fillStyle = '#000';
1222
1235
  workContext.fillRect(0,0,w,h);
1236
+ if (backgroundCanvas)
1237
+ workContext.drawImage(backgroundCanvas, 0, 0, w, h);
1223
1238
  glCopyToContext(workContext);
1224
1239
  workContext.drawImage(mainCanvas, 0, 0);
1225
1240
  mainContext.drawImage(workCanvas, 0, 0);
@@ -84,6 +84,7 @@ export
84
84
  glCircleSides,
85
85
  gamepadsEnable,
86
86
  gamepadDirectionEmulateStick,
87
+ gamepadAxisFilterEnable,
87
88
  inputWASDEmulateDirection,
88
89
  touchInputEnable,
89
90
  touchGamepadEnable,
@@ -140,6 +141,7 @@ export
140
141
  setTouchInputEnable,
141
142
  setGamepadsEnable,
142
143
  setGamepadDirectionEmulateStick,
144
+ setGamepadAxisFilterEnable,
143
145
  setInputWASDEmulateDirection,
144
146
  setTouchGamepadEnable,
145
147
  setTouchGamepadPassthrough,
@@ -255,6 +257,7 @@ export
255
257
  workContext,
256
258
  workReadCanvas,
257
259
  workReadContext,
260
+ backgroundCanvas,
258
261
  mainCanvasSize,
259
262
  textureInfos,
260
263
  drawCount,
@@ -280,6 +283,7 @@ export
280
283
  drawText,
281
284
  drawTextScreen,
282
285
  setAdditiveBlendMode,
286
+ setBackgroundCanvas,
283
287
  combineCanvases,
284
288
  engineImageFont,
285
289
  ImageFont,
@@ -124,6 +124,7 @@ function inputClear()
124
124
  touchGamepadStickPointerId.length = 0; // release floating sticks so they re-anchor
125
125
  gamepadStickData.length = 0;
126
126
  gamepadDpadData.length = 0;
127
+ gamepadAxisCentered.length = 0;
127
128
  }
128
129
 
129
130
  ///////////////////////////////////////////////////////////////////////////////
@@ -361,6 +362,11 @@ const inputData = [[]];
361
362
 
362
363
  // gamepad internal variables
363
364
  const gamepadStickData = [], gamepadDpadData = [], gamepadHadInput = [];
365
+ // per gamepad, how many consecutive frames each axis has rested inside the
366
+ // dead zone, used to tell stick axes from axes that rest at full deflection
367
+ const gamepadAxisCentered = [];
368
+ // how long an axis must rest inside the dead zone before it counts as a stick
369
+ const gamepadAxisCenteredFrames = 15;
364
370
 
365
371
  // touch gamepad internal variables
366
372
  const touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadSticks = [];
@@ -609,7 +615,7 @@ function inputUpdate()
609
615
  // gamepad: any button held or stick moved
610
616
  let gamepadActive = false;
611
617
  for (let s = gamepadStickCount(); s-- && !gamepadActive;)
612
- gamepadActive = gamepadStick(s).lengthSquared() > .04;
618
+ gamepadActive = gamepadStick(s).lengthSquared() > .2;
613
619
  for (let b = 17; b-- && !gamepadActive;)
614
620
  gamepadActive = gamepadIsDown(b);
615
621
 
@@ -637,12 +643,12 @@ function inputUpdate()
637
643
  // gamepads are updated by engine every frame automatically
638
644
  function gamepadsUpdate()
639
645
  {
646
+ const deadZoneMin=.3, deadZoneMax=.8;
640
647
  const applyDeadZones = (v)=>
641
648
  {
642
- const min=.3, max=.8;
643
649
  const deadZone = (v)=>
644
- v > min ? percent(v, min, max) :
645
- v < -min ? -percent(-v, min, max) : 0;
650
+ v > deadZoneMin ? percent(v, deadZoneMin, deadZoneMax) :
651
+ v < -deadZoneMin ? -percent(-v, deadZoneMin, deadZoneMax) : 0;
646
652
  return vec2(deadZone(v.x), deadZone(-v.y)).clampLength();
647
653
  };
648
654
 
@@ -726,6 +732,7 @@ function inputUpdate()
726
732
  gamepadStickData[i] = undefined;
727
733
  gamepadDpadData[i] = undefined;
728
734
  gamepadHadInput[i] = undefined;
735
+ gamepadAxisCentered[i] = undefined;
729
736
  continue;
730
737
  }
731
738
 
@@ -734,8 +741,30 @@ function inputUpdate()
734
741
  const dpad = gamepadDpadData[i] ?? (gamepadDpadData[i] = vec2());
735
742
 
736
743
  // read analog sticks
744
+ // gamepads without standard mapping (steering wheels, flight sticks)
745
+ // can report axes that rest at full deflection instead of center,
746
+ // which would otherwise read as a stick held down forever, so only
747
+ // trust an axis once it has rested inside the dead zone for a moment
748
+ const isStandard = gamepad.mapping === 'standard';
749
+ const centered = gamepadAxisCentered[i] ?? (gamepadAxisCentered[i] = []);
750
+ const readAxis = (j)=>
751
+ {
752
+ const v = gamepad.axes[j];
753
+ if (isStandard && j < 4)
754
+ return v; // spec guarantees axes 0-3 are the two sticks
755
+ if (!gamepadAxisFilterEnable)
756
+ return v;
757
+
758
+ // once an axis has proven it rests at center it stays trusted,
759
+ // otherwise moving it would immediately disqualify it again
760
+ const frames = centered[j] | 0;
761
+ if (frames > gamepadAxisCenteredFrames)
762
+ return v;
763
+ centered[j] = abs(v) < deadZoneMin ? frames + 1 : 0;
764
+ return 0;
765
+ };
737
766
  for (let j = 0; j < gamepad.axes.length-1; j+=2)
738
- sticks[j>>1] = applyDeadZones(vec2(gamepad.axes[j],gamepad.axes[j+1]));
767
+ sticks[j>>1] = applyDeadZones(vec2(readAxis(j), readAxis(j+1)));
739
768
 
740
769
  // read buttons
741
770
  let hadInput = false;
@@ -236,6 +236,14 @@ let gamepadsEnable = true;
236
236
  * @memberof Settings */
237
237
  let gamepadDirectionEmulateStick = true;
238
238
 
239
+ /** If true, axes that do not rest near center are ignored on gamepads without
240
+ * standard mapping. Steering wheels and flight sticks report pedal and throttle
241
+ * axes that rest at full deflection, which otherwise reads as a stick held down.
242
+ * @type {boolean}
243
+ * @default
244
+ * @memberof Settings */
245
+ let gamepadAxisFilterEnable = true;
246
+
239
247
  /** If true the WASD keys are also routed to the direction keys (for better accessibility)
240
248
  * @type {boolean}
241
249
  * @default
@@ -574,6 +582,11 @@ function setGamepadsEnable(enable) { gamepadsEnable = enable; }
574
582
  * @memberof Settings */
575
583
  function setGamepadDirectionEmulateStick(enable) { gamepadDirectionEmulateStick = enable; }
576
584
 
585
+ /** Set if axes that do not rest near center are ignored on non-standard gamepads
586
+ * @param {boolean} enable
587
+ * @memberof Settings */
588
+ function setGamepadAxisFilterEnable(enable) { gamepadAxisFilterEnable = enable; }
589
+
577
590
  /** Set if true the WASD keys are also routed to the direction keys
578
591
  * @param {boolean} enable
579
592
  * @memberof Settings */