littlejsengine 1.18.4 → 1.18.7

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.
Files changed (177) hide show
  1. package/COPYRIGHT.txt +38 -0
  2. package/FAQ.md +633 -0
  3. package/README.md +19 -1
  4. package/dist/littlejs.d.ts +349 -54
  5. package/dist/littlejs.esm.js +1469 -448
  6. package/dist/littlejs.esm.min.js +1 -1
  7. package/dist/littlejs.js +1438 -436
  8. package/dist/littlejs.min.js +1 -1
  9. package/dist/littlejs.release.js +1438 -436
  10. package/package.json +8 -1
  11. package/plugins/box2d.js +32 -7
  12. package/{src/engineMedals.js → plugins/medalSystem.js} +248 -196
  13. package/plugins/pathFinder.js +758 -0
  14. package/plugins/pluginExport.js +19 -0
  15. package/plugins/tween.js +1 -1
  16. package/plugins/tweenSystem.js +509 -0
  17. package/plugins/uiSystem.js +150 -11
  18. package/src/engine.js +2 -141
  19. package/src/engineAudio.js +8 -5
  20. package/src/engineBuild.mjs +4 -2
  21. package/src/engineDebug.js +1 -1
  22. package/src/engineDraw.js +21 -3
  23. package/src/engineExport.js +12 -12
  24. package/src/engineInput.js +1 -1
  25. package/src/engineLogo.js +146 -0
  26. package/src/engineMath.js +10 -3
  27. package/src/engineObject.js +11 -5
  28. package/src/engineParticles.js +6 -3
  29. package/src/engineRelease.js +1 -1
  30. package/src/engineSettings.js +0 -47
  31. package/src/engineUtilities.js +7 -2
  32. package/src/engineWebGL.js +34 -7
  33. package/src/jsconfig.json +3 -1
  34. package/.github/workflows/test.yml +0 -17
  35. package/AI.md +0 -173
  36. package/CLAUDE.md +0 -1
  37. package/examples/box2d/game.js +0 -192
  38. package/examples/box2d/gameObjects.js +0 -564
  39. package/examples/box2d/index.html +0 -9
  40. package/examples/box2d/scenes.js +0 -194
  41. package/examples/box2d/tiles.png +0 -0
  42. package/examples/breakout/game.js +0 -175
  43. package/examples/breakout/gameObjects.js +0 -147
  44. package/examples/breakout/index.html +0 -8
  45. package/examples/breakout/tiles.png +0 -0
  46. package/examples/breakoutTutorial/README.md +0 -521
  47. package/examples/breakoutTutorial/game.js +0 -191
  48. package/examples/breakoutTutorial/images/1.png +0 -0
  49. package/examples/breakoutTutorial/images/10.png +0 -0
  50. package/examples/breakoutTutorial/images/11.png +0 -0
  51. package/examples/breakoutTutorial/images/2.png +0 -0
  52. package/examples/breakoutTutorial/images/3.png +0 -0
  53. package/examples/breakoutTutorial/images/4.png +0 -0
  54. package/examples/breakoutTutorial/images/5.png +0 -0
  55. package/examples/breakoutTutorial/images/6.png +0 -0
  56. package/examples/breakoutTutorial/images/7.png +0 -0
  57. package/examples/breakoutTutorial/images/8.png +0 -0
  58. package/examples/breakoutTutorial/images/9.png +0 -0
  59. package/examples/breakoutTutorial/index.html +0 -8
  60. package/examples/electron/build.mjs +0 -127
  61. package/examples/electron/electron.js +0 -35
  62. package/examples/electron/game.js +0 -54
  63. package/examples/electron/index.html +0 -13
  64. package/examples/electron/package.json +0 -5
  65. package/examples/electron/tiles.png +0 -0
  66. package/examples/empty/game.js +0 -51
  67. package/examples/empty/index.html +0 -5
  68. package/examples/empty/tiles.png +0 -0
  69. package/examples/favicon.png +0 -0
  70. package/examples/games.jpg +0 -0
  71. package/examples/htmlMenu/game.js +0 -82
  72. package/examples/htmlMenu/index.html +0 -45
  73. package/examples/htmlMenu/tiles.png +0 -0
  74. package/examples/index.html +0 -62
  75. package/examples/logo.png +0 -0
  76. package/examples/logo2.png +0 -0
  77. package/examples/module/build.mjs +0 -124
  78. package/examples/module/game.js +0 -132
  79. package/examples/module/index.html +0 -10
  80. package/examples/module/tiles.png +0 -0
  81. package/examples/particles/index.html +0 -426
  82. package/examples/particles/tiles.png +0 -0
  83. package/examples/platformer/data/gameLevelData.tmx +0 -143
  84. package/examples/platformer/data/gameLevelData.tsx +0 -4
  85. package/examples/platformer/game.js +0 -147
  86. package/examples/platformer/gameCharacter.js +0 -309
  87. package/examples/platformer/gameEffects.js +0 -278
  88. package/examples/platformer/gameLevel.js +0 -205
  89. package/examples/platformer/gameLevelData.json +0 -171
  90. package/examples/platformer/gameObjects.js +0 -378
  91. package/examples/platformer/gamePlayer.js +0 -35
  92. package/examples/platformer/index.html +0 -9
  93. package/examples/platformer/tiles.png +0 -0
  94. package/examples/platformer/tilesLevel.png +0 -0
  95. package/examples/puzzle/game.js +0 -331
  96. package/examples/puzzle/index.html +0 -8
  97. package/examples/puzzle/tiles.png +0 -0
  98. package/examples/screenshot.jpg +0 -0
  99. package/examples/shorts/animation.js +0 -18
  100. package/examples/shorts/base.html +0 -54
  101. package/examples/shorts/blending.js +0 -14
  102. package/examples/shorts/box2d.js +0 -51
  103. package/examples/shorts/box2dCar.js +0 -59
  104. package/examples/shorts/box2dPool.js +0 -107
  105. package/examples/shorts/box2dTileLayer.js +0 -48
  106. package/examples/shorts/cameraDrag.js +0 -22
  107. package/examples/shorts/clock.js +0 -21
  108. package/examples/shorts/colors.js +0 -25
  109. package/examples/shorts/debugDraw.js +0 -37
  110. package/examples/shorts/empty.js +0 -30
  111. package/examples/shorts/flappyGame.js +0 -55
  112. package/examples/shorts/fontImage.js +0 -17
  113. package/examples/shorts/fps.js +0 -90
  114. package/examples/shorts/helloWorld.js +0 -11
  115. package/examples/shorts/hillGlideGame.js +0 -63
  116. package/examples/shorts/input.js +0 -64
  117. package/examples/shorts/landerGame.js +0 -57
  118. package/examples/shorts/maze.js +0 -48
  119. package/examples/shorts/medals.js +0 -51
  120. package/examples/shorts/music.js +0 -78
  121. package/examples/shorts/musicPlayer.js +0 -137
  122. package/examples/shorts/nineSlice.js +0 -40
  123. package/examples/shorts/parallax.js +0 -72
  124. package/examples/shorts/particles.js +0 -28
  125. package/examples/shorts/piano.js +0 -44
  126. package/examples/shorts/platformer.js +0 -45
  127. package/examples/shorts/pongGame.js +0 -41
  128. package/examples/shorts/postProcess.js +0 -62
  129. package/examples/shorts/sequencer.js +0 -124
  130. package/examples/shorts/shader.js +0 -29
  131. package/examples/shorts/shapes.js +0 -21
  132. package/examples/shorts/slidingPuzzle.js +0 -52
  133. package/examples/shorts/song.mp3 +0 -0
  134. package/examples/shorts/sound.js +0 -36
  135. package/examples/shorts/spaceGame.js +0 -58
  136. package/examples/shorts/spriteAtlas.js +0 -31
  137. package/examples/shorts/starfield.js +0 -15
  138. package/examples/shorts/texture.js +0 -16
  139. package/examples/shorts/textureWrapped.js +0 -12
  140. package/examples/shorts/tileLayer.js +0 -48
  141. package/examples/shorts/tileRaycast.js +0 -39
  142. package/examples/shorts/tiles.png +0 -0
  143. package/examples/shorts/tiltedView.js +0 -63
  144. package/examples/shorts/timers.js +0 -53
  145. package/examples/shorts/topDown.js +0 -38
  146. package/examples/shorts/tween.js +0 -24
  147. package/examples/shorts/uiSystem.js +0 -61
  148. package/examples/shorts/video.webm +0 -0
  149. package/examples/shorts/videoPlayer.js +0 -34
  150. package/examples/shorts.js +0 -743
  151. package/examples/starter/build.bat +0 -7
  152. package/examples/starter/build.mjs +0 -126
  153. package/examples/starter/game.js +0 -137
  154. package/examples/starter/index.html +0 -35
  155. package/examples/starter/tiles.png +0 -0
  156. package/examples/stress/index.html +0 -173
  157. package/examples/style.css +0 -150
  158. package/examples/tweenSystem/game.js +0 -171
  159. package/examples/tweenSystem/index.html +0 -10
  160. package/examples/tweenSystem/tiles.png +0 -0
  161. package/examples/typescript/build.mjs +0 -60
  162. package/examples/typescript/game.js +0 -100
  163. package/examples/typescript/game.ts +0 -132
  164. package/examples/typescript/index.html +0 -10
  165. package/examples/typescript/tiles.png +0 -0
  166. package/examples/typescript/tsconfig.json +0 -17
  167. package/examples/uiSystem/game.js +0 -139
  168. package/examples/uiSystem/index.html +0 -10
  169. package/examples/uiSystem/tiles.png +0 -0
  170. package/jsconfig.json +0 -12
  171. package/plugins/desktop.ini +0 -2
  172. package/reference.md +0 -448
  173. package/test/math.test.mjs +0 -774
  174. package/test/setup.mjs +0 -22
  175. package/test/smoke.test.mjs +0 -274
  176. package/test/tween.test.mjs +0 -576
  177. package/test/util.test.mjs +0 -80
@@ -118,16 +118,13 @@ class UISystemPlugin
118
118
  this.lastHoverObject = undefined;
119
119
  /** @property {UIObject} - Current confirm menu being shown */
120
120
  this.confirmDialog = undefined;
121
- /** @property {UIObject} - Object to send keyboard input to */
122
- this.keyInputObject = undefined;
121
+ /** @private */
122
+ this._keyInputObject = undefined;
123
+ /** @private */
124
+ this._onKeyDown = (e) => this._keyInputObject?.onKeyDown(e);
123
125
 
124
126
  engineAddPlugin(uiUpdate, uiRender);
125
127
 
126
- // key down handler
127
- function onKeyDown(e)
128
- { uiSystem.keyInputObject?.onKeyDown(e); }
129
- document.addEventListener('keydown', onKeyDown);
130
-
131
128
  // set object position in parent space
132
129
  function updateTransforms(o)
133
130
  {
@@ -469,8 +466,23 @@ class UISystemPlugin
469
466
  return p;
470
467
  }
471
468
 
469
+ /** Object to send keyboard input to (typically a UITextInput).
470
+ * The document keydown listener is only attached while this is set,
471
+ * so games that never use text input pay no event-handling cost.
472
+ * @type {UIObject} */
473
+ get keyInputObject() { return this._keyInputObject; }
474
+ set keyInputObject(obj)
475
+ {
476
+ const had = !!this._keyInputObject;
477
+ this._keyInputObject = obj;
478
+ if (!had && obj)
479
+ document.addEventListener('keydown', this._onKeyDown);
480
+ else if (had && !obj)
481
+ document.removeEventListener('keydown', this._onKeyDown);
482
+ }
483
+
472
484
  /** Destroy and remove all objects
473
- * @memberof Engine */
485
+ * @memberof UISystem */
474
486
  destroyObjects()
475
487
  {
476
488
  for (const o of this.uiObjects)
@@ -893,15 +905,13 @@ class UIObject
893
905
  * @return {string} */
894
906
  toString()
895
907
  {
896
- if (!debug) return;
897
-
898
908
  let text = 'type = ' + this.constructor.name;
899
909
  if (this.text)
900
910
  text += '\ntext = ' + this.text;
901
911
  if (this.pos.x || this.pos.y)
902
912
  text += '\npos = ' + this.pos;
903
913
  if (this.localPos.x || this.localPos.y)
904
- text += '\localPos = ' + this.localPos;
914
+ text += '\nlocalPos = ' + this.localPos;
905
915
  if (this.size.x || this.size.y)
906
916
  text += '\nsize = ' + this.size;
907
917
  if (this.color)
@@ -1495,4 +1505,133 @@ class UIVideo extends UIObject
1495
1505
  this.video.remove();
1496
1506
  super.destroy();
1497
1507
  }
1508
+ }
1509
+
1510
+ ///////////////////////////////////////////////////////////////////////////////
1511
+ /**
1512
+ * UILayout - A container that auto-arranges children in a vertical list, horizontal list, or grid
1513
+ * - Set columns to 1 for a vertical list (default)
1514
+ * - Set columns to children.length for a horizontal list
1515
+ * - Set columns to N (1 < N < children.length) for a grid with N columns
1516
+ * - Per-child sizing: each row's height = max child.size.y in that row, each column's width = max child.size.x in that column
1517
+ * - Children are positioned centered in their cell
1518
+ * - Container auto-sizes to fit children plus padding
1519
+ * @extends UIObject
1520
+ * @memberof UISystem
1521
+ */
1522
+ class UILayout extends UIObject
1523
+ {
1524
+ /** Create a UILayout container that auto-arranges children
1525
+ * @param {Vector2} [pos]
1526
+ * @param {number} [columns=1] - Number of columns (1 = vertical list)
1527
+ * @param {number} [gap=10] - Space between children
1528
+ * @param {number} [padding=10] - Space between container border and children
1529
+ * @param {boolean} [transparent=false] - If true, draws no background, outline, or shadow
1530
+ */
1531
+ constructor(pos, columns=1, gap=10, padding=10, transparent=false)
1532
+ {
1533
+ super(pos);
1534
+
1535
+ ASSERT(isNumber(columns) && columns >= 1, 'ui layout columns must be a number >= 1');
1536
+ ASSERT(isNumber(gap), 'ui layout gap must be a number');
1537
+ ASSERT(isNumber(padding), 'ui layout padding must be a number');
1538
+
1539
+ /** @property {number} - Number of columns in the layout */
1540
+ this.columns = columns;
1541
+ /** @property {number} - Space between children */
1542
+ this.gap = gap;
1543
+ /** @property {number} - Space between container border and children */
1544
+ this.padding = padding;
1545
+
1546
+ if (transparent)
1547
+ {
1548
+ // pure positioning helper - skip background, outline, and shadow
1549
+ this.color = CLEAR_BLACK;
1550
+ this.gradientColor = undefined;
1551
+ this.lineWidth = 0;
1552
+ this.shadowColor = CLEAR_BLACK;
1553
+ }
1554
+ this.relayout();
1555
+ }
1556
+
1557
+ /** Add a child UIObject and re-layout
1558
+ * @param {UIObject} child
1559
+ * @return {UIObject} The child object added */
1560
+ addChild(child)
1561
+ {
1562
+ super.addChild(child);
1563
+ this.relayout();
1564
+ return child;
1565
+ }
1566
+
1567
+ /** Remove a child UIObject and re-layout
1568
+ * @param {UIObject} child */
1569
+ removeChild(child)
1570
+ {
1571
+ super.removeChild(child);
1572
+ this.relayout();
1573
+ }
1574
+
1575
+ /** Recompute child positions and container size based on per-child sizes.
1576
+ * Called automatically by addChild and removeChild. Call manually if you
1577
+ * mutate a child's size or change columns, gap, or padding. */
1578
+ relayout()
1579
+ {
1580
+ const n = this.children.length;
1581
+ if (!n)
1582
+ {
1583
+ this.size = vec2(this.padding * 2);
1584
+ return;
1585
+ }
1586
+
1587
+ const cols = this.columns;
1588
+ const rows = ceil(n / cols);
1589
+ const colWidths = new Array(cols).fill(0);
1590
+ const rowHeights = new Array(rows).fill(0);
1591
+
1592
+ // first pass: compute column widths and row heights from child sizes
1593
+ for (let i = 0; i < n; ++i)
1594
+ {
1595
+ const col = i % cols;
1596
+ const row = floor(i / cols);
1597
+ const child = this.children[i];
1598
+ colWidths[col] = max(colWidths[col], child.size.x);
1599
+ rowHeights[row] = max(rowHeights[row], child.size.y);
1600
+ }
1601
+
1602
+ // total content size (sum of column widths/row heights plus gaps between them)
1603
+ let contentWidth = this.gap * (cols - 1);
1604
+ for (const w of colWidths) contentWidth += w;
1605
+ let contentHeight = this.gap * (rows - 1);
1606
+ for (const h of rowHeights) contentHeight += h;
1607
+
1608
+ // cumulative column/row offsets so positioning is O(n) not O(n^2)
1609
+ const colOffsets = new Array(cols);
1610
+ let xAcc = 0;
1611
+ for (let c = 0; c < cols; ++c)
1612
+ {
1613
+ colOffsets[c] = xAcc;
1614
+ xAcc += colWidths[c];
1615
+ }
1616
+ const rowOffsets = new Array(rows);
1617
+ let yAcc = 0;
1618
+ for (let r = 0; r < rows; ++r)
1619
+ {
1620
+ rowOffsets[r] = yAcc;
1621
+ yAcc += rowHeights[r];
1622
+ }
1623
+
1624
+ // second pass: position each child centered in its cell
1625
+ for (let i = 0; i < n; ++i)
1626
+ {
1627
+ const col = i % cols;
1628
+ const row = floor(i / cols);
1629
+ const x = -contentWidth/2 + colOffsets[col] + this.gap * col + colWidths[col] / 2;
1630
+ const y = -contentHeight/2 + rowOffsets[row] + this.gap * row + rowHeights[row] / 2;
1631
+ this.children[i].localPos = vec2(x, y);
1632
+ }
1633
+
1634
+ // container size = content + padding on all sides
1635
+ this.size = vec2(contentWidth + this.padding * 2, contentHeight + this.padding * 2);
1636
+ }
1498
1637
  }
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.4';
35
+ const engineVersion = '1.18.7';
36
36
 
37
37
  /** Frames per second to update
38
38
  * @type {number}
@@ -204,7 +204,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
204
204
  const combinedScale = timeScale * debugScale;
205
205
  frameTimeDeltaMS *= combinedScale;
206
206
  frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
207
- if (combinedScale <= 1)
207
+ if (debugScale <= 1)
208
208
  frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp min framerate
209
209
 
210
210
  let wasUpdated = false;
@@ -562,143 +562,4 @@ function engineObjectsRaycast(start, end, objects=engineObjects)
562
562
 
563
563
  debugRaycast && debugLine(start, end, hitObjects.length ? '#f00' : '#00f', .02);
564
564
  return hitObjects;
565
- }
566
-
567
- ///////////////////////////////////////////////////////////////////////////////
568
- function drawEngineLogo(t)
569
- {
570
- const blackAndWhite = 0;
571
- const showName = 1;
572
-
573
- // LittleJS Logo and Splash Screen
574
- const x = mainContext;
575
- const dpr = canvasPixelRatio ?? (devicePixelRatio || 1);
576
- const w = mainCanvas.width = innerWidth * dpr;
577
- const h = mainCanvas.height = innerHeight * dpr;
578
- {
579
- // background
580
- const p3 = percent(t, 1, .8);
581
- const p4 = percent(t, 0, .5);
582
- const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.6);
583
- g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
584
- g.addColorStop(1,hsl(0,0,0,p3).toString());
585
- x.save();
586
- x.fillStyle = g;
587
- x.fillRect(0,0,w,h);
588
- }
589
- const gradient = (X1,Y1,X2,Y2,C,S=1)=>
590
- {
591
- if (C >= 0)
592
- {
593
- if (blackAndWhite)
594
- x.fillStyle = '#fff';
595
- else
596
- {
597
- const g = x.fillStyle = x.createLinearGradient(X1,Y1,X2,Y2);
598
- g.addColorStop(0,color(C,2));
599
- g.addColorStop(1,color(C,1));
600
- }
601
- }
602
- else
603
- x.fillStyle = '#000';
604
- C >= -1 ? (x.fill(), S && x.stroke()) : x.stroke();
605
- }
606
- const circle = (X,Y,R,A=0,B=2*PI,C,S)=>
607
- {
608
- x.beginPath();
609
- x.arc(X,Y,R,p*A,p*B);
610
- gradient(X,Y-R,X,Y+R,C,S);
611
- }
612
- const rect = (X,Y,W,H,C)=>
613
- {
614
- x.beginPath();
615
- x.rect(X,Y,W,H*p);
616
- gradient(X,Y+H,X+W,Y,C);
617
- }
618
- const poly = (points,C,Y,H)=>
619
- {
620
- x.beginPath();
621
- for (const p of points)
622
- x.lineTo(p.x, p.y);
623
- x.closePath();
624
- gradient(0, Y, 0, Y+H,C);
625
- }
626
- const color = (c,l)=> l?`hsl(${[.95,.56,.13][c%3]*360} 99%${[0,50,75][l]}%`:'#000';
627
-
628
- // center and fit tos screen
629
- const alpha = oscillate(1,1,t);
630
- const p = percent(alpha, .1, .5);
631
- const size = min(6, min(w,h)/99);
632
- x.translate(w/2,h/2);
633
- x.scale(size,size);
634
- x.translate(-40,-35);
635
- p < 1 && x.setLineDash([99*p,99]);
636
- x.lineJoin = x.lineCap = 'round';
637
- x.lineWidth = .1 + p*1.9;
638
- //x.strokeStyle='#fff7';
639
-
640
- if (showName)
641
- {
642
- // engine name text
643
- const Y = 54;
644
- const s = 'LittleJS';
645
- x.font = '900 15.5px arial';
646
- x.lineWidth = .1+p*3.9;
647
- x.textAlign = 'center';
648
- x.textBaseline = 'top';
649
- rect(11,Y+1,59,8*p,-1);
650
- x.beginPath();
651
-
652
- let w2 = 0;
653
- for (let i=0;i<s.length;++i)
654
- w2 += x.measureText(s[i]).width;
655
- for (let j=2;j--;)
656
- for (let i=0,X=40-w2/2;i<s.length;++i)
657
- {
658
- const w = x.measureText(s[i]).width, X2 = X+w/2;
659
- gradient(X2,Y,X2+2,Y+13,i>5?1:0);
660
- x[j?'strokeText':'fillText'](s[i],X2,Y+.5,17*p);
661
- X += w;
662
- }
663
-
664
- x.lineWidth = .1 + p*1.9;
665
- rect(3,Y,73,0); // bottom
666
- }
667
-
668
- rect(7,15,26,-7,0); // cab top
669
- rect(25,15,8,25,-1); // cab front
670
- rect(10,40,15,-25,1); // cab back
671
- rect(14,21,7,9,2); // cab window
672
- rect(38,20,6,-6,2); // little stack
673
-
674
- // big stack
675
- rect(49,20,10,-6,0);
676
- const stackPoints = [vec2(44,8),vec2(64,8),vec2(59,8+6*p),vec2(49,8+6*p)];
677
- poly(stackPoints,2,8,6*p);
678
- rect(44,8,20,-7,0);
679
-
680
- // engine
681
- for (let i=5;i--;) circle(59-i*6*p,30,10,0,2*PI,1,0);
682
- circle(59,30,4,0,7,2); // light
683
-
684
- // engine outline
685
- rect(35,20,24,0); // top
686
- circle(59,30,10); // front
687
- circle(47,30,10,PI/2,PI*3/2); // middle
688
- circle(35,30,10,PI/2,PI*3/2); // back
689
- rect(7,40,13,7,-1); // bottom back
690
- rect(17,40,43,14,-1); // bottom center
691
-
692
- // wheels
693
- for (let i=3;i--;) for (let j=2;j--;) circle(17+15*i,47,j?7:1,0,2*PI,2);
694
-
695
- // cowcatcher
696
- for (let i=2;i--;)
697
- {
698
- let w=6, s=7, o=53+w*p*i
699
- const points = [vec2(o+s,54),vec2(o,40),vec2(o+w*p,40),vec2(o+s+w*p,54)];
700
- poly(points,0,40,14);
701
- }
702
-
703
- x.restore();
704
565
  }
@@ -14,10 +14,12 @@
14
14
 
15
15
  'use strict';
16
16
 
17
- /** Audio context used by the engine
17
+ /** Audio context used by the engine. Created lazily in audioInit() to avoid
18
+ * browser autoplay warnings about constructing an AudioContext before any
19
+ * user gesture.
18
20
  * @type {AudioContext}
19
21
  * @memberof Audio */
20
- let audioContext = new AudioContext;
22
+ let audioContext;
21
23
 
22
24
  /** Master gain node for all audio to pass through
23
25
  * @type {GainNode}
@@ -33,12 +35,13 @@ const audioDefaultSampleRate = 44100;
33
35
  * @return {boolean} - True if the audio context is running
34
36
  * @memberof Audio */
35
37
  function audioIsRunning()
36
- { return audioContext.state === 'running'; }
38
+ { return audioContext?.state === 'running'; }
37
39
 
38
40
  function audioInit()
39
41
  {
40
42
  if (!soundEnable || headlessMode) return;
41
43
 
44
+ audioContext = new AudioContext;
42
45
  audioMasterGain = audioContext.createGain();
43
46
  audioMasterGain.connect(audioContext.destination);
44
47
  audioMasterGain.gain.value = soundVolume; // set starting value
@@ -105,8 +108,8 @@ class Sound
105
108
 
106
109
  if (Array.isArray(asset))
107
110
  {
108
- // generate zzfx sound
109
- const zzfxSound = asset;
111
+ // generate zzfx sound — copy so we don't mutate the caller's array
112
+ const zzfxSound = asset.slice();
110
113
 
111
114
  // remove randomness so it can be applied on playback
112
115
  const defaultRandomness = randomness ?? .05;
@@ -33,18 +33,20 @@ const engineSourceFiles =
33
33
  `${SOURCE_FOLDER}/engineAudio.js`,
34
34
  `${SOURCE_FOLDER}/engineTileLayer.js`,
35
35
  `${SOURCE_FOLDER}/engineParticles.js`,
36
- `${SOURCE_FOLDER}/engineMedals.js`,
37
36
  `${SOURCE_FOLDER}/engineWebGL.js`,
37
+ `${SOURCE_FOLDER}/engineLogo.js`,
38
38
  ];
39
39
  const enginePluginFiles =
40
40
  [
41
+ `${PLUGIN_FOLDER}/medalSystem.js`,
41
42
  `${PLUGIN_FOLDER}/newgrounds.js`,
42
43
  `${PLUGIN_FOLDER}/postProcess.js`,
43
44
  `${PLUGIN_FOLDER}/zzfxm.js`,
44
45
  `${PLUGIN_FOLDER}/uiSystem.js`,
45
46
  `${PLUGIN_FOLDER}/box2d.js`,
46
47
  `${PLUGIN_FOLDER}/drawUtilities.js`,
47
- `${PLUGIN_FOLDER}/tween.js`,
48
+ `${PLUGIN_FOLDER}/tweenSystem.js`,
49
+ `${PLUGIN_FOLDER}/pathFinder.js`,
48
50
  ];
49
51
  const engineExtraFiles =
50
52
  [
@@ -44,7 +44,7 @@ let debugKey = 'Escape';
44
44
  let debugOverlay = false;
45
45
 
46
46
  // Engine internal variables not exposed to documentation
47
- let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParticles = false, debugGamepads = false, debugMedals = false, debugTakeScreenshot;
47
+ let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParticles = false, debugGamepads = false, debugTakeScreenshot;
48
48
 
49
49
  ///////////////////////////////////////////////////////////////////////////////
50
50
  // Debug helper functions
package/src/engineDraw.js CHANGED
@@ -217,8 +217,9 @@ class TextureInfo
217
217
  * Create a TextureInfo, called automatically by the engine
218
218
  * @param {HTMLImageElement|OffscreenCanvas} image
219
219
  * @param {boolean} [useWebGL] - Should use WebGL if available?
220
+ * @param {boolean} [wrap] - Should the texture wrap (REPEAT) or clamp (CLAMP_TO_EDGE)?
220
221
  */
221
- constructor(image, useWebGL=true)
222
+ constructor(image, useWebGL=true, wrap=false)
222
223
  {
223
224
  /** @property {HTMLImageElement|OffscreenCanvas} - image source */
224
225
  this.image = image;
@@ -228,6 +229,8 @@ class TextureInfo
228
229
  this.sizeInverse = image ? vec2(1/image.width, 1/image.height) : vec2();
229
230
  /** @property {WebGLTexture} - WebGL texture */
230
231
  this.glTexture = undefined;
232
+ /** @property {boolean} - true for REPEAT wrap mode, false for CLAMP_TO_EDGE */
233
+ this.wrap = wrap;
231
234
  useWebGL && this.createWebGLTexture();
232
235
  }
233
236
 
@@ -240,6 +243,14 @@ class TextureInfo
240
243
  /** Check if the texture is webgl enabled
241
244
  * @return {boolean} */
242
245
  hasWebGL() { return !!this.glTexture; }
246
+
247
+ /** Set the wrap mode for this texture
248
+ * @param {boolean} [wrap] - true for REPEAT, false for CLAMP_TO_EDGE */
249
+ setWrap(wrap=true)
250
+ {
251
+ this.wrap = wrap;
252
+ glSetTextureWrap(this.glTexture, wrap);
253
+ }
243
254
  }
244
255
 
245
256
  ///////////////////////////////////////////////////////////////////////////////
@@ -301,8 +312,13 @@ function drawTile(pos, size=vec2(1), tileInfo, color=WHITE,
301
312
  }
302
313
  else
303
314
  {
304
- // if no tile info, force untextured
305
- glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
315
+ // if no tile info, force untextured by zeroing rgba (so whatever
316
+ // texture is bound doesn't leak in) and folding color+additive
317
+ // into the additive slot — matches the Canvas2D path's
318
+ // color.add(additiveColor) on line ~337.
319
+ const combined = additiveColor ? color.add(additiveColor) : color;
320
+ glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0,
321
+ 0, combined.rgbaInt());
306
322
  }
307
323
  }
308
324
  else
@@ -441,6 +457,8 @@ function drawTextureWrapped(pos, size, wrapCount, texture=0, color=WHITE,
441
457
  const textureInfo = typeof texture === 'number' ? textureInfos[texture] : texture;
442
458
  ASSERT(textureInfo instanceof TextureInfo, 'texture not loaded');
443
459
  ASSERT(textureInfo.size.x > 0, 'texture not loaded');
460
+ ASSERT(textureInfo.wrap,
461
+ 'drawTextureWrapped requires a wrap-enabled texture; call textureInfo.setWrap(true) first');
444
462
 
445
463
  if (useWebGL && glEnable)
446
464
  {
@@ -11,6 +11,7 @@ export
11
11
  frameRate,
12
12
  timeDelta,
13
13
  engineObjects,
14
+ engineObjectsCollide,
14
15
  frame,
15
16
  time,
16
17
  timeReal,
@@ -33,6 +34,7 @@ export
33
34
  // Debug
34
35
  ASSERT,
35
36
  LOG,
37
+ debugPointSize,
36
38
  debugRect,
37
39
  debugPoly,
38
40
  debugCircle,
@@ -77,11 +79,14 @@ export
77
79
  gravity,
78
80
  particleEmitRateScale,
79
81
  glEnable,
82
+ glCircleSides,
80
83
  gamepadsEnable,
81
84
  gamepadDirectionEmulateStick,
82
85
  inputWASDEmulateDirection,
86
+ touchInputEnable,
83
87
  touchGamepadEnable,
84
88
  touchGamepadCenterButtonSize,
89
+ touchGamepadButtonCount,
85
90
  touchGamepadAnalog,
86
91
  touchGamepadSize,
87
92
  touchGamepadAlpha,
@@ -91,9 +96,6 @@ export
91
96
  soundVolume,
92
97
  soundDefaultRange,
93
98
  soundDefaultTaper,
94
- medalDisplayTime,
95
- medalDisplaySlideTime,
96
- medalDisplaySize,
97
99
 
98
100
  // Setters for globals
99
101
  setCameraPos,
@@ -125,6 +127,7 @@ export
125
127
  setObjectMaxSpeed,
126
128
  setGravity,
127
129
  setParticleEmitRateScale,
130
+ setGLCircleSides,
128
131
  setTouchInputEnable,
129
132
  setGamepadsEnable,
130
133
  setGamepadDirectionEmulateStick,
@@ -140,10 +143,6 @@ export
140
143
  setSoundVolume,
141
144
  setSoundDefaultRange,
142
145
  setSoundDefaultTaper,
143
- setMedalDisplayTime,
144
- setMedalDisplaySlideTime,
145
- setMedalDisplaySize,
146
- setMedalsPreventUnlock,
147
146
  setDebugWatermark,
148
147
  setDebugKey,
149
148
 
@@ -173,6 +172,7 @@ export
173
172
  percentLerp,
174
173
  smoothStep,
175
174
  nearestPowerOfTwo,
175
+ isPowerOfTwo,
176
176
  isOverlapping,
177
177
  isIntersecting,
178
178
  lineTest,
@@ -252,6 +252,7 @@ export
252
252
  drawLineList,
253
253
  drawLine,
254
254
  drawPoly,
255
+ drawRegularPoly,
255
256
  drawEllipse,
256
257
  drawCircle,
257
258
  drawCanvas2D,
@@ -265,6 +266,7 @@ export
265
266
  toggleFullscreen,
266
267
  setCursor,
267
268
  getCameraSize,
269
+ isOnScreen,
268
270
 
269
271
  // WebGL
270
272
  glCanvas,
@@ -272,6 +274,7 @@ export
272
274
  glAntialias,
273
275
  glClearCanvas,
274
276
  glSetTexture,
277
+ glSetTextureWrap,
275
278
  glCompileShader,
276
279
  glCreateProgram,
277
280
  glCreateTexture,
@@ -313,6 +316,7 @@ export
313
316
  gamepadWasPressed,
314
317
  gamepadWasReleased,
315
318
  gamepadStick,
319
+ gamepadStickCount,
316
320
  gamepadDpad,
317
321
  gamepadConnected,
318
322
  gamepadVibrate,
@@ -327,6 +331,7 @@ export
327
331
  audioContext,
328
332
  audioMasterGain,
329
333
  audioDefaultSampleRate,
334
+ audioIsRunning,
330
335
  Sound,
331
336
  SoundInstance,
332
337
  speak,
@@ -354,9 +359,4 @@ export
354
359
  ParticleEmitter,
355
360
  Particle,
356
361
 
357
- // Medals
358
- medals,
359
- medalsPreventUnlock,
360
- medalsInit,
361
- Medal,
362
362
  }
@@ -66,7 +66,7 @@ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
66
66
  * This is useful to disable for html menus so the browser can handle input normally
67
67
  * @param {boolean} preventDefault
68
68
  * @memberof Input */
69
- function setInputPreventDefault(preventDefault) { inputPreventDefault = preventDefault; }
69
+ function setInputPreventDefault(preventDefault=true) { inputPreventDefault = preventDefault; }
70
70
 
71
71
  /** Clears an input key state
72
72
  * @param {string|number} key