littlejsengine 1.4.7 → 1.4.9

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.
@@ -492,7 +492,7 @@ const wave = (frequency=1, amplitude=1, t=time)=> amplitude/2 * (1 - Math.cos(t*
492
492
  * @param {Number} t - time in seconds
493
493
  * @return {String}
494
494
  * @memberof Utilities */
495
- const formatTime = (t)=> (t/60|0)+':'+(t%60<10?'0':'')+(t%60|0);
495
+ const formatTime = (t)=> (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0);
496
496
 
497
497
  ///////////////////////////////////////////////////////////////////////////////
498
498
 
@@ -516,7 +516,7 @@ const randInt = (a=1, b=0)=> rand(a,b)|0;
516
516
  /** Randomly returns either -1 or 1
517
517
  * @return {Number}
518
518
  * @memberof Random */
519
- const randSign = ()=> (rand(2)|0) * 2 - 1;
519
+ const randSign = ()=> randInt(2) * 2 - 1;
520
520
 
521
521
  /** Returns a random Vector2 within a circular shape
522
522
  * @param {Number} [radius=1]
@@ -540,10 +540,15 @@ const randVector = (length=1)=> new Vector2().setAngle(rand(2*PI), length);
540
540
  const randColor = (cA = new Color, cB = new Color(0,0,0,1), linear)=>
541
541
  linear ? cA.lerp(cB, rand()) : new Color(rand(cA.r,cB.r),rand(cA.g,cB.g),rand(cA.b,cB.b),rand(cA.a,cB.a));
542
542
 
543
- /** The seed used by the randSeeded function, should not be 0
543
+ /** Seed used by the randSeeded function
544
544
  * @memberof Random */
545
545
  let randSeed = 1;
546
546
 
547
+ /** Set seed used by the randSeeded function, should not be 0
548
+ * @param {Number} seed
549
+ * @memberof Random */
550
+ const setRandSeed = (seed)=> randSeed = seed;
551
+
547
552
  /** Returns a seeded random value between the two values passed in using randSeed
548
553
  * @param {Number} [valueA=1]
549
554
  * @param {Number} [valueB=0]
@@ -571,6 +576,14 @@ const randSeeded = (a=1, b=0)=>
571
576
  */
572
577
  const vec2 = (x=0, y)=> x.x == undefined ? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y);
573
578
 
579
+ /**
580
+ * Check if object is a valid Vector2
581
+ * @param {Vector2} vector
582
+ * @return {Boolean}
583
+ * @memberof Utilities
584
+ */
585
+ const isVector2 = (v)=> !isNaN(v.x) && !isNaN(v.y);
586
+
574
587
  /**
575
588
  * 2D Vector object with vector math library
576
589
  * <br> - Functions do not change this so they can be chained together
@@ -600,27 +613,27 @@ class Vector2
600
613
  /** Returns a copy of this vector plus the vector passed in
601
614
  * @param {Vector2} vector
602
615
  * @return {Vector2} */
603
- add(v) { ASSERT(v.x!=undefined); return new Vector2(this.x + v.x, this.y + v.y); }
616
+ add(v) { ASSERT(isVector2(v)); return new Vector2(this.x + v.x, this.y + v.y); }
604
617
 
605
618
  /** Returns a copy of this vector minus the vector passed in
606
619
  * @param {Vector2} vector
607
620
  * @return {Vector2} */
608
- subtract(v) { ASSERT(v.x!=undefined); return new Vector2(this.x - v.x, this.y - v.y); }
621
+ subtract(v) { ASSERT(isVector2(v)); return new Vector2(this.x - v.x, this.y - v.y); }
609
622
 
610
623
  /** Returns a copy of this vector times the vector passed in
611
624
  * @param {Vector2} vector
612
625
  * @return {Vector2} */
613
- multiply(v) { ASSERT(v.x!=undefined); return new Vector2(this.x * v.x, this.y * v.y); }
626
+ multiply(v) { ASSERT(isVector2(v)); return new Vector2(this.x * v.x, this.y * v.y); }
614
627
 
615
628
  /** Returns a copy of this vector divided by the vector passed in
616
629
  * @param {Vector2} vector
617
630
  * @return {Vector2} */
618
- divide(v) { ASSERT(v.x!=undefined); return new Vector2(this.x / v.x, this.y / v.y); }
631
+ divide(v) { ASSERT(isVector2(v)); return new Vector2(this.x / v.x, this.y / v.y); }
619
632
 
620
633
  /** Returns a copy of this vector scaled by the vector passed in
621
634
  * @param {Number} scale
622
635
  * @return {Vector2} */
623
- scale(s) { ASSERT(s.x==undefined); return new Vector2(this.x * s, this.y * s); }
636
+ scale(s) { ASSERT(!isVector2(s)); return new Vector2(this.x * s, this.y * s); }
624
637
 
625
638
  /** Returns the length of this vector
626
639
  * @return {Number} */
@@ -653,12 +666,12 @@ class Vector2
653
666
  /** Returns the dot product of this and the vector passed in
654
667
  * @param {Vector2} vector
655
668
  * @return {Number} */
656
- dot(v) { ASSERT(v.x!=undefined); return this.x*v.x + this.y*v.y; }
669
+ dot(v) { ASSERT(isVector2(v)); return this.x*v.x + this.y*v.y; }
657
670
 
658
671
  /** Returns the cross product of this and the vector passed in
659
672
  * @param {Vector2} vector
660
673
  * @return {Number} */
661
- cross(v) { ASSERT(v.x!=undefined); return this.x*v.y - this.y*v.x; }
674
+ cross(v) { ASSERT(isVector2(v)); return this.x*v.y - this.y*v.x; }
662
675
 
663
676
  /** Returns the angle of this vector, up is angle 0
664
677
  * @return {Number} */
@@ -694,7 +707,7 @@ class Vector2
694
707
  * @param {Vector2} vector
695
708
  * @param {Number} percent
696
709
  * @return {Vector2} */
697
- lerp(v, p) { ASSERT(v.x!=undefined); return this.add(v.subtract(this).scale(clamp(p))); }
710
+ lerp(v, p) { ASSERT(isVector2(v)); return this.add(v.subtract(this).scale(clamp(p))); }
698
711
 
699
712
  /** Returns true if this vector is within the bounds of an array size passed in
700
713
  * @param {Vector2} arraySize
@@ -705,7 +718,7 @@ class Vector2
705
718
  * @param {float} digits - precision to display
706
719
  * @return {String} */
707
720
  toString(digits=3)
708
- { return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`; }
721
+ { if (debug) { return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`; }}
709
722
  }
710
723
 
711
724
  ///////////////////////////////////////////////////////////////////////////////
@@ -943,7 +956,7 @@ class Timer
943
956
 
944
957
  /** Returns this timer expressed as a string
945
958
  * @return {String} */
946
- toString() { if (debug) { return this.unset() ? 'unset' : Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ); } }
959
+ toString() { if (debug) { return this.unset() ? 'unset' : Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ); }}
947
960
 
948
961
  /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
949
962
  * @return {Number} */
@@ -956,6 +969,20 @@ class Timer
956
969
 
957
970
  'use strict';
958
971
 
972
+ ///////////////////////////////////////////////////////////////////////////////
973
+ // Camera settings
974
+
975
+ /** Position of camera in world space
976
+ * @type {Vector2}
977
+ * @default
978
+ * @memberof Settings */
979
+ let cameraPos = vec2();
980
+
981
+ /** Scale of camera in world space
982
+ * @default
983
+ * @memberof Settings */
984
+ let cameraScale = 32;
985
+
959
986
  ///////////////////////////////////////////////////////////////////////////////
960
987
  // Display settings
961
988
 
@@ -982,6 +1009,19 @@ let cavasPixelated = 1;
982
1009
  * @memberof Settings */
983
1010
  let fontDefault = 'arial';
984
1011
 
1012
+ ///////////////////////////////////////////////////////////////////////////////
1013
+ // WebGL settings
1014
+
1015
+ /** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled)
1016
+ * @default
1017
+ * @memberof Settings */
1018
+ let glEnable = 1;
1019
+
1020
+ /** Fixes slow rendering in some browsers by not compositing the WebGL canvas
1021
+ * @default
1022
+ * @memberof Settings */
1023
+ let glOverlay = 1;
1024
+
985
1025
  ///////////////////////////////////////////////////////////////////////////////
986
1026
  // Tile sheet settings
987
1027
 
@@ -1050,33 +1090,6 @@ let gravity = 0;
1050
1090
  * @memberof Settings */
1051
1091
  let particleEmitRateScale = 1;
1052
1092
 
1053
- ///////////////////////////////////////////////////////////////////////////////
1054
- // Camera settings
1055
-
1056
- /** Position of camera in world space
1057
- * @type {Vector2}
1058
- * @default
1059
- * @memberof Settings */
1060
- let cameraPos = vec2();
1061
-
1062
- /** Scale of camera in world space
1063
- * @default
1064
- * @memberof Settings */
1065
- let cameraScale = max(tileSizeDefault.x, tileSizeDefault.y);
1066
-
1067
- ///////////////////////////////////////////////////////////////////////////////
1068
- // WebGL settings
1069
-
1070
- /** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled)
1071
- * @default
1072
- * @memberof Settings */
1073
- let glEnable = 1;
1074
-
1075
- /** Fixes slow rendering in some browsers by not compositing the WebGL canvas
1076
- * @default
1077
- * @memberof Settings */
1078
- let glOverlay = 1;
1079
-
1080
1093
  ///////////////////////////////////////////////////////////////////////////////
1081
1094
  // Input settings
1082
1095
 
@@ -1125,20 +1138,20 @@ let vibrateEnable = 1;
1125
1138
  ///////////////////////////////////////////////////////////////////////////////
1126
1139
  // Audio settings
1127
1140
 
1128
- /** Volume scale to apply to all sound, music and speech
1141
+ /** All audio code can be disabled and removed from build
1129
1142
  * @default
1130
1143
  * @memberof Settings */
1131
- let soundVolume = .5;
1144
+ let soundEnable = 1;
1132
1145
 
1133
- /** All audio code can be disabled and removed from build
1146
+ /** Volume scale to apply to all sound, music and speech
1134
1147
  * @default
1135
1148
  * @memberof Settings */
1136
- let soundEnable = 1;
1149
+ let soundVolume = .5;
1137
1150
 
1138
1151
  /** Default range where sound no longer plays
1139
1152
  * @default
1140
1153
  * @memberof Settings */
1141
- let soundDefaultRange = 30;
1154
+ let soundDefaultRange = 40;
1142
1155
 
1143
1156
  /** Default range percent to start tapering off sound (0-1)
1144
1157
  * @default
@@ -1158,20 +1171,20 @@ let medalDisplayTime = 5;
1158
1171
  * @memberof Settings */
1159
1172
  let medalDisplaySlideTime = .5;
1160
1173
 
1161
- /** Width of medal display
1174
+ /** Size of medal display
1162
1175
  * @default
1163
1176
  * @memberof Settings */
1164
- let medalDisplayWidth = 640;
1177
+ let medalDisplaySize = vec2(640, 80);
1165
1178
 
1166
- /** Height of medal display
1179
+ /** Size of icon in medal display
1167
1180
  * @default
1168
1181
  * @memberof Settings */
1169
- let medalDisplayHeight = 80;
1182
+ let medalDisplayIconSize = 50;
1170
1183
 
1171
- /** Size of icon in medal display
1184
+ /** Set to stop medals from being unlockable (like if cheats are enabled)
1172
1185
  * @default
1173
1186
  * @memberof Settings */
1174
- let medalDisplayIconSize = 50;
1187
+ let medalsPreventUnlock;
1175
1188
  /*
1176
1189
  LittleJS Object System
1177
1190
  */
@@ -1217,7 +1230,7 @@ class EngineObject
1217
1230
  constructor(pos=vec2(), size=objectDefaultSize, tileIndex=-1, tileSize=tileSizeDefault, angle=0, color, renderOrder=0)
1218
1231
  {
1219
1232
  // set passed in params
1220
- ASSERT(pos && pos.x != undefined && size.x != undefined); // ensure pos and size are vec2s
1233
+ ASSERT(isVector2(pos) && isVector2(size)); // ensure pos and size are vec2s
1221
1234
 
1222
1235
  /** @property {Vector2} - World space position of the object */
1223
1236
  this.pos = pos.copy();
@@ -1283,9 +1296,9 @@ class EngineObject
1283
1296
 
1284
1297
  // apply physics
1285
1298
  const oldPos = this.pos.copy();
1286
- this.pos.x += this.velocity.x = this.damping * this.velocity.x;
1287
- this.pos.y += this.velocity.y = this.damping * this.velocity.y + gravity * this.gravityScale;
1288
- this.angle += this.angleVelocity *= this.angleDamping;
1299
+ this.velocity.y += gravity * this.gravityScale;
1300
+ this.pos.x += this.velocity.x *= this.damping;
1301
+ this.pos.y += this.velocity.y *= this.damping;
1289
1302
 
1290
1303
  // physics sanity checks
1291
1304
  ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
@@ -1307,7 +1320,7 @@ class EngineObject
1307
1320
  if (this.collideSolidObjects)
1308
1321
  {
1309
1322
  // check collisions against solid objects
1310
- const epsilon = 1e-3; // necessary to push slightly outside of the collision
1323
+ const epsilon = .001; // necessary to push slightly outside of the collision
1311
1324
  for (const o of engineObjectsCollide)
1312
1325
  {
1313
1326
  // non solid objects don't collide with eachother
@@ -1343,7 +1356,7 @@ class EngineObject
1343
1356
  const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
1344
1357
  const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
1345
1358
 
1346
- if (smallStepUp || isBlockedY || !isBlockedX) // resolve y collision
1359
+ if (smallStepUp | isBlockedY | !isBlockedX) // resolve y collision
1347
1360
  {
1348
1361
  // push outside object collision
1349
1362
  this.pos.y = o.pos.y + (sizeBoth.y/2 + epsilon) * sign(oldPos.y - o.pos.y);
@@ -1373,7 +1386,7 @@ class EngineObject
1373
1386
  o.velocity.y = lerp(elasticity, inelastic, elastic1);
1374
1387
  }
1375
1388
  }
1376
- if (!smallStepUp && (isBlockedX || !isBlockedY)) // resolve x collision
1389
+ if (!smallStepUp & isBlockedX) // resolve x collision
1377
1390
  {
1378
1391
  // push outside collision
1379
1392
  this.pos.x = o.pos.x + (sizeBoth.x/2 + epsilon) * sign(oldPos.x - o.pos.x);
@@ -1411,7 +1424,7 @@ class EngineObject
1411
1424
  // test which side we bounced off (or both if a corner)
1412
1425
  const isBlockedY = tileCollisionTest(new Vector2(oldPos.x, this.pos.y), this.size, this);
1413
1426
  const isBlockedX = tileCollisionTest(new Vector2(this.pos.x, oldPos.y), this.size, this);
1414
- if (isBlockedY || !isBlockedX)
1427
+ if (isBlockedY | !isBlockedX)
1415
1428
  {
1416
1429
  // set if landed on ground
1417
1430
  this.groundObject = wasMovingDown;
@@ -1570,11 +1583,6 @@ class EngineObject
1570
1583
 
1571
1584
  'use strict';
1572
1585
 
1573
- /** Tile sheet for batch rendering system
1574
- * @type {Image}
1575
- * @memberof Draw */
1576
- const tileImage = new Image();
1577
-
1578
1586
  /** The primary 2D canvas visible to the user
1579
1587
  * @type {HTMLCanvasElement}
1580
1588
  * @memberof Draw */
@@ -1600,6 +1608,14 @@ let overlayContext;
1600
1608
  * @memberof Draw */
1601
1609
  let mainCanvasSize = vec2();
1602
1610
 
1611
+ /** Tile sheet for batch rendering system
1612
+ * @type {Image}
1613
+ * @memberof Draw */
1614
+ const tileImage = new Image;
1615
+
1616
+ // Engine internal variables not exposed to documentation
1617
+ let tileImageSize, tileImageFixBleed, drawCount;
1618
+
1603
1619
  /** Convert from screen to world space coordinates
1604
1620
  * - if calling outside of render, you may need to manually set mainCanvasSize
1605
1621
  * @param {Vector2} screenPos
@@ -1624,7 +1640,7 @@ const worldToScreen = (worldPos)=>
1624
1640
 
1625
1641
  /** Draw textured tile centered in world space, with color applied if using WebGL
1626
1642
  * @param {Vector2} pos - Center of the tile in world space
1627
- * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space, width and height
1643
+ * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space
1628
1644
  * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
1629
1645
  * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
1630
1646
  * @param {Color} [color=new Color(1,1,1)] - Color to modulate with
@@ -1830,25 +1846,23 @@ function drawText(text, pos, size=1, color, lineWidth, lineColor, textAlign, fon
1830
1846
  * font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
1831
1847
  */
1832
1848
 
1849
+ // default font image created by engine
1833
1850
  let engineFontImage;
1834
1851
 
1835
1852
  class FontImage
1836
1853
  {
1837
1854
  /** Create an image font
1838
- * @param {HTMLImageElement} [image] - The image the font is stored in, if undefined the default font is used
1839
- * @param {Vector2} [tileSize=vec2(8)] - The size of the font source tiles
1855
+ * @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
1856
+ * @param {Vector2} [tileSize=vec2(8)] - Size of the font source tiles
1840
1857
  * @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
1841
1858
  * @param {Number} [startTileIndex=0] - Tile index in image where font starts
1842
1859
  * @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
1843
1860
  */
1844
1861
  constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
1845
1862
  {
1846
- if (!image && !engineFontImage)
1847
- {
1848
- // load default font image
1849
- engineFontImage = new Image();
1850
- engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
1851
- }
1863
+ // load default font image
1864
+ if (!engineFontImage)
1865
+ (engineFontImage = new Image).src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
1852
1866
 
1853
1867
  this.image = image || engineFontImage;
1854
1868
  this.tileSize = tileSize;
@@ -1913,7 +1927,7 @@ class FontImage
1913
1927
  /** Returns true if fullscreen mode is active
1914
1928
  * @return {Boolean}
1915
1929
  * @memberof Draw */
1916
- const isFullscreen =()=> document.fullscreenElement;
1930
+ const isFullscreen = ()=> document.fullscreenElement;
1917
1931
 
1918
1932
  /** Toggle fullsceen mode
1919
1933
  * @memberof Draw */
@@ -1923,16 +1937,9 @@ function toggleFullscreen()
1923
1937
  {
1924
1938
  if (document.exitFullscreen)
1925
1939
  document.exitFullscreen();
1926
- else if (document.mozCancelFullScreen)
1927
- document.mozCancelFullScreen();
1928
- }
1929
- else
1930
- {
1931
- if (document.body.webkitRequestFullScreen)
1932
- document.body.webkitRequestFullScreen();
1933
- else if (document.body.mozRequestFullScreen)
1934
- document.body.mozRequestFullScreen();
1935
1940
  }
1941
+ else if (document.body.requestFullscreen)
1942
+ document.body.requestFullscreen();
1936
1943
  }
1937
1944
 
1938
1945
  /**
@@ -1951,7 +1958,7 @@ function toggleFullscreen()
1951
1958
  * @param {Number} [device=0]
1952
1959
  * @return {Boolean}
1953
1960
  * @memberof Input */
1954
- const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1 ? 1 : 0;
1961
+ const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1;
1955
1962
 
1956
1963
  /** Returns true if device key was pressed this frame
1957
1964
  * @param {Number} key
@@ -2191,35 +2198,40 @@ const isTouchDevice = window.ontouchstart !== undefined;
2191
2198
  if (isTouchDevice)
2192
2199
  {
2193
2200
  // override mouse events
2194
- const mouseDown = onmousedown, mouseUp = onmouseup, mouseMove = onmousemove;
2195
- onmousedown = onmouseup = onmousemove = (e)=> 0;
2201
+ let wasTouching, mouseDown = onmousedown, mouseUp = onmouseup;
2202
+ onmousedown = onmouseup = ()=> 0;
2196
2203
 
2197
- // handle all touch events the same way
2198
- let wasTouching, hadTouch;
2199
- ontouchstart = ontouchmove = ontouchend = (e)=>
2204
+ // setup touch input
2205
+ ontouchstart = (e)=>
2200
2206
  {
2201
- e.button = 0; // all touches are left click
2207
+ // fix mobile audio, force it to play a sound on first touch
2208
+ zzfx(0);
2202
2209
 
2203
- // check if touching and pass to mouse events
2204
- const touching = e.touches.length;
2205
- if (touching)
2210
+ // handle all touch events the same way
2211
+ ontouchstart = ontouchmove = ontouchend = (e)=>
2206
2212
  {
2207
- // fix mobile audio, force it to play a sound on first touch
2208
- hadTouch || zzfx(0, hadTouch=1);
2213
+ e.button = 0; // all touches are left click
2209
2214
 
2210
- // set event pos and pass it along
2211
- e.x = e.touches[0].clientX;
2212
- e.y = e.touches[0].clientY;
2213
- wasTouching ? mouseMove(e) : mouseDown(e);
2214
- }
2215
- else if (wasTouching)
2216
- mouseUp(e);
2215
+ // check if touching and pass to mouse events
2216
+ const touching = e.touches.length;
2217
+ if (touching)
2218
+ {
2219
+ // set event pos and pass it along
2220
+ e.x = e.touches[0].clientX;
2221
+ e.y = e.touches[0].clientY;
2222
+ wasTouching ? onmousemove(e) : mouseDown(e);
2223
+ }
2224
+ else if (wasTouching)
2225
+ mouseUp(e);
2226
+
2227
+ // set was touching
2228
+ wasTouching = touching;
2217
2229
 
2218
- // set was touching
2219
- wasTouching = touching;
2230
+ // must return true so the document will get focus
2231
+ return true;
2232
+ }
2220
2233
 
2221
- // must return true so the document will get focus
2222
- return true;
2234
+ return ontouchstart(e);
2223
2235
  }
2224
2236
  }
2225
2237
 
@@ -2227,7 +2239,7 @@ if (isTouchDevice)
2227
2239
  // touch gamepad, virtual on screen gamepad emulator for touch devices
2228
2240
 
2229
2241
  // touch input internal variables
2230
- let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2();
2242
+ let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
2231
2243
 
2232
2244
  // create the touch gamepad, called automatically by the engine
2233
2245
  function touchGamepadCreate()
@@ -2235,65 +2247,73 @@ function touchGamepadCreate()
2235
2247
  if (!touchGamepadEnable || !isTouchDevice)
2236
2248
  return;
2237
2249
 
2238
- ontouchstart = ontouchmove = ontouchend = (e)=>
2250
+ // touch input internal variables
2251
+ touchGamepadButtons = [];
2252
+ touchGamepadStick = vec2();
2253
+
2254
+ // setup touch input
2255
+ ontouchstart = (e)=>
2239
2256
  {
2240
- if (!touchGamepadEnable)
2241
- return;
2257
+ // fix mobile audio, force it to play a sound on first touch
2258
+ zzfx(0);
2242
2259
 
2243
- // clear touch gamepad input
2244
- touchGamepadStick = vec2();
2245
- touchGamepadButtons = [];
2246
-
2247
- const touching = e.touches.length;
2248
- if (touching)
2260
+ ontouchstart = ontouchmove = ontouchend = (e)=>
2249
2261
  {
2250
- touchGamepadTimer.isSet() || zzfx(0) ; // fix mobile audio, force it to play a sound the first time
2251
-
2252
- // set that gamepad is active
2253
- isUsingGamepad = 1;
2254
- touchGamepadTimer.set();
2255
-
2256
- if (paused)
2262
+ // clear touch gamepad input
2263
+ touchGamepadStick = vec2();
2264
+ touchGamepadButtons = [];
2265
+
2266
+ const touching = e.touches.length;
2267
+ if (touching)
2257
2268
  {
2258
- // touch anywhere to press start when paused
2259
- touchGamepadButtons[9] = 1;
2260
- return;
2269
+ // set that gamepad is active
2270
+ isUsingGamepad = 1;
2271
+ touchGamepadTimer.set();
2272
+
2273
+ if (paused)
2274
+ {
2275
+ // touch anywhere to press start when paused
2276
+ touchGamepadButtons[9] = 1;
2277
+ return;
2278
+ }
2261
2279
  }
2262
- }
2263
2280
 
2264
- // get center of left and right sides
2265
- const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2266
- const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
2267
- const startCenter = mainCanvasSize.scale(.5);
2281
+ // get center of left and right sides
2282
+ const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2283
+ const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
2284
+ const startCenter = mainCanvasSize.scale(.5);
2268
2285
 
2269
- // check each touch point
2270
- for (const touch of e.touches)
2271
- {
2272
- const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
2273
- if (touchPos.distance(stickCenter) < touchGamepadSize)
2286
+ // check each touch point
2287
+ for (const touch of e.touches)
2274
2288
  {
2275
- // virtual analog stick
2276
- if (touchGamepadAnalog)
2277
- touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
2278
- else
2289
+ const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
2290
+ if (touchPos.distance(stickCenter) < touchGamepadSize)
2279
2291
  {
2280
- // 8 way dpad
2281
- const angle = touchPos.subtract(stickCenter).angle();
2282
- touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
2292
+ // virtual analog stick
2293
+ if (touchGamepadAnalog)
2294
+ touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
2295
+ else
2296
+ {
2297
+ // 8 way dpad
2298
+ const angle = touchPos.subtract(stickCenter).angle();
2299
+ touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
2300
+ }
2301
+ }
2302
+ else if (touchPos.distance(buttonCenter) < touchGamepadSize)
2303
+ {
2304
+ // virtual face buttons
2305
+ const button = touchPos.subtract(buttonCenter).direction();
2306
+ touchGamepadButtons[button] = 1;
2307
+ }
2308
+ else if (touchPos.distance(startCenter) < touchGamepadSize)
2309
+ {
2310
+ // virtual start button in center
2311
+ touchGamepadButtons[9] = 1;
2283
2312
  }
2284
- }
2285
- else if (touchPos.distance(buttonCenter) < touchGamepadSize)
2286
- {
2287
- // virtual face buttons
2288
- const button = touchPos.subtract(buttonCenter).direction();
2289
- touchGamepadButtons[button] = 1;
2290
- }
2291
- else if (touchPos.distance(startCenter) < touchGamepadSize)
2292
- {
2293
- // virtual start button in center
2294
- touchGamepadButtons[9] = 1;
2295
2313
  }
2296
2314
  }
2315
+
2316
+ return ontouchstart(e);
2297
2317
  }
2298
2318
  }
2299
2319
 
@@ -2411,7 +2431,7 @@ class Sound
2411
2431
  {
2412
2432
  if (!soundEnable) return;
2413
2433
 
2414
- let pan = 0;
2434
+ let pan;
2415
2435
  if (pos)
2416
2436
  {
2417
2437
  const range = this.range;
@@ -2441,7 +2461,7 @@ class Sound
2441
2461
  * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
2442
2462
  * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
2443
2463
  */
2444
- playNote(semitoneOffset, pos, volume=1)
2464
+ playNote(semitoneOffset, pos, volume)
2445
2465
  {
2446
2466
  if (!soundEnable) return;
2447
2467
 
@@ -2496,7 +2516,7 @@ class Music
2496
2516
  * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
2497
2517
  * @return {AudioBufferSourceNode} - The audio node, can be used to stop sound later
2498
2518
  */
2499
- play(volume = 1, loop = 1)
2519
+ play(volume, loop = 1)
2500
2520
  {
2501
2521
  if (!soundEnable) return;
2502
2522
 
@@ -2578,7 +2598,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
2578
2598
 
2579
2599
  // create audio context
2580
2600
  if (!audioContext)
2581
- audioContext = new (window.AudioContext||webkitAudioContext);
2601
+ audioContext = new AudioContext;
2582
2602
 
2583
2603
  // fix stalled audio
2584
2604
  audioContext.resume();
@@ -2597,18 +2617,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
2597
2617
  source.playbackRate.value = rate;
2598
2618
  source.loop = loop;
2599
2619
 
2600
- // create and connect gain node (createGain is more widley spported then GainNode construtor)
2620
+ // create and connect gain node (createGain is more widely spported then GainNode construtor)
2601
2621
  const gainNode = audioContext.createGain();
2602
2622
  gainNode.gain.value = soundVolume*volume;
2603
2623
  gainNode.connect(audioContext.destination);
2604
2624
 
2605
- // connect source to gain
2606
- (
2607
- window.StereoPannerNode ? // create pan node if possible
2608
- source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)}))
2609
- : source
2610
- )
2611
- .connect(gainNode);
2625
+ // connect source to stereo panner and gain
2626
+ source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)})).connect(gainNode);
2612
2627
 
2613
2628
  // play and return sound
2614
2629
  source.start();
@@ -2726,10 +2741,8 @@ function zzfxG
2726
2741
  * @memberof Audio */
2727
2742
  function zzfxM(instruments, patterns, sequence, BPM = 125)
2728
2743
  {
2744
+ let i, j, k;
2729
2745
  let instrumentParameters;
2730
- let i;
2731
- let j;
2732
- let k;
2733
2746
  let note;
2734
2747
  let sample;
2735
2748
  let patternChannel;
@@ -2899,14 +2912,11 @@ function tileCollisionRaycast(posStart, posEnd, object)
2899
2912
  {
2900
2913
  // test if a ray collides with tiles from start to end
2901
2914
  // todo: a way to get the exact hit point, it must still register as inside the hit tile
2902
- posStart = posStart.floor();
2903
- posEnd = posEnd.floor();
2904
- const posDelta = posEnd.subtract(posStart);
2915
+ const posDelta = (posEnd = posEnd.floor()).subtract(posStart = posStart.floor());
2905
2916
  const dx = abs(posDelta.x), dy = -abs(posDelta.y);
2906
2917
  const sx = sign(posDelta.x), sy = sign(posDelta.y);
2907
- let e = dx + dy;
2908
2918
 
2909
- for (let x = posStart.x, y = posStart.y;;)
2919
+ for (let x = posStart.x, y = posStart.y, e = dx + dy;;)
2910
2920
  {
2911
2921
  const tileData = getTileCollisionData(vec2(x,y));
2912
2922
  if (tileData && (object ? object.collideWithTileRaycast(tileData, new Vector2(x, y)) : tileData > 0))
@@ -2998,7 +3008,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
2998
3008
  // init tile data
2999
3009
  this.data = [];
3000
3010
  for (let j = this.size.area(); j--;)
3001
- this.data.push(new TileLayerData());
3011
+ this.data.push(new TileLayerData);
3002
3012
  }
3003
3013
 
3004
3014
  /** Set data at a given position in the array
@@ -3054,21 +3064,23 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3054
3064
  * @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
3055
3065
  redrawStart(clear = 0)
3056
3066
  {
3057
- if (clear)
3058
- {
3059
- // clear and set size
3060
- this.canvas.width = this.size.x * this.tileSize.x;
3061
- this.canvas.height = this.size.y * this.tileSize.y;
3062
- }
3063
-
3064
3067
  // save current render settings
3065
- this.savedRenderSettings = [mainCanvas, mainContext, cameraPos, cameraScale];
3068
+ this.savedRenderSettings = [mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale];
3066
3069
 
3067
- // use normal rendering system to render the tiles
3070
+ // hack: use normal rendering system to render the tiles
3068
3071
  mainCanvas = this.canvas;
3069
3072
  mainContext = this.context;
3070
3073
  cameraPos = this.size.scale(.5);
3071
3074
  cameraScale = this.tileSize.x;
3075
+
3076
+ if (clear)
3077
+ {
3078
+ // clear and set size
3079
+ mainCanvas.width = this.size.x * this.tileSize.x;
3080
+ mainCanvas.height = this.size.y * this.tileSize.y;
3081
+ }
3082
+
3083
+ // begin a new render for the tile canvas
3072
3084
  enginePreRender();
3073
3085
  }
3074
3086
 
@@ -3080,7 +3092,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3080
3092
  //debugSaveCanvas(this.canvas);
3081
3093
 
3082
3094
  // set stuff back to normal
3083
- [mainCanvas, mainContext, cameraPos, cameraScale] = this.savedRenderSettings;
3095
+ [mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale] = this.savedRenderSettings;
3084
3096
  }
3085
3097
 
3086
3098
  /** Draw the tile at a given position
@@ -3321,7 +3333,7 @@ class ParticleEmitter extends EngineObject
3321
3333
  this.parent && super.update();
3322
3334
 
3323
3335
  // update emitter
3324
- if (!this.emitTime || this.getAliveTime() <= this.emitTime)
3336
+ if (!this.emitTime | this.getAliveTime() <= this.emitTime)
3325
3337
  {
3326
3338
  // emit particles
3327
3339
  if (this.emitRate * particleEmitRateScale)
@@ -3345,7 +3357,7 @@ class ParticleEmitter extends EngineObject
3345
3357
  let pos = this.emitSize.x != undefined ? // check if vec2 was used for size
3346
3358
  (new Vector2(rand(-.5,.5), rand(-.5,.5)))
3347
3359
  .multiply(this.emitSize).rotate(this.angle) // box emitter
3348
- : randInCircle(this.emitSize * .5); // circle emitter
3360
+ : randInCircle(this.emitSize/2); // circle emitter
3349
3361
  let angle = rand(this.particleConeAngle, -this.particleConeAngle);
3350
3362
  if (!this.localSpace)
3351
3363
  {
@@ -3388,7 +3400,7 @@ class ParticleEmitter extends EngineObject
3388
3400
  particle.additive = this.additive;
3389
3401
  particle.renderOrder = this.renderOrder;
3390
3402
  particle.trailScale = this.trailScale;
3391
- particle.mirror = rand()<.5;
3403
+ particle.mirror = randInt(2);
3392
3404
  particle.localSpaceEmitter = this.localSpace && this;
3393
3405
 
3394
3406
  // setup callbacks for particles
@@ -3417,7 +3429,8 @@ class Particle extends EngineObject
3417
3429
  * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
3418
3430
  * @param {Number} [angle=0] - Angle to rotate the particle
3419
3431
  */
3420
- constructor(pos, tileIndex, tileSize, angle) { super(pos, new Vector2, tileIndex, tileSize, angle); }
3432
+ constructor(pos, tileIndex, tileSize, angle)
3433
+ { super(pos, new Vector2, tileIndex, tileSize, angle); }
3421
3434
 
3422
3435
  /** Render the particle, automatically called each frame, sorted by renderOrder */
3423
3436
  render()
@@ -3486,10 +3499,6 @@ class Particle extends EngineObject
3486
3499
  * @memberof Medals */
3487
3500
  const medals = [];
3488
3501
 
3489
- /** Set to stop medals from being unlockable (like if cheats are enabled)
3490
- * @memberof Medals */
3491
- let medalsPreventUnlock;
3492
-
3493
3502
  // Engine internal variables not exposed to documentation
3494
3503
  let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
3495
3504
 
@@ -3537,9 +3546,8 @@ class Medal
3537
3546
  this.name = name;
3538
3547
  this.description = description;
3539
3548
  this.icon = icon;
3540
- this.image = new Image();
3541
3549
  if (src)
3542
- this.image.src = src;
3550
+ (this.image = new Image).src = src;
3543
3551
  }
3544
3552
 
3545
3553
  /** Unlocks a medal if not already unlocked */
@@ -3552,10 +3560,7 @@ class Medal
3552
3560
  ASSERT(medalsSaveName); // save name must be set
3553
3561
  localStorage[this.storageKey()] = this.unlocked = 1;
3554
3562
  medalsDisplayQueue.push(this);
3555
-
3556
- // save for newgrounds and OS13K
3557
3563
  newgrounds && newgrounds.unlockMedal(this.id);
3558
- localStorage['OS13kTrophy,' + this.icon + ',' + medalsSaveName + ',' + this.name] = this.description;
3559
3564
  }
3560
3565
 
3561
3566
  /** Render a medal
@@ -3564,27 +3569,26 @@ class Medal
3564
3569
  render(hidePercent=0)
3565
3570
  {
3566
3571
  const context = overlayContext;
3567
- const width = min(medalDisplayWidth, mainCanvas.width);
3572
+ const width = min(medalDisplaySize.x, mainCanvas.width);
3568
3573
  const x = overlayCanvas.width - width;
3569
- const y = -medalDisplayHeight*hidePercent;
3574
+ const y = -medalDisplaySize.y*hidePercent;
3570
3575
 
3571
3576
  // draw containing rect and clip to that region
3572
3577
  context.save();
3573
3578
  context.beginPath();
3574
- context.fillStyle = '#ddd'
3575
- context.fill(context.rect(x, y, width, medalDisplayHeight));
3576
- context.strokeStyle = '#000';
3579
+ context.fillStyle = new Color(.9,.9,.9);
3580
+ context.strokeStyle = new Color(0,0,0);
3577
3581
  context.lineWidth = 3;
3582
+ context.fill(context.rect(x, y, width, medalDisplaySize.y));
3578
3583
  context.stroke();
3579
3584
  context.clip();
3580
3585
 
3581
3586
  // draw the icon and text
3582
- this.renderIcon(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2);
3583
- context.textAlign = 'left';
3584
- context.font = '38px '+ fontDefault;
3585
- context.fillText(this.name, x+medalDisplayIconSize+30, y+28);
3586
- context.font = '24px '+ fontDefault;
3587
- context.fillText(this.description, x+medalDisplayIconSize+30, y+60);
3587
+ this.renderIcon(vec2(x+15+medalDisplayIconSize/2, y+medalDisplaySize.y/2));
3588
+ const pos = vec2(x+medalDisplayIconSize+30, y+28);
3589
+ drawTextScreen(this.name, pos, 38, new Color(0,0,0), 0, 0, 'left');
3590
+ pos.y += 32;
3591
+ drawTextScreen(this.description, pos, 24, new Color(0,0,0), 0, 0, 'left');
3588
3592
  context.restore();
3589
3593
  }
3590
3594
 
@@ -3593,18 +3597,13 @@ class Medal
3593
3597
  * @param {Number} y - Screen space Y position
3594
3598
  * @param {Number} [size=medalDisplayIconSize] - Screen space size
3595
3599
  */
3596
- renderIcon(x, y, size=medalDisplayIconSize)
3600
+ renderIcon(pos, size=medalDisplayIconSize)
3597
3601
  {
3598
3602
  // draw the image or icon
3599
- const context = overlayContext;
3600
- context.fillStyle = '#000';
3601
- context.textAlign = 'center';
3602
- context.textBaseline = 'middle';
3603
- context.font = size*.7 + 'px '+ fontDefault;
3604
- if (this.image.src)
3605
- context.drawImage(this.image, x-size/2, y-size/2, size, size);
3603
+ if (this.image)
3604
+ overlayContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
3606
3605
  else
3607
- context.fillText(this.icon, x, y); // show icon if there is no image
3606
+ drawTextScreen(this.icon, pos, size*.7, new Color(0,0,0));
3608
3607
  }
3609
3608
 
3610
3609
  // Get local storage key used by the medal
@@ -3662,18 +3661,20 @@ class Newgrounds
3662
3661
  constructor(app_id, cipher)
3663
3662
  {
3664
3663
  ASSERT(!newgrounds && app_id);
3664
+
3665
3665
  this.app_id = app_id;
3666
3666
  this.cipher = cipher;
3667
3667
  this.host = location ? location.hostname : '';
3668
-
3668
+
3669
3669
  // create an instance of CryptoJS for encrypted calls
3670
- cipher && (this.cryptoJS = CryptoJS());
3670
+ if (cipher)
3671
+ this.cryptoJS = this.CryptoJS();
3671
3672
 
3672
3673
  // get session id from url search params
3673
3674
  const url = new URL(location.href);
3674
- this.session_id = url.searchParams.get('ngio_session_id') || 0;
3675
+ this.session_id = url.searchParams.get('ngio_session_id');
3675
3676
 
3676
- if (this.session_id == 0)
3677
+ if (!this.session_id)
3677
3678
  return; // only use newgrounds when logged in
3678
3679
 
3679
3680
  // get medals
@@ -3686,6 +3687,7 @@ class Newgrounds
3686
3687
  if (medal)
3687
3688
  {
3688
3689
  // copy newgrounds medal data
3690
+ medal.image = new Image;
3689
3691
  medal.image.src = newgroundsMedal['icon'];
3690
3692
  medal.name = newgroundsMedal['name'];
3691
3693
  medal.description = newgroundsMedal['description'];
@@ -3770,13 +3772,16 @@ class Newgrounds
3770
3772
  debugMedals && console.log(xmlHttp.responseText);
3771
3773
  return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
3772
3774
  }
3773
- }
3774
3775
 
3775
- ///////////////////////////////////////////////////////////////////////////////
3776
- // Crypto-JS - https://github.com/brix/crypto-js [The MIT License (MIT)]
3777
- // Copyright (c) 2009-2013 Jeff Mott Copyright (c) 2013-2016 Evan Vosberg
3776
+ CryptoJS()
3777
+ {
3778
+ ///////////////////////////////////////////////////////////////////////////////
3779
+ // Crypto-JS - https://github.com/brix/crypto-js [The MIT License (MIT)]
3780
+ // Copyright (c) 2009-2013 Jeff Mott Copyright (c) 2013-2016 Evan Vosberg
3778
3781
 
3779
- const CryptoJS=()=>eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",...']charCodeAtUinyxpf',"for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h<t,o=o%h+(i?t:h-t)*(o>>17)-!i*t,f.map((n,x)=>(U=r[n]+=(i*h/2-r[n]<<13)/((C[n]+=C[n]<5)+1/20)>>13,a[x]+=y[x]*(i-t/h))),p=p*2+i)for(f='010202103203210431053105410642065206541'.split(t=0).map((n,x)=>(U=0,[...n].map((n,x)=>(U=U*997+(c[e-n]|0)|0)),h*32-1&U*997+p+!!A*129)*12+x);o<h*32;o=o*64|M.charCodeAt(d++)&63);for(C=String.fromCharCode(...c);r=/[\0-#?@\\\\~]/.exec(C);)with(C.split(r))C=join(shift());return C")([],[],1<<17,[0,0,0,0,0,0,0,0,0,0,0,0],new Uint16Array(51e6).fill(1<<15),new Uint8Array(51e6),0,0,0,0));
3782
+ return eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",...']charCodeAtUinyxpf',"for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h<t,o=o%h+(i?t:h-t)*(o>>17)-!i*t,f.map((n,x)=>(U=r[n]+=(i*h/2-r[n]<<13)/((C[n]+=C[n]<5)+1/20)>>13,a[x]+=y[x]*(i-t/h))),p=p*2+i)for(f='010202103203210431053105410642065206541'.split(t=0).map((n,x)=>(U=0,[...n].map((n,x)=>(U=U*997+(c[e-n]|0)|0)),h*32-1&U*997+p+!!A*129)*12+x);o<h*32;o=o*64|M.charCodeAt(d++)&63);for(C=String.fromCharCode(...c);r=/[\0-#?@\\\\~]/.exec(C);)with(C.split(r))C=join(shift());return C")([],[],1<<17,[0,0,0,0,0,0,0,0,0,0,0,0],new Uint16Array(51e6).fill(1<<15),new Uint8Array(51e6),0,0,0,0));
3783
+ }
3784
+ }
3780
3785
  /**
3781
3786
  * LittleJS WebGL Interface
3782
3787
  * <br> - All webgl used by the engine is wrapped up here
@@ -3806,7 +3811,7 @@ let glContext;
3806
3811
  let glTileTexture;
3807
3812
 
3808
3813
  // WebGL internal variables not exposed to documentation
3809
- let glActiveTexture, glShader, glArrayBuffer, glVertexData, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
3814
+ let glActiveTexture, glShader, glArrayBuffer, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
3810
3815
 
3811
3816
  ///////////////////////////////////////////////////////////////////////////////
3812
3817
 
@@ -3827,27 +3832,25 @@ function glInit()
3827
3832
  'uniform mat4 m;'+ // transform matrix
3828
3833
  'attribute vec2 p,t;'+ // position, uv
3829
3834
  'attribute vec4 c,a;'+ // color, additiveColor
3830
- 'varying vec2 v;'+ // return uv
3831
- 'varying vec4 d,e;'+ // return color, additiveColor
3835
+ 'varying vec4 v,d,e;'+ // return uv, color, additiveColor
3832
3836
  'void main(){'+ // shader entry point
3833
3837
  'gl_Position=m*vec4(p,1,1);'+ // transform position
3834
- 'v=t;d=c;e=a;'+ // pass stuff to fragment shader
3838
+ 'v=vec4(t,p);d=c;e=a;'+ // pass stuff to fragment shader
3835
3839
  '}' // end of shader
3836
3840
  ,
3837
- 'precision highp float;'+ // use highp for better accuracy
3838
- 'varying vec2 v;'+ // uv
3839
- 'varying vec4 d,e;'+ // color, additiveColor
3840
- 'uniform sampler2D s;'+ // texture
3841
- 'void main(){'+ // shader entry point
3842
- 'gl_FragColor=texture2D(s,v)*d+e;'+ // modulate texture by color plus additive
3843
- '}' // end of shader
3841
+ 'precision highp float;'+ // use highp for better accuracy
3842
+ 'varying vec4 v,d,e;'+ // uv, color, additiveColor
3843
+ 'uniform sampler2D s;'+ // texture
3844
+ 'void main(){'+ // shader entry point
3845
+ 'gl_FragColor=texture2D(s,v.xy)*d+e;'+ // modulate texture by color plus additive
3846
+ '}' // end of shader
3844
3847
  );
3845
3848
 
3846
3849
  // init buffers
3847
- glVertexData = new ArrayBuffer(gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE);
3850
+ const vertexData = new ArrayBuffer(gl_VERTEX_BUFFER_SIZE);
3848
3851
  glArrayBuffer = glContext.createBuffer();
3849
- glPositionData = new Float32Array(glVertexData);
3850
- glColorData = new Uint32Array(glVertexData);
3852
+ glPositionData = new Float32Array(vertexData);
3853
+ glColorData = new Uint32Array(vertexData);
3851
3854
  glBatchCount = 0;
3852
3855
  }
3853
3856
 
@@ -3922,18 +3925,19 @@ function glCreateTexture(image)
3922
3925
  image && image.width && glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
3923
3926
 
3924
3927
  // use point filtering for pixelated rendering
3925
- glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, cavasPixelated ? gl_NEAREST : gl_LINEAR);
3926
- glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, cavasPixelated ? gl_NEAREST : gl_LINEAR);
3928
+ const filter = cavasPixelated ? gl_NEAREST : gl_LINEAR;
3929
+ glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
3930
+ glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
3927
3931
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE);
3928
3932
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE);
3929
3933
  return texture;
3930
3934
  }
3931
3935
 
3932
3936
  // called automatically by engine before render
3933
- function glPreRender(width, height, cameraX, cameraY, cameraScale)
3937
+ function glPreRender()
3934
3938
  {
3935
3939
  // clear and set to same size as main canvas
3936
- glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
3940
+ glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
3937
3941
  glContext.clear(gl_COLOR_BUFFER_BIT);
3938
3942
 
3939
3943
  // set up the shader
@@ -3941,7 +3945,7 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
3941
3945
  glContext.activeTexture(gl_TEXTURE0);
3942
3946
  glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
3943
3947
  glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
3944
- glContext.bufferData(gl_ARRAY_BUFFER, glVertexData.byteLength, gl_DYNAMIC_DRAW);
3948
+ glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
3945
3949
  glSetBlendMode();
3946
3950
 
3947
3951
  // set vertex attributes
@@ -3959,14 +3963,14 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
3959
3963
  initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
3960
3964
 
3961
3965
  // build the transform matrix
3962
- const sx = 2 * cameraScale / width;
3963
- const sy = 2 * cameraScale / height;
3966
+ const sx = 2 * cameraScale / mainCanvas.width;
3967
+ const sy = 2 * cameraScale / mainCanvas.height;
3964
3968
  glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
3965
3969
  new Float32Array([
3966
3970
  sx, 0, 0, 0,
3967
3971
  0, sy, 0, 0,
3968
3972
  1, 1, -1, 1,
3969
- -1-sx*cameraX, -1-sy*cameraY, 0, 0
3973
+ -1-sx*cameraPos.x, -1-sy*cameraPos.y, 0, 0
3970
3974
  ])
3971
3975
  );
3972
3976
  }
@@ -4014,10 +4018,10 @@ function glCopyToContext(context, forceDraw)
4014
4018
  * @param uv0Y
4015
4019
  * @param uv1X
4016
4020
  * @param uv1Y
4017
- * @param [rgba=0xffffffff]
4021
+ * @param rgba
4018
4022
  * @param [rgbaAdditive=0]
4019
4023
  * @memberof WebGL */
4020
- function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffffff, rgbaAdditive=0)
4024
+ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdditive=0)
4021
4025
  {
4022
4026
  // flush if there is no room for more verts or if different blend mode
4023
4027
  if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
@@ -4028,43 +4032,16 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffff
4028
4032
  const cx = c*sizeX, cy = c*sizeY, sx = s*sizeX, sy = s*sizeY;
4029
4033
 
4030
4034
  // setup 2 triangles to form a quad
4031
- let offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT;
4032
-
4033
- // vertex 0
4034
- glPositionData[offset++] = x - cx - sy;
4035
- glPositionData[offset++] = y - cy + sx;
4036
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
4037
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
4038
-
4039
- // vertex 1
4040
- glPositionData[offset++] = x + cx + sy;
4041
- glPositionData[offset++] = y + cy - sx;
4042
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
4043
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
4044
-
4045
- // vertex 2
4046
- glPositionData[offset++] = x - cx + sy;
4047
- glPositionData[offset++] = y + cy + sx;
4048
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
4049
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
4050
-
4051
- // vertex 0
4052
- glPositionData[offset++] = x - cx - sy;
4053
- glPositionData[offset++] = y - cy + sx;
4054
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
4055
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
4056
-
4057
- // vertex 3
4058
- glPositionData[offset++] = x + cx - sy;
4059
- glPositionData[offset++] = y - cy - sx;
4060
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
4061
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
4062
-
4063
- // vertex 1
4064
- glPositionData[offset++] = x + cx + sy;
4065
- glPositionData[offset++] = y + cy - sx;
4066
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
4067
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
4035
+ for(let i=6, offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT; i--;)
4036
+ {
4037
+ const a = i-4&&i>1, b = i-5&&i-2&&i-1;
4038
+ glPositionData[offset++] = x + (a?-cx:cx) + (b?sy:-sy);
4039
+ glPositionData[offset++] = y + (b?cy:-cy) + (a?sx:-sx);
4040
+ glPositionData[offset++] = a ? uv0X : uv1X;
4041
+ glPositionData[offset++] = b ? uv0Y : uv1Y;
4042
+ glColorData[offset++] = rgba;
4043
+ glColorData[offset++] = rgbaAdditive;
4044
+ }
4068
4045
  }
4069
4046
 
4070
4047
  ///////////////////////////////////////////////////////////////////////////////
@@ -4116,14 +4093,13 @@ function glRenderPostProcess()
4116
4093
  return;
4117
4094
 
4118
4095
  // prepare to render post process shader
4119
- const width = mainCanvas.width, height = mainCanvas.height;
4120
4096
  if (glEnable)
4121
4097
  {
4122
4098
  glFlush(); // clear out the buffer
4123
4099
  mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
4124
4100
  }
4125
- else
4126
- glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height); // set viewport
4101
+ else // set viewport
4102
+ glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
4127
4103
 
4128
4104
  // setup shader program to draw one triangle
4129
4105
  glContext.useProgram(glPostShader);
@@ -4147,7 +4123,7 @@ function glRenderPostProcess()
4147
4123
  const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
4148
4124
  glContext.uniform1i(uniformLocation('iChannel0'), 0);
4149
4125
  glContext.uniform1f(uniformLocation('iTime'), time);
4150
- glContext.uniform3f(uniformLocation('iResolution'), width, height, 1);
4126
+ glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
4151
4127
  glContext.drawArrays(gl_TRIANGLES, 0, 3);
4152
4128
  }
4153
4129
 
@@ -4187,7 +4163,8 @@ gl_UNPACK_FLIP_Y_WEBGL = 37440,
4187
4163
  gl_VERTICES_PER_QUAD = 6,
4188
4164
  gl_INDICIES_PER_VERT = 6,
4189
4165
  gl_MAX_BATCH = 1<<16,
4190
- gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2
4166
+ gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
4167
+ gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE;
4191
4168
  /*
4192
4169
  LittleJS - The Tiny JavaScript Game Engine That Can!
4193
4170
  MIT License - Copyright 2021 Frank Force
@@ -4203,6 +4180,7 @@ gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2
4203
4180
  - Particle effect system
4204
4181
  - Medal system tracks and displays achievements
4205
4182
  - Debug tools and debug rendering system
4183
+ - Post processing effects
4206
4184
  - Call engineInit() to start it up!
4207
4185
  */
4208
4186
 
@@ -4212,7 +4190,7 @@ gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2
4212
4190
  const engineName = 'LittleJS';
4213
4191
 
4214
4192
  /** Version of engine */
4215
- const engineVersion = '1.4.7';
4193
+ const engineVersion = '1.4.9';
4216
4194
 
4217
4195
  /** Frames per second to update objects
4218
4196
  * @default */
@@ -4237,14 +4215,13 @@ let time = 0;
4237
4215
  /** Actual clock time since start in seconds (not affected by pause or frame rate clamping) */
4238
4216
  let timeReal = 0;
4239
4217
 
4240
- /** Is the game paused? Causes time and objects to not be updated. */
4218
+ /** Is the game paused? Causes time and objects to not be updated */
4241
4219
  let paused = 0;
4242
4220
 
4243
- // Engine internal variables not exposed to documentation
4244
- let tileImageSize, tileImageFixBleed;
4245
-
4246
- // Engine stat tracking, if showWatermark is true
4247
- let averageFPS, drawCount;
4221
+ /** Set if game is paused
4222
+ * @param {Boolean} paused
4223
+ */
4224
+ function setPaused(_paused) { paused = _paused; }
4248
4225
 
4249
4226
  ///////////////////////////////////////////////////////////////////////////////
4250
4227
 
@@ -4265,13 +4242,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4265
4242
  tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
4266
4243
  debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
4267
4244
 
4268
- // setup css
4245
+ // setup html
4269
4246
  const styleBody = 'margin:0;overflow:hidden;background:#000' + // fill the window
4270
4247
  ';touch-action:none' + // prevent mobile pinch to resize
4271
4248
  ';user-select:none' + // prevent mobile hold to select
4272
- ';-webkit-user-select:none;-moz-user-select:none'; // compatibility for mobile
4273
-
4274
- // setup html
4249
+ ';-webkit-user-select:none'; // compatibility for ios
4275
4250
  document.body.style = styleBody;
4276
4251
  document.body.appendChild(mainCanvas = document.createElement('canvas'));
4277
4252
  mainContext = mainCanvas.getContext('2d');
@@ -4286,7 +4261,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4286
4261
 
4287
4262
  // set canvas style to fill the window
4288
4263
  const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
4289
- (glCanvas||mainCanvas).style = overlayCanvas.style = mainCanvas.style = styleCanvas;
4264
+ (glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
4290
4265
 
4291
4266
  gameInit();
4292
4267
  touchGamepadCreate();
@@ -4294,16 +4269,16 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4294
4269
  };
4295
4270
 
4296
4271
  // frame time tracking
4297
- let frameTimeLastMS = 0, frameTimeBufferMS = 0;
4272
+ let frameTimeLastMS = 0, frameTimeBufferMS, averageFPS;
4298
4273
 
4299
4274
  // main update loop
4300
- const engineUpdate = (frameTimeMS=0)=>
4275
+ function engineUpdate(frameTimeMS=0)
4301
4276
  {
4302
4277
  // update time keeping
4303
4278
  let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
4304
4279
  frameTimeLastMS = frameTimeMS;
4305
4280
  if (debug || showWatermark)
4306
- averageFPS = lerp(.05, averageFPS || 0, 1e3/(frameTimeDeltaMS||1));
4281
+ averageFPS = lerp(.05, averageFPS, 1e3/(frameTimeDeltaMS||1));
4307
4282
  const debugSpeedUp = debug && keyIsDown(107); // +
4308
4283
  const debugSpeedDown = debug && keyIsDown(109); // -
4309
4284
  if (debug)
@@ -4315,24 +4290,19 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4315
4290
 
4316
4291
  if (canvasFixedSize.x)
4317
4292
  {
4318
- // clear set fixed size
4293
+ // clear canvas and set fixed size
4319
4294
  overlayCanvas.width = mainCanvas.width = canvasFixedSize.x;
4320
4295
  overlayCanvas.height = mainCanvas.height = canvasFixedSize.y;
4321
4296
 
4322
4297
  // fit to window by adding space on top or bottom if necessary
4323
4298
  const aspect = innerWidth / innerHeight;
4324
4299
  const fixedAspect = mainCanvas.width / mainCanvas.height;
4325
- mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
4326
- mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
4327
- if (glCanvas)
4328
- {
4329
- glCanvas.style.width = mainCanvas.style.width;
4330
- glCanvas.style.height = mainCanvas.style.height;
4331
- }
4300
+ (glCanvas||mainCanvas).style.width = mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
4301
+ (glCanvas||mainCanvas).style.height = mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
4332
4302
  }
4333
4303
  else
4334
4304
  {
4335
- // clear and set size to same as window
4305
+ // clear canvas and set size to same as window
4336
4306
  overlayCanvas.width = mainCanvas.width = min(innerWidth, canvasMaxSize.x);
4337
4307
  overlayCanvas.height = mainCanvas.height = min(innerHeight, canvasMaxSize.y);
4338
4308
  }
@@ -4399,7 +4369,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4399
4369
  overlayContext.fillStyle = '#000';
4400
4370
  const text = engineName + ' ' + 'v' + engineVersion + ' / '
4401
4371
  + drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
4402
- + ' ' + (glEnable ? 'GL' : '2D') ;
4372
+ + (glEnable ? ' GL' : ' 2D') ;
4403
4373
  overlayContext.fillText(text, mainCanvas.width-3, 3);
4404
4374
  overlayContext.fillStyle = '#fff';
4405
4375
  overlayContext.fillText(text, mainCanvas.width-2, 2);
@@ -4413,7 +4383,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4413
4383
  tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
4414
4384
  }
4415
4385
 
4416
- // called by engine to setup render system
4386
+ // Called automatically by engine to setup render system
4417
4387
  function enginePreRender()
4418
4388
  {
4419
4389
  // save canvas size
@@ -4423,12 +4393,10 @@ function enginePreRender()
4423
4393
  mainContext.imageSmoothingEnabled = !cavasPixelated;
4424
4394
 
4425
4395
  // setup gl rendering if enabled
4426
- glEnable && glPreRender(mainCanvas.width, mainCanvas.height, cameraPos.x, cameraPos.y, cameraScale);
4396
+ glEnable && glPreRender();
4427
4397
  }
4428
4398
 
4429
- ///////////////////////////////////////////////////////////////////////////////
4430
-
4431
- /** Calls update on each engine object (recursively if child), removes destroyed objects, and updated time */
4399
+ /** Update each engine object, remove destroyed objects, and update time */
4432
4400
  function engineObjectsUpdate()
4433
4401
  {
4434
4402
  // get list of solid objects for physics optimzation
@@ -4492,14 +4460,15 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
4492
4460
  * <br> - Export engine as a module with extra functions where necessary
4493
4461
  */
4494
4462
 
4495
- // setters for all variables that devs will need to modify
4463
+ // Setters for all variables that devs will need to modify
4496
4464
  const setCameraPos = (v)=> cameraPos = v;
4497
4465
  const setCameraScale = (v)=> cameraScale = v;
4498
- const setRandSeed = (v)=> randSeed = v;
4499
4466
  const setCanvasMaxSize = (v)=> canvasMaxSize = v;
4500
4467
  const setCanvasFixedSize = (v)=> canvasFixedSize = v;
4501
4468
  const setCavasPixelated = (v)=> cavasPixelated = v;
4502
4469
  const setFontDefault = (v)=> fontDefault = v;
4470
+ const setGlEnable = (v)=> glEnable = v;
4471
+ const setGlOverlay = (v)=> glOverlay = v;
4503
4472
  const setTileSizeDefault = (v)=> tileSizeDefault = v;
4504
4473
  const setTileFixBleedScale = (v)=> tileFixBleedScale = v;
4505
4474
  const setObjectDefaultSize = (v)=> objectDefaultSize = v;
@@ -4512,8 +4481,6 @@ const setObjectDefaultFriction = (v)=> objectDefaultFriction = v;
4512
4481
  const setObjectMaxSpeed = (v)=> objectMaxSpeed = v;
4513
4482
  const setGravity = (v)=> gravity = v;
4514
4483
  const setParticleEmitRateScale = (v)=> particleEmitRateScale = v;
4515
- const setGlEnable = (v)=> glEnable = v;
4516
- const setGlOverlay = (v)=> glOverlay = v;
4517
4484
  const setGamepadsEnable = (v)=> gamepadsEnable = v;
4518
4485
  const setGamepadDirectionEmulateStick = (v)=> gamepadDirectionEmulateStick = v;
4519
4486
  const setInputWASDEmulateDirection = (v)=> inputWASDEmulateDirection = v;
@@ -4522,28 +4489,26 @@ const setTouchGamepadAnalog = (v)=> touchGamepadAnalog = v;
4522
4489
  const setTouchGamepadSize = (v)=> touchGamepadSize = v;
4523
4490
  const setTouchGamepadAlpha = (v)=> touchGamepadAlpha = v;
4524
4491
  const setVibrateEnable = (v)=> vibrateEnable = v;
4525
- const setSoundVolume = (v)=> soundVolume = v;
4526
4492
  const setSoundEnable = (v)=> soundEnable = v;
4493
+ const setSoundVolume = (v)=> soundVolume = v;
4527
4494
  const setSoundDefaultRange = (v)=> soundDefaultRange = v;
4528
4495
  const setSoundDefaultTaper = (v)=> soundDefaultTaper = v;
4529
4496
  const setMedalDisplayTime = (v)=> medalDisplayTime = v;
4530
4497
  const setMedalDisplaySlideTime = (v)=> medalDisplaySlideTime = v;
4531
- const setMedalDisplayWidth = (v)=> medalDisplayWidth = v;
4532
- const setMedalDisplayHeight = (v)=> medalDisplayHeight = v;
4498
+ const setMedalDisplaySize = (v)=> medalDisplaySize = v;
4533
4499
  const setMedalDisplayIconSize = (v)=> medalDisplayIconSize = v;
4534
4500
  const setMedalsPreventUnlock = (v)=> medalsPreventUnlock = v;
4535
- const setShowWatermark = (v)=> showWatermark = v;
4536
- const setGodMode = (v)=> godMode = v;
4537
4501
 
4538
4502
  export {
4539
- // Custom methods
4503
+ // Setters for global variables
4540
4504
  setCameraPos,
4541
4505
  setCameraScale,
4542
- setRandSeed,
4543
4506
  setCanvasMaxSize,
4544
4507
  setCanvasFixedSize,
4545
4508
  setCavasPixelated,
4546
4509
  setFontDefault,
4510
+ setGlEnable,
4511
+ setGlOverlay,
4547
4512
  setTileSizeDefault,
4548
4513
  setTileFixBleedScale,
4549
4514
  setObjectDefaultSize,
@@ -4556,8 +4521,6 @@ export {
4556
4521
  setObjectMaxSpeed,
4557
4522
  setGravity,
4558
4523
  setParticleEmitRateScale,
4559
- setGlEnable,
4560
- setGlOverlay,
4561
4524
  setGamepadsEnable,
4562
4525
  setGamepadDirectionEmulateStick,
4563
4526
  setInputWASDEmulateDirection,
@@ -4566,18 +4529,15 @@ export {
4566
4529
  setTouchGamepadSize,
4567
4530
  setTouchGamepadAlpha,
4568
4531
  setVibrateEnable,
4569
- setSoundVolume,
4570
4532
  setSoundEnable,
4533
+ setSoundVolume,
4571
4534
  setSoundDefaultRange,
4572
4535
  setSoundDefaultTaper,
4573
4536
  setMedalDisplayTime,
4574
4537
  setMedalDisplaySlideTime,
4575
- setMedalDisplayWidth,
4576
- setMedalDisplayHeight,
4538
+ setMedalDisplaySize,
4577
4539
  setMedalDisplayIconSize,
4578
4540
  setMedalsPreventUnlock,
4579
- setShowWatermark,
4580
- setGodMode,
4581
4541
 
4582
4542
  // Settings
4583
4543
  canvasMaxSize,
@@ -4608,14 +4568,13 @@ export {
4608
4568
  touchGamepadSize,
4609
4569
  touchGamepadAlpha,
4610
4570
  vibrateEnable,
4611
- soundVolume,
4612
4571
  soundEnable,
4572
+ soundVolume,
4613
4573
  soundDefaultRange,
4614
4574
  soundDefaultTaper,
4615
4575
  medalDisplayTime,
4616
4576
  medalDisplaySlideTime,
4617
- medalDisplayWidth,
4618
- medalDisplayHeight,
4577
+ medalDisplaySize,
4619
4578
  medalDisplayIconSize,
4620
4579
 
4621
4580
  // Globals
@@ -4669,6 +4628,7 @@ export {
4669
4628
  randVector,
4670
4629
  randColor,
4671
4630
  randSeed,
4631
+ setRandSeed,
4672
4632
  randSeeded,
4673
4633
 
4674
4634
  // Utility Classes
@@ -4733,15 +4693,12 @@ export {
4733
4693
  // onmousemove,
4734
4694
  // onwheel,
4735
4695
  // oncontextmenu,
4736
- mouseToScreen,
4737
4696
  //stickData,
4697
+ mouseToScreen,
4738
4698
  gamepadsUpdate,
4739
4699
  vibrate,
4740
4700
  vibrateStop,
4741
4701
  isTouchDevice,
4742
- //touchGamepadTimer,
4743
- touchGamepadCreate,
4744
- touchGamepadRender,
4745
4702
 
4746
4703
  // Audio
4747
4704
  Sound,
@@ -4809,11 +4766,9 @@ export {
4809
4766
  time,
4810
4767
  timeReal,
4811
4768
  paused,
4812
- averageFPS,
4813
- drawCount,
4769
+ setPaused,
4814
4770
  engineInit,
4815
- //enginePreRender,
4816
- //engineObjectsUpdate,
4771
+ engineObjectsUpdate,
4817
4772
  engineObjectsDestroy,
4818
4773
  engineObjectsCallback,
4819
4774
  };