littlejsengine 1.3.0 → 1.3.8

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/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  LittleJS is a super lightweight JavaScript game engine with a fast hybrid rendering system. It provides everything necessary to make amazing games, including rendering, physics, particles, sound effects, music, input handling, and debug tools. LittleJS is designed to be extremely small yet powerful enough for a variety of applications, from game jams to commercial releases. The code is very well documented with a variety of examples to get you started. Choo-Choo! 🚂
6
6
 
7
- ## [LittleJS Documentation](https://killedbyapixel.github.io/LittleJS/docs) - [LittleJS Trailer](https://youtu.be/chuBzGjv7Ms) - [LittleJS Trello](https://trello.com/b/E9zf1Xak/littlejs)
7
+ ## [LittleJS Documentation](https://killedbyapixel.github.io/LittleJS/docs) - [LittleJS Trailer](https://youtu.be/chuBzGjv7Ms) - [LittleJS Discord](https://discord.gg/zb7hcGkyZe)
8
8
 
9
9
  ![LittleJS Screenshot](examples/screenshot.jpg)
10
10
 
@@ -29,7 +29,8 @@ LittleJS is a super lightweight JavaScript game engine with a fast hybrid render
29
29
  - Debug tools and debug rendering system
30
30
  - [Full documentation](https://killedbyapixel.github.io/LittleJS/docs) automatically generated from the source code block tags with [JSDoc](https://github.com/jsdoc/jsdoc)
31
31
  - Build system automatically combines everything, minifies, and removes unused code
32
- - For size coding competitions like [js13kGames](https://js13kgames.com/), starter project builds to a 7KB zip with [Roadroller](https://github.com/lifthrasiir/roadroller)
32
+ - For size coding competitions like [js13kGames](https://js13kgames.com/), starter project builds to a 7KB zip file
33
+ - (https://github.com/lifthrasiir/roadroller)
33
34
  - Easily build a Windows executable with [Electron](https://www.electronjs.org/) for distribution on platforms like Steam
34
35
  - Open Source with the [MIT license](https://github.com/KilledByAPixel/LittleJS/blob/main/LICENSE) so it can be used for anything you want
35
36
 
@@ -41,11 +42,13 @@ To easily include LittleJS in your game, you can use one of the 3 pre-built js f
41
42
  - [engine.all.release.js](https://github.com/KilledByAPixel/LittleJS/blob/main/engine/engine.all.release.js) - The engine optimized for release builds
42
43
  - [engine.all.min.js](https://github.com/KilledByAPixel/LittleJS/blob/main/engine/engine.all.min.js) - The engine in release mode and minified
43
44
 
44
- The hello world example includes a batch file [build.bat](https://github.com/KilledByAPixel/LittleJS/blob/main/build.bat) that compresses everything into a tiny zip file using Google Closure, UglifyJS, Roadroller, and ECT. You must run buildSetup.bat to install the necessary npm dependencies.
45
+ The hello world example includes a batch file [build.bat](https://github.com/KilledByAPixel/LittleJS/blob/main/build.bat) that compresses everything into a tiny zip file using Google Closure, UglifyJS, Roadroller, and ECT. You must run [buildSetup.bat](https://github.com/KilledByAPixel/LittleJS/blob/main/buildSetup.bat) to install the necessary npm dependencies.
45
46
 
46
47
  ## Games Made With LittleJS
47
48
 
48
- - [Space Huggers](https://github.com/KilledByAPixel/SpaceHuggers) - A more developed version of the platformer example
49
+ - [Space Huggers](https://www.newgrounds.com/portal/view/819609) - Run and gun platformer with procedural levels
50
+ - [Space Huggers - JS13k Edition](https://github.com/KilledByAPixel/SpaceHuggers) - Original js13k game with source code
51
+ - [Isletopia](https://store.steampowered.com/app/1861260/Isletopia) - Relaxing strategy game of greenifying barren islands
49
52
  - Send me your games!
50
53
 
51
54
  ![LittleJS Logo](favicon.png)
package/build.bat CHANGED
@@ -49,7 +49,7 @@ if %ERRORLEVEL% NEQ 0 (
49
49
  exit /b %ERRORLEVEL%
50
50
  )
51
51
 
52
- rem roadroaller compresses the code better then zip
52
+ rem roadroller compresses the code better then zip
53
53
  copy %BUILD_FILENAME% roadroller_%BUILD_FILENAME%
54
54
  call npx roadroller roadroller_%BUILD_FILENAME% -o roadroller_%BUILD_FILENAME%
55
55
  if %ERRORLEVEL% NEQ 0 (
package/buildSetup.bat CHANGED
@@ -4,4 +4,7 @@ call npm install google-closure-compiler
4
4
  call npm install uglify-js
5
5
  call npm install roadroller
6
6
  call npm install ect-bin
7
- call npm install typescript
7
+ call npm install typescript
8
+
9
+ echo Finishd installing LittleJS build dependencies.
10
+ pause
@@ -30,7 +30,7 @@ const enableAsserts = 1;
30
30
  * @memberof Debug */
31
31
  const debugPointSize = .5;
32
32
 
33
- /** True if watermark with FPS should be down
33
+ /** True if watermark with FPS should be down, false in release builds
34
34
  * @default
35
35
  * @memberof Debug */
36
36
  let showWatermark = 1;
@@ -650,7 +650,7 @@ class Vector2
650
650
  /** Returns a new vector in same direction as this one with the length passed in
651
651
  * @param {Number} [length=1]
652
652
  * @return {Vector2} */
653
- normalize(length=1) { const l = this.length(); return l ? this.scale(length/l) : new Vector2(length); }
653
+ normalize(length=1) { const l = this.length(); return l ? this.scale(length/l) : new Vector2(0, length); }
654
654
 
655
655
  /** Returns a new vector clamped to length passed in
656
656
  * @param {Number} [length=1]
@@ -695,7 +695,7 @@ class Vector2
695
695
 
696
696
  /** Returns the area this vector covers as a rectangle
697
697
  * @return {Number} */
698
- area() { return this.x * this.y; }
698
+ area() { return abs(this.x * this.y); }
699
699
 
700
700
  /** Returns a new vector that is p percent between this and the vector passed in
701
701
  * @param {Vector2} vector
@@ -804,6 +804,34 @@ class Color
804
804
  return this;
805
805
  }
806
806
 
807
+ /** Returns this color expressed in hsla format
808
+ * @return {Array} */
809
+ getHSLA()
810
+ {
811
+ const r = this.r;
812
+ const g = this.g;
813
+ const b = this.b;
814
+ const a = this.a;
815
+ const max = Math.max(r, g, b);
816
+ const min = Math.min(r, g, b);
817
+ const l = (max + min) / 2;
818
+
819
+ let h = 0, s = 0;
820
+ if (max != min)
821
+ {
822
+ let d = max - min;
823
+ s = l > .5 ? d / (2 - max - min) : d / (max + min);
824
+ if (r == max)
825
+ h = (g - b) / d + (g < b ? 6 : 0);
826
+ else if (g == max)
827
+ h = (b - r) / d + 2;
828
+ else if (b == max)
829
+ h = (r - g) / d + 4;
830
+ }
831
+
832
+ return [h / 6, s, l, a];
833
+ }
834
+
807
835
  /** Returns a new color that has each component randomly adjusted
808
836
  * @param {Number} [amount=.05]
809
837
  * @param {Number} [alphaAmount=0]
@@ -835,14 +863,6 @@ class Color
835
863
  return (this.r*255|0) + (this.g*255<<8) + (this.b*255<<16) + (this.a*255<<24);
836
864
  }
837
865
 
838
- /** Returns this color expressed as a hex code
839
- * @return {String} */
840
- hex()
841
- {
842
- const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
843
- return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b);
844
- }
845
-
846
866
  /** Set this color from a hex code
847
867
  * @param {String} hex - html hex code
848
868
  * @return {Color} */
@@ -856,6 +876,14 @@ class Color
856
876
  ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1);
857
877
  return this;
858
878
  }
879
+
880
+ /** Returns this color expressed as a hex code
881
+ * @return {String} */
882
+ getHex()
883
+ {
884
+ const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
885
+ return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b);
886
+ }
859
887
  }
860
888
 
861
889
  ///////////////////////////////////////////////////////////////////////////////
@@ -904,7 +932,7 @@ class Timer
904
932
 
905
933
  /** Returns this timer expressed as a string
906
934
  * @return {String} */
907
- toString() { return this.unset() ? 'unset' : Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ); }
935
+ toString() { if (debug) { return this.unset() ? 'unset' : Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ); } }
908
936
  }
909
937
  /**
910
938
  * LittleJS Engine Settings
@@ -922,7 +950,8 @@ class Timer
922
950
  * @memberof Settings */
923
951
  let canvasMaxSize = vec2(1920, 1200);
924
952
 
925
- /** Fixed size of the canvas, if enabled cavnvas size never changes
953
+ /** Fixed size of the canvas, if enabled canvas size never changes
954
+ * - you may also need to set mainCanvasSize if using screen space coords in startup
926
955
  * @type {Vector2}
927
956
  * @default
928
957
  * @memberof Settings */
@@ -961,6 +990,11 @@ let tileFixBleedScale = .3;
961
990
  * @memberof Settings */
962
991
  let objectDefaultSize = vec2(1);
963
992
 
993
+ /** Enable physics solver for collisions between objects
994
+ * @default
995
+ * @memberof Settings */
996
+ let enablePhysicsSolver = 1;
997
+
964
998
  /** Default object mass for collison calcuations (how heavy objects are)
965
999
  * @default
966
1000
  * @memberof Settings */
@@ -1159,7 +1193,7 @@ class EngineObject
1159
1193
  /** Create an engine object and adds it to the list of objects
1160
1194
  * @param {Vector2} [position=new Vector2()] - World space position of the object
1161
1195
  * @param {Vector2} [size=objectDefaultSize] - World space size of the object
1162
- * @param {Number} [tileIndex=-1] - Tile to use to render object, untextured if -1
1196
+ * @param {Number} [tileIndex=-1] - Tile to use to render object (-1 is untextured)
1163
1197
  * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
1164
1198
  * @param {Number} [angle=0] - Angle the object is rotated by
1165
1199
  * @param {Color} [color] - Color to apply to tile when rendered
@@ -1176,7 +1210,7 @@ class EngineObject
1176
1210
  this.size = size;
1177
1211
  /** @property {Vector2} - Size of object used for drawing, uses size if not set */
1178
1212
  this.drawSize;
1179
- /** @property {Number} - Tile to use to render object, untextured if -1 */
1213
+ /** @property {Number} - Tile to use to render object (-1 is untextured) */
1180
1214
  this.tileIndex = tileIndex;
1181
1215
  /** @property {Vector2} - Size of tile in source pixels */
1182
1216
  this.tileSize = tileSize;
@@ -1188,7 +1222,7 @@ class EngineObject
1188
1222
  this.additiveColor;
1189
1223
 
1190
1224
  // set object defaults
1191
- /** @property {Number} [mass=objectDefaultMass] - How heavy the object is */
1225
+ /** @property {Number} [mass=objectDefaultMass] - How heavy the object is, static if 0 */
1192
1226
  this.mass = objectDefaultMass;
1193
1227
  /** @property {Number} [damping=objectDefaultDamping] - How much to slow down velocity each frame (0-1) */
1194
1228
  this.damping = objectDefaultDamping;
@@ -1242,7 +1276,7 @@ class EngineObject
1242
1276
  ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
1243
1277
  ASSERT(this.damping >= 0 && this.damping <= 1);
1244
1278
 
1245
- if (!this.mass) // do not update collision for fixed objects
1279
+ if (!enablePhysicsSolver || !this.mass) // do not update collision for fixed objects
1246
1280
  return;
1247
1281
 
1248
1282
  const wasMovingDown = this.velocity.y < 0;
@@ -1252,7 +1286,7 @@ class EngineObject
1252
1286
  const groundSpeed = this.groundObject.velocity ? this.groundObject.velocity.x : 0;
1253
1287
  this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * this.friction;
1254
1288
  this.groundObject = 0;
1255
- //debugPhysics && debugPoint(this.pos.subtract(vec2(0,this.size.y/2)), '#0f0');
1289
+ //debugOverlay && debugPhysics && debugPoint(this.pos.subtract(vec2(0,this.size.y/2)), '#0f0');
1256
1290
  }
1257
1291
 
1258
1292
  if (this.collideSolidObjects)
@@ -1284,7 +1318,7 @@ class EngineObject
1284
1318
  if (o.mass) // push away if not fixed
1285
1319
  o.velocity = o.velocity.subtract(velocity);
1286
1320
 
1287
- debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f00');
1321
+ debugOverlay && debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f00');
1288
1322
  continue;
1289
1323
  }
1290
1324
 
@@ -1347,7 +1381,7 @@ class EngineObject
1347
1381
  else // bounce if other object is fixed
1348
1382
  this.velocity.x *= -this.elasticity;
1349
1383
  }
1350
- debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f0f');
1384
+ debugOverlay && debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f0f');
1351
1385
  }
1352
1386
  }
1353
1387
  if (this.collideTiles)
@@ -1480,18 +1514,21 @@ class EngineObject
1480
1514
 
1481
1515
  toString()
1482
1516
  {
1483
- let text = 'type = ' + this.constructor.name;
1484
- if (this.pos.x || this.pos.y)
1485
- text += '\npos = ' + this.pos;
1486
- if (this.velocity.x || this.velocity.y)
1487
- text += '\nvelocity = ' + this.velocity;
1488
- if (this.size.x || this.size.y)
1489
- text += '\nsize = ' + this.size;
1490
- if (this.angle)
1491
- text += '\nangle = ' + this.angle.toFixed(3);
1492
- if (this.color)
1493
- text += '\ncolor = ' + this.color;
1494
- return text;
1517
+ if (debug)
1518
+ {
1519
+ let text = 'type = ' + this.constructor.name;
1520
+ if (this.pos.x || this.pos.y)
1521
+ text += '\npos = ' + this.pos;
1522
+ if (this.velocity.x || this.velocity.y)
1523
+ text += '\nvelocity = ' + this.velocity;
1524
+ if (this.size.x || this.size.y)
1525
+ text += '\nsize = ' + this.size;
1526
+ if (this.angle)
1527
+ text += '\nangle = ' + this.angle.toFixed(3);
1528
+ if (this.color)
1529
+ text += '\ncolor = ' + this.color;
1530
+ return text;
1531
+ }
1495
1532
  }
1496
1533
  }
1497
1534
  /**
@@ -1549,18 +1586,26 @@ let overlayContext;
1549
1586
  let mainCanvasSize = vec2();
1550
1587
 
1551
1588
  /** Convert from screen to world space coordinates
1589
+ * - if calling outside of render, you may need to manually set mainCanvasSize
1552
1590
  * @param {Vector2} screenPos
1553
1591
  * @return {Vector2}
1554
1592
  * @memberof Draw */
1555
1593
  const screenToWorld = (screenPos)=>
1556
- screenPos.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos);
1594
+ {
1595
+ ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
1596
+ return screenPos.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos);
1597
+ }
1557
1598
 
1558
1599
  /** Convert from world to screen space coordinates
1600
+ * - if calling outside of render, you may need to manually set mainCanvasSize
1559
1601
  * @param {Vector2} worldPos
1560
1602
  * @return {Vector2}
1561
1603
  * @memberof Draw */
1562
1604
  const worldToScreen = (worldPos)=>
1563
- worldPos.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));
1605
+ {
1606
+ ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
1607
+ return worldPos.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));
1608
+ }
1564
1609
 
1565
1610
  /** Draw textured tile centered in world space, with color applied if using WebGL
1566
1611
  * @param {Vector2} pos - Center of the tile in world space
@@ -1661,7 +1706,7 @@ function drawTileScreenSpace(pos, size=vec2(1), tileIndex, tileSize, color, angl
1661
1706
  * @memberof Draw */
1662
1707
  function drawRectScreenSpace(pos, size, color, angle, useWebGL)
1663
1708
  {
1664
- drawTileSrceenSpace(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
1709
+ drawTileScreenSpace(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
1665
1710
  }
1666
1711
 
1667
1712
  /** Draw colored line between two points
@@ -1724,14 +1769,15 @@ function setBlendMode(additive, useWebGL=glEnable)
1724
1769
  function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault)
1725
1770
  {
1726
1771
  overlayContext.fillStyle = color;
1727
- overlayContext.lineWidth = lineWidth *= cameraScale;
1772
+ overlayContext.lineWidth = lineWidth;
1728
1773
  overlayContext.strokeStyle = lineColor;
1729
1774
  overlayContext.textAlign = textAlign;
1730
1775
  overlayContext.font = size + 'px '+ font;
1731
1776
  overlayContext.textBaseline = 'middle';
1777
+ overlayContext.lineJoin = 'round';
1732
1778
 
1733
1779
  pos = pos.copy();
1734
- text.split('\n').forEach(line=>
1780
+ (text+'').split('\n').forEach(line=>
1735
1781
  {
1736
1782
  lineWidth && overlayContext.strokeText(line, pos.x, pos.y);
1737
1783
  overlayContext.fillText(line, pos.x, pos.y);
@@ -1751,7 +1797,7 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
1751
1797
  * @memberof Draw */
1752
1798
  function drawText(text, pos, size=1, color, lineWidth, lineColor, textAlign, font)
1753
1799
  {
1754
- drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth, lineColor, textAlign, font);
1800
+ drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font);
1755
1801
  }
1756
1802
 
1757
1803
  ///////////////////////////////////////////////////////////////////////////////
@@ -1945,6 +1991,10 @@ let mouseWheel = 0;
1945
1991
  * @memberof Input */
1946
1992
  let isUsingGamepad = 0;
1947
1993
 
1994
+ /** Prevents input continuing to the default browser handling (false by default)
1995
+ * @memberof Input */
1996
+ let preventDefaultInput = 0;
1997
+
1948
1998
  /** Returns true if gamepad button is down
1949
1999
  * @param {Number} button
1950
2000
  * @param {Number} [gamepad=0]
@@ -2008,7 +2058,7 @@ onkeydown = (e)=>
2008
2058
  {
2009
2059
  if (debug && e.target != document.body) return;
2010
2060
  e.repeat || (inputData[isUsingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
2011
- debug || e.preventDefault();
2061
+ preventDefaultInput && e.preventDefault();
2012
2062
  }
2013
2063
  onkeyup = (e)=>
2014
2064
  {
@@ -2033,7 +2083,7 @@ const mouseToScreen = (mousePos)=>
2033
2083
  return vec2(); // fix bug that can occur if user clicks before page loads
2034
2084
 
2035
2085
  const rect = mainCanvas.getBoundingClientRect();
2036
- return mainCanvasSize.multiply(
2086
+ return vec2(mainCanvas.width, mainCanvas.height).multiply(
2037
2087
  vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
2038
2088
  }
2039
2089
 
@@ -2572,8 +2622,7 @@ function zzfxG
2572
2622
  )
2573
2623
  {
2574
2624
  // init parameters
2575
- let PI2 = PI*2, sign = v => v>0?1:-1,
2576
- startSlide = slide *= 500 * PI2 / zzfxR / zzfxR, b=[],
2625
+ let PI2 = PI*2, startSlide = slide *= 500 * PI2 / zzfxR / zzfxR, b=[],
2577
2626
  startFrequency = frequency *= (1 + randomness*rand(-1,1)) * PI2 / zzfxR,
2578
2627
  t=0, tm=0, i=0, j=1, r=0, c=0, s=0, f, length;
2579
2628
 
@@ -2597,15 +2646,15 @@ function zzfxG
2597
2646
  {
2598
2647
  s = shape? shape>1? shape>2? shape>3? // wave shape
2599
2648
  Math.sin((t%PI2)**3) : // 4 noise
2600
- Math.max(Math.min(Math.tan(t),1),-1): // 3 tan
2649
+ max(min(Math.tan(t),1),-1): // 3 tan
2601
2650
  1-(2*t/PI2%2+2)%2: // 2 saw
2602
- 1-4*abs(Math.round(t/PI2)-t/PI2): // 1 triangle
2651
+ 1-4*abs(Math.round(t/PI2)-t/PI2): // 1 triangle
2603
2652
  Math.sin(t); // 0 sin
2604
2653
 
2605
2654
  s = (repeatTime ?
2606
2655
  1 - tremolo + tremolo*Math.sin(PI2*i/repeatTime) // tremolo
2607
2656
  : 1) *
2608
- sign(s)*(abs(s)**shapeCurve) * // curve 0=square, 2=pointy
2657
+ sign(s)*(abs(s)**shapeCurve) * // curve 0=square, 2=pointy
2609
2658
  volume * soundVolume * ( // envelope
2610
2659
  i < attack ? i/attack : // attack
2611
2660
  i < attack + decay ? // decay
@@ -2666,7 +2715,6 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
2666
2715
  let notFirstBeat;
2667
2716
  let stop;
2668
2717
  let instrument;
2669
- let pitch;
2670
2718
  let attenuation;
2671
2719
  let outSampleOffset;
2672
2720
  let isSequenceEnd;
@@ -2685,7 +2733,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
2685
2733
  for (; hasMore; channelIndex++) {
2686
2734
 
2687
2735
  // reset current values
2688
- sampleBuffer = [hasMore = notFirstBeat = pitch = outSampleOffset = 0];
2736
+ sampleBuffer = [hasMore = notFirstBeat = outSampleOffset = 0];
2689
2737
 
2690
2738
  // for each pattern in sequence
2691
2739
  sequence.forEach((patternIndex, sequenceIndex) => {
@@ -3210,7 +3258,7 @@ class ParticleEmitter extends EngineObject
3210
3258
  // particle settings
3211
3259
  /** @property {Number} - How long particles live */
3212
3260
  this.particleTime = particleTime;
3213
- /** @property {Number} - How big are particles at start */
3261
+ /** @property {Number} - How big are particles at start */
3214
3262
  this.sizeStart = sizeStart;
3215
3263
  /** @property {Number} - How big are particles at end */
3216
3264
  this.sizeEnd = sizeEnd;
@@ -3573,7 +3621,7 @@ class Newgrounds
3573
3621
  cipher && (this.cryptoJS = CryptoJS());
3574
3622
 
3575
3623
  // get session id from url search params
3576
- const url = new URL(window.location.href);
3624
+ const url = new URL(location.href);
3577
3625
  this.session_id = url.searchParams.get('ngio_session_id') || 0;
3578
3626
 
3579
3627
  if (this.session_id == 0)
@@ -3926,7 +3974,7 @@ function glFlush()
3926
3974
  * @memberof WebGL */
3927
3975
  function glCopyToContext(context, forceDraw)
3928
3976
  {
3929
- if (!glEnable || !glBatchCount) return;
3977
+ if ((!glEnable || !glBatchCount) && !forceDraw) return;
3930
3978
 
3931
3979
  glFlush();
3932
3980
 
@@ -4056,7 +4104,7 @@ gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2
4056
4104
  const engineName = 'LittleJS';
4057
4105
 
4058
4106
  /** Version of engine */
4059
- const engineVersion = '1.3.0';
4107
+ const engineVersion = '1.3.8';
4060
4108
 
4061
4109
  /** Frames per second to update objects
4062
4110
  * @default */
@@ -4147,6 +4195,33 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4147
4195
  timeReal += frameTimeDeltaMS / 1e3;
4148
4196
  frameTimeBufferMS = min(frameTimeBufferMS + !paused * frameTimeDeltaMS, 50); // clamp incase of slow framerate
4149
4197
 
4198
+ if (canvasFixedSize.x)
4199
+ {
4200
+ // clear set fixed size
4201
+ overlayCanvas.width = mainCanvas.width = canvasFixedSize.x;
4202
+ overlayCanvas.height = mainCanvas.height = canvasFixedSize.y;
4203
+
4204
+ // fit to window by adding space on top or bottom if necessary
4205
+ const aspect = innerWidth / innerHeight;
4206
+ const fixedAspect = mainCanvas.width / mainCanvas.height;
4207
+ mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
4208
+ mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
4209
+ if (glCanvas)
4210
+ {
4211
+ glCanvas.style.width = mainCanvas.style.width;
4212
+ glCanvas.style.height = mainCanvas.style.height;
4213
+ }
4214
+ }
4215
+ else
4216
+ {
4217
+ // clear and set size to same as window
4218
+ overlayCanvas.width = mainCanvas.width = min(innerWidth, canvasMaxSize.x);
4219
+ overlayCanvas.height = mainCanvas.height = min(innerHeight, canvasMaxSize.y);
4220
+ }
4221
+
4222
+ // save canvas size
4223
+ mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
4224
+
4150
4225
  if (paused)
4151
4226
  {
4152
4227
  // do post update even when paused
@@ -4183,30 +4258,6 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4183
4258
  // add the time smoothing back in
4184
4259
  frameTimeBufferMS += deltaSmooth;
4185
4260
  }
4186
-
4187
- if (canvasFixedSize.x)
4188
- {
4189
- // clear set fixed size
4190
- mainCanvas.width = canvasFixedSize.x;
4191
- mainCanvas.height = canvasFixedSize.y;
4192
-
4193
- // fit to window by adding space on top or bottom if necessary
4194
- const aspect = innerWidth / innerHeight;
4195
- const fixedAspect = mainCanvas.width / mainCanvas.height;
4196
- mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
4197
- mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
4198
- if (glCanvas)
4199
- {
4200
- glCanvas.style.width = mainCanvas.style.width;
4201
- glCanvas.style.height = mainCanvas.style.height;
4202
- }
4203
- }
4204
- else
4205
- {
4206
- // clear and set size to same as window
4207
- mainCanvas.width = min(innerWidth, canvasMaxSize.x);
4208
- mainCanvas.height = min(innerHeight, canvasMaxSize.y);
4209
- }
4210
4261
 
4211
4262
  // render sort then render while removing destroyed objects
4212
4263
  enginePreRender();
@@ -4246,8 +4297,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4246
4297
  // called by engine to setup render system
4247
4298
  function enginePreRender()
4248
4299
  {
4249
- // save canvas size and clear canvases
4250
- mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
4300
+ // save canvas size
4301
+ mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
4251
4302
 
4252
4303
  // disable smoothing for pixel art
4253
4304
  mainContext.imageSmoothingEnabled = !cavasPixelated;