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/plugins/box2d.js CHANGED
@@ -119,7 +119,7 @@ class Box2dObject extends EngineObject
119
119
  // draw non-edge fixtures
120
120
  this.getFixtureList().forEach((fixture)=>
121
121
  {
122
- const shape = box2d.castObjectType(fixture.GetShape());
122
+ const shape = box2d.castShapeObject(fixture.GetShape());
123
123
  if (shape.GetType() !== box2d.instance.b2Shape.e_edge)
124
124
  {
125
125
  box2d.drawFixture(fixture, this.pos, this.angle, color, lineColor, lineWidth, useWebGL, context);
@@ -759,7 +759,7 @@ class Box2dJoint
759
759
  constructor(jointDef)
760
760
  {
761
761
  /** @property {Object} - The Box2d joint */
762
- this.box2dJoint = box2d.castObjectType(box2d.world.CreateJoint(jointDef));
762
+ this.box2dJoint = box2d.castJointObject(box2d.world.CreateJoint(jointDef));
763
763
  }
764
764
 
765
765
  /** Destroy this joint */
@@ -1044,7 +1044,7 @@ class Box2dRevoluteJoint extends Box2dJoint
1044
1044
 
1045
1045
  /** Enable/disable the joint limit
1046
1046
  * @param {boolean} [enable] */
1047
- enableLimit(enable=true) { return this.box2dJoint.enableLimit(enable); }
1047
+ enableLimit(enable=true) { return this.box2dJoint.EnableLimit(enable); }
1048
1048
 
1049
1049
  /** Get the lower joint limit
1050
1050
  * @return {number} */
@@ -1202,7 +1202,7 @@ class Box2dPrismaticJoint extends Box2dJoint
1202
1202
 
1203
1203
  /** Enable/disable the joint limit
1204
1204
  * @param {boolean} [enable] */
1205
- enableLimit(enable=true) { return this.box2dJoint.enableLimit(enable); }
1205
+ enableLimit(enable=true) { return this.box2dJoint.EnableLimit(enable); }
1206
1206
 
1207
1207
  /** Get the lower joint limit
1208
1208
  * @return {number} */
@@ -1816,7 +1816,7 @@ class Box2dPlugin
1816
1816
  * @param {CanvasRenderingContext2D} [context] */
1817
1817
  drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, useWebgl, context)
1818
1818
  {
1819
- const shape = box2d.castObjectType(fixture.GetShape());
1819
+ const shape = box2d.castShapeObject(fixture.GetShape());
1820
1820
  switch (shape.GetType())
1821
1821
  {
1822
1822
  case box2d.instance.b2Shape.e_polygon:
@@ -1874,9 +1874,9 @@ class Box2dPlugin
1874
1874
  * @param {Object} o */
1875
1875
  isNull(o) { return !box2d.instance.getPointer(o); }
1876
1876
 
1877
- /** casts a box2d object to its correct type
1877
+ /** casts a box2d object to a shape type
1878
1878
  * @param {Object} o */
1879
- castObjectType(o)
1879
+ castShapeObject(o)
1880
1880
  {
1881
1881
  switch (o.GetType())
1882
1882
  {
@@ -1888,6 +1888,17 @@ class Box2dPlugin
1888
1888
  return box2d.instance.castObject(o, box2d.instance.b2PolygonShape);
1889
1889
  case box2d.instance.b2Shape.e_chain:
1890
1890
  return box2d.instance.castObject(o, box2d.instance.b2ChainShape);
1891
+ }
1892
+
1893
+ ASSERT(false, 'Unknown box2d object type');
1894
+ }
1895
+
1896
+ /** casts a box2d object to a joint type
1897
+ * @param {Object} o */
1898
+ castJointObject(o)
1899
+ {
1900
+ switch (o.GetType())
1901
+ {
1891
1902
  case box2d.instance.e_revoluteJoint:
1892
1903
  return box2d.instance.castObject(o, box2d.instance.b2RevoluteJoint);
1893
1904
  case box2d.instance.e_prismaticJoint:
@@ -27,7 +27,7 @@ export
27
27
  UITile,
28
28
  UIButton,
29
29
  UICheckbox,
30
- UIScrollbar,
30
+ UISlider,
31
31
  UIVideo,
32
32
 
33
33
  // Box2D Physics
@@ -7,7 +7,7 @@
7
7
  * - Buttons
8
8
  * - Checkboxes
9
9
  * - Images
10
- * - Scrollbars
10
+ * - Sliders
11
11
  * - Video
12
12
  * @namespace UISystem
13
13
  */
@@ -1206,7 +1206,7 @@ class UICheckbox extends UIObject
1206
1206
  ASSERT(isString(text), 'ui checkbox must be a string');
1207
1207
  ASSERT(isColor(color), 'ui checkbox color must be a color');
1208
1208
 
1209
- /** @property {boolean} - Current percentage value of this scrollbar 0-1 */
1209
+ /** @property {boolean} - Current percentage value of this slider 0-1 */
1210
1210
  this.checked = checked;
1211
1211
  // set properties
1212
1212
  this.text = text;
@@ -1241,13 +1241,13 @@ class UICheckbox extends UIObject
1241
1241
 
1242
1242
  ///////////////////////////////////////////////////////////////////////////////
1243
1243
  /**
1244
- * UIScrollbar - A UI object that acts as a scrollbar
1244
+ * UISlider - A UI object that acts as a slider or scrollbar
1245
1245
  * @extends UIObject
1246
1246
  * @memberof UISystem
1247
1247
  */
1248
- class UIScrollbar extends UIObject
1248
+ class UISlider extends UIObject
1249
1249
  {
1250
- /** Create a UIScrollbar object
1250
+ /** Create a UISlider object
1251
1251
  * @param {Vector2} [pos]
1252
1252
  * @param {Vector2} [size]
1253
1253
  * @param {number} [value]
@@ -1259,14 +1259,14 @@ class UIScrollbar extends UIObject
1259
1259
  {
1260
1260
  super(pos, size);
1261
1261
 
1262
- ASSERT(isNumber(value), 'ui scrollbar value must be a number');
1263
- ASSERT(isString(text), 'ui scrollbar must be a string');
1264
- ASSERT(isColor(color), 'ui scrollbar color must be a color');
1265
- ASSERT(isColor(handleColor), 'ui scrollbar handleColor must be a color');
1262
+ ASSERT(isNumber(value), 'ui slider value must be a number');
1263
+ ASSERT(isString(text), 'ui slider must be a string');
1264
+ ASSERT(isColor(color), 'ui slider color must be a color');
1265
+ ASSERT(isColor(handleColor), 'ui slider handleColor must be a color');
1266
1266
 
1267
- /** @property {number} - Current percentage value of this scrollbar 0-1 */
1267
+ /** @property {number} - Current percentage value of this slider 0-1 */
1268
1268
  this.value = value;
1269
- /** @property {Color} - Color for the handle part of the scrollbar */
1269
+ /** @property {Color} - Color for the handle part of the slider */
1270
1270
  this.handleColor = handleColor.copy();
1271
1271
  /** @property {boolean} - Should it fill up like a progress bar? */
1272
1272
  this.fillMode = false;
@@ -1285,7 +1285,7 @@ class UIScrollbar extends UIObject
1285
1285
  const oldValue = this.value;
1286
1286
  if (this.isActiveObject())
1287
1287
  {
1288
- // handle horizontal or vertical scrollbar
1288
+ // handle horizontal or vertical slider
1289
1289
  const isHorizontal = this.size.x > this.size.y;
1290
1290
  const handleSize = isHorizontal ? this.size.y : this.size.x;
1291
1291
  const barSize = isHorizontal ? this.size.x : this.size.y;
@@ -1313,7 +1313,7 @@ class UIScrollbar extends UIObject
1313
1313
  {
1314
1314
  super.render();
1315
1315
 
1316
- // handle horizontal or vertical scrollbar
1316
+ // handle horizontal or vertical slider
1317
1317
  const isHorizontal = this.size.x > this.size.y;
1318
1318
  const barWidth = isHorizontal ? this.size.x : this.size.y;
1319
1319
  const handleWidth = isHorizontal ? this.size.y : this.size.x;
@@ -1331,7 +1331,7 @@ class UIScrollbar extends UIObject
1331
1331
  }
1332
1332
  else
1333
1333
  {
1334
- // draw the scrollbar handle
1334
+ // draw the slider handle
1335
1335
  const value = clamp(isHorizontal ? this.value : 1 - this.value);
1336
1336
  const p = (barWidth - handleWidth) * (value - .5);
1337
1337
  const pos = this.pos.add(isHorizontal ? vec2(p, 0) : vec2(0, p));
@@ -1340,7 +1340,7 @@ class UIScrollbar extends UIObject
1340
1340
  uiSystem.drawRect(pos, drawSize, color, this.lineWidth, this.lineColor, this.cornerRadius, this.gradientColor);
1341
1341
  }
1342
1342
 
1343
- // draw the text scaled to fit on the scrollbar
1343
+ // draw the text scaled to fit on the slider
1344
1344
  const textSize = this.getTextSize();
1345
1345
  uiSystem.drawText(this.text, this.pos, textSize,
1346
1346
  this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
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.17.15';
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()
@@ -276,8 +278,6 @@ function debugUpdate()
276
278
  debugOverlay = !debugOverlay;
277
279
  if (debugOverlay)
278
280
  {
279
- if (keyWasPressed('Digit0'))
280
- debugWatermark = !debugWatermark;
281
281
  if (keyWasPressed('Digit1'))
282
282
  debugPhysics = !debugPhysics, debugParticles = false;
283
283
  if (keyWasPressed('Digit2'))
@@ -565,7 +565,7 @@ function debugRenderPost()
565
565
  return;
566
566
  }
567
567
 
568
- if (!debugWatermark) return;
568
+ if (!debugWatermark && !debugOverlay) return;
569
569
 
570
570
  // update fps display
571
571
  mainContext.textAlign = 'right';
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,
@@ -83,6 +84,7 @@ export
83
84
  touchGamepadAnalog,
84
85
  touchGamepadSize,
85
86
  touchGamepadAlpha,
87
+ touchGamepadDisplayTime,
86
88
  vibrateEnable,
87
89
  soundEnable,
88
90
  soundVolume,
@@ -104,6 +106,7 @@ export
104
106
  setCanvasFixedSize,
105
107
  setCanvasPixelated,
106
108
  setTilesPixelated,
109
+ setCanvasPixelRatio,
107
110
  setFontDefault,
108
111
  setShowSplashScreen,
109
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;
@@ -727,10 +738,10 @@ function inputRender()
727
738
  function touchGamepadRender()
728
739
  {
729
740
  if (!touchInputEnable || !isTouchDevice || headlessMode) return;
730
- if (!touchGamepadEnable || !touchGamepadTimer.isSet()) return;
741
+ if (!touchGamepadEnable || !touchGamepadTimer.isSet() && touchGamepadDisplayTime) return;
731
742
 
732
743
  // fade off when not touching or paused
733
- const alpha = percent(touchGamepadTimer.get(), 4, 3);
744
+ const alpha = touchGamepadDisplayTime ? percent(touchGamepadTimer.get(), touchGamepadDisplayTime+1, touchGamepadDisplayTime) : 1;
734
745
  if (!alpha || paused) return;
735
746
 
736
747
  // setup the canvas
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
@@ -260,6 +267,12 @@ let touchGamepadSize = 99;
260
267
  * @memberof Settings */
261
268
  let touchGamepadAlpha = .3;
262
269
 
270
+ /** How long to display the touch gamepad on screen in seconds, set to 0 to always display
271
+ * @type {number}
272
+ * @default
273
+ * @memberof Settings */
274
+ let touchGamepadDisplayTime = 3;
275
+
263
276
  /** Allow vibration hardware if it exists
264
277
  * @type {boolean}
265
278
  * @default
@@ -388,6 +401,12 @@ function setCanvasPixelated(pixelated)
388
401
  * @memberof Settings */
389
402
  function setTilesPixelated(pixelated) { tilesPixelated = pixelated; }
390
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
+
391
410
  /** Set default font used for text rendering
392
411
  * @param {string} font
393
412
  * @memberof Settings */
@@ -534,6 +553,11 @@ function setTouchGamepadSize(size) { touchGamepadSize = size; }
534
553
  * @memberof Settings */
535
554
  function setTouchGamepadAlpha(alpha) { touchGamepadAlpha = alpha; }
536
555
 
556
+ /** Set how long to display the touch gamepad on screen in seconds, set to 0 to always display
557
+ * @param {number} time
558
+ * @memberof Settings */
559
+ function setTouchGamepadDisplayTime(time) { touchGamepadDisplayTime = time; }
560
+
537
561
  /** Set to allow vibration hardware if it exists
538
562
  * @param {boolean} enable
539
563
  * @memberof Settings */