littlejsengine 1.7.21 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +27 -55
  2. package/build/littlejs.d.ts +101 -58
  3. package/build/littlejs.esm.js +497 -419
  4. package/build/littlejs.esm.min.js +1 -1
  5. package/build/littlejs.js +493 -228
  6. package/build/littlejs.min.js +1 -1
  7. package/build/littlejs.release.js +493 -228
  8. package/examples/breakout/game.js +17 -14
  9. package/examples/breakout/gameObjects.js +29 -8
  10. package/examples/breakout/index.html +3 -3
  11. package/examples/breakoutTutorial/README.md +2 -2
  12. package/examples/breakoutTutorial/game.js +4 -4
  13. package/examples/breakoutTutorial/index.html +2 -2
  14. package/examples/electron/game.js +3 -2
  15. package/examples/electron/index.html +2 -2
  16. package/examples/empty/game.js +1 -1
  17. package/examples/favicon.png +0 -0
  18. package/examples/js13k/game.js +20 -15
  19. package/examples/js13k/index.html +14 -14
  20. package/examples/module/game.js +4 -3
  21. package/examples/module/index.html +1 -1
  22. package/examples/particles/index.html +19 -22
  23. package/examples/platformer/game.js +3 -3
  24. package/examples/platformer/gameEffects.js +21 -19
  25. package/examples/platformer/gameLevel.js +6 -6
  26. package/examples/platformer/gameObjects.js +18 -18
  27. package/examples/platformer/gamePlayer.js +4 -3
  28. package/examples/platformer/index.html +6 -6
  29. package/examples/platformer/tiles.png +0 -0
  30. package/examples/platformer/tilesLevel.png +0 -0
  31. package/examples/puzzle/game.js +10 -8
  32. package/examples/puzzle/index.html +2 -2
  33. package/examples/screenshot.jpg +0 -0
  34. package/examples/starter/game.js +32 -21
  35. package/examples/starter/index.html +13 -13
  36. package/examples/starter/tiles.png +0 -0
  37. package/examples/stress/index.html +2 -2
  38. package/examples/typescript/game.js +4 -3
  39. package/examples/typescript/game.ts +4 -3
  40. package/examples/typescript/index.html +1 -1
  41. package/package.json +1 -1
  42. package/src/engine.js +59 -52
  43. package/src/engineDraw.js +137 -38
  44. package/src/engineExport.js +4 -191
  45. package/src/engineMedals.js +13 -45
  46. package/src/engineObject.js +12 -13
  47. package/src/engineParticles.js +17 -17
  48. package/src/engineSettings.js +195 -2
  49. package/src/engineTileLayer.js +23 -22
  50. package/src/engineUtilities.js +6 -6
  51. package/src/engineWebGL.js +31 -32
@@ -240,10 +240,10 @@ function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear)
240
240
  * - Can be used to create a deterministic random number sequence
241
241
  * @example
242
242
  * let r = new RandomGenerator(123); // random number generator with seed 123
243
- * let a = r.rand(); // random value between 0 and 1
244
- * let b = r.randInt(10); // random integer between 0 and 9
243
+ * let a = r.float(); // random value between 0 and 1
244
+ * let b = r.int(10); // random integer between 0 and 9
245
245
  * r.seed = 123; // reset the seed
246
- * let c = r.rand(); // the same value as a
246
+ * let c = r.float(); // the same value as a
247
247
  */
248
248
  class RandomGenerator
249
249
  {
@@ -272,18 +272,18 @@ class RandomGenerator
272
272
  * @param {Number} valueA
273
273
  * @param {Number} [valueB=0]
274
274
  * @return {Number} */
275
- int(valueA, valueB=0) { return Math.floor(this.rand(valueA, valueB)); }
275
+ int(valueA, valueB=0) { return Math.floor(this.float(valueA, valueB)); }
276
276
 
277
277
  /** Randomly returns either -1 or 1 deterministically
278
278
  * @return {Number} */
279
- sign() { return this.randInt(2) * 2 - 1; }
279
+ sign() { return this.int(2) * 2 - 1; }
280
280
  }
281
281
 
282
282
  ///////////////////////////////////////////////////////////////////////////////
283
283
 
284
284
  /**
285
285
  * Create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
286
- * @param {Number} [x=0]
286
+ * @param {(Number|Vector2)} [x=0]
287
287
  * @param {Number} [y=0]
288
288
  * @return {Vector2}
289
289
  * @example
@@ -767,7 +767,7 @@ let glOverlay = 1;
767
767
  * @memberof Settings */
768
768
  let tileSizeDefault = vec2(16);
769
769
 
770
- /** Prevent tile bleeding from neighbors in pixels
770
+ /** How many pixels smaller to draw tiles to prevent bleeding from neighbors
771
771
  * @type {Number}
772
772
  * @default
773
773
  * @memberof Settings */
@@ -941,7 +941,200 @@ let medalDisplayIconSize = 50;
941
941
  * @type {Boolean}
942
942
  * @default 0
943
943
  * @memberof Settings */
944
- let medalsPreventUnlock;
944
+ let medalsPreventUnlock;
945
+
946
+ ///////////////////////////////////////////////////////////////////////////////
947
+ // Setters for global variables
948
+
949
+ /** Set position of camera in world space
950
+ * @param {Vector2} pos
951
+ * @memberof Settings */
952
+ function setCameraPos(pos) { cameraPos = pos; }
953
+
954
+ /** Set scale of camera in world space
955
+ * @param {Number} scale
956
+ * @memberof Settings */
957
+ function setCameraScale(scale) { cameraScale = scale; }
958
+
959
+ /** Set max size of the canvas
960
+ * @param {Vector2} size
961
+ * @memberof Settings */
962
+ function setCanvasMaxSize(size) { canvasMaxSize = size; }
963
+
964
+ /** Set fixed size of the canvas
965
+ * @param {Vector2} size
966
+ * @memberof Settings */
967
+ function setCanvasFixedSize(size) { canvasFixedSize = size; }
968
+
969
+ /** Disables anti aliasing for pixel art if true
970
+ * @param {Boolean} pixelated
971
+ * @memberof Settings */
972
+ function setCanvasPixelated(pixelated) { canvasPixelated = pixelated; }
973
+
974
+ /** Set default font used for text rendering
975
+ * @param {String} font
976
+ * @memberof Settings */
977
+ function setFontDefault(font) { fontDefault = font; }
978
+
979
+ /** Set if webgl rendering is enabled
980
+ * @param {Boolean} enable
981
+ * @memberof Settings */
982
+ function setGlEnable(enable) { glEnable = enable; }
983
+
984
+ /** Set to not composite the WebGL canvas
985
+ * @param {Boolean} overlay
986
+ * @memberof Settings */
987
+ function setGlOverlay(overlay) { glOverlay = overlay; }
988
+
989
+ /** Set default size of tiles in pixels
990
+ * @param {Vector2} size
991
+ * @memberof Settings */
992
+ function setTileSizeDefault(size) { tileSizeDefault = size; }
993
+
994
+ /** Set to prevent tile bleeding from neighbors in pixels
995
+ * @param {Number} scale
996
+ * @memberof Settings */
997
+ function setTileFixBleedScale(scale) { tileFixBleedScale = scale; }
998
+
999
+ /** Set if collisions between objects are enabled
1000
+ * @param {Boolean} enable
1001
+ * @memberof Settings */
1002
+ function setEnablePhysicsSolver(enable) { enablePhysicsSolver = enable; }
1003
+
1004
+ /** Set default object mass for collison calcuations
1005
+ * @param {Number} mass
1006
+ * @memberof Settings */
1007
+ function setObjectDefaultMass(mass) { objectDefaultMass = mass; }
1008
+
1009
+ /** Set how much to slow velocity by each frame
1010
+ * @param {Number} damping
1011
+ * @memberof Settings */
1012
+ function setObjectDefaultDamping(damp) { objectDefaultDamping = damp; }
1013
+
1014
+ /** Set how much to slow angular velocity each frame
1015
+ * @param {Number} damping
1016
+ * @memberof Settings */
1017
+ function setObjectDefaultAngleDamping(damp) { objectDefaultAngleDamping = damp; }
1018
+
1019
+ /** Set how much to bounce when a collision occur
1020
+ * @param {Number} elasticity
1021
+ * @memberof Settings */
1022
+ function setObjectDefaultElasticity(elasticity) { objectDefaultElasticity = elasticity; }
1023
+
1024
+ /** Set how much to slow when touching
1025
+ * @param {Number} friction
1026
+ * @memberof Settings */
1027
+ function setObjectDefaultFriction(friction) { objectDefaultFriction = friction; }
1028
+
1029
+ /** Set max speed to avoid fast objects missing collisions
1030
+ * @param {Number} speed
1031
+ * @memberof Settings */
1032
+ function setObjectMaxSpeed(speed) { objectMaxSpeed = speed; }
1033
+
1034
+ /** Set how much gravity to apply to objects along the Y axis
1035
+ * @param {Number} gravity
1036
+ * @memberof Settings */
1037
+ function setGravity(g) { gravity = g; }
1038
+
1039
+ /** Set to scales emit rate of particles
1040
+ * @param {Number} scale
1041
+ * @memberof Settings */
1042
+ function setParticleEmitRateScale(scale) { particleEmitRateScale = scale; }
1043
+
1044
+ /** Set if gamepads are enabled
1045
+ * @param {Boolean} enable
1046
+ * @memberof Settings */
1047
+ function setGamepadsEnable(enable) { gamepadsEnable = enable; }
1048
+
1049
+ /** Set if the dpad input is also routed to the left analog stick
1050
+ * @param {Boolean} enable
1051
+ * @memberof Settings */
1052
+ function setGamepadDirectionEmulateStick(enable) { gamepadDirectionEmulateStick = enable; }
1053
+
1054
+ /** Set if true the WASD keys are also routed to the direction keys
1055
+ * @param {Boolean} enable
1056
+ * @memberof Settings */
1057
+ function setInputWASDEmulateDirection(enable) { inputWASDEmulateDirection = enable; }
1058
+
1059
+ /** Set if touch gamepad should appear on mobile devices
1060
+ * @param {Boolean} enable
1061
+ * @memberof Settings */
1062
+ function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
1063
+
1064
+ /** Set if touch gamepad should be analog stick or 8 way dpad
1065
+ * @param {Boolean} analog
1066
+ * @memberof Settings */
1067
+ function setTouchGamepadAnalog(analog) { touchGamepadAnalog = analog; }
1068
+
1069
+ /** Set size of virutal gamepad for touch devices in pixels
1070
+ * @param {Number} size
1071
+ * @memberof Settings */
1072
+ function setTouchGamepadSize(size) { touchGamepadSize = size; }
1073
+
1074
+ /** Set transparency of touch gamepad overlay
1075
+ * @param {Number} alpha
1076
+ * @memberof Settings */
1077
+ function setTouchGamepadAlpha(alpha) { touchGamepadAlpha = alpha; }
1078
+
1079
+ /** Set to allow vibration hardware if it exists
1080
+ * @param {Boolean} enable
1081
+ * @memberof Settings */
1082
+ function setVibrateEnable(enable) { vibrateEnable = enable; }
1083
+
1084
+ /** Set to disable all audio code
1085
+ * @param {Boolean} enable
1086
+ * @memberof Settings */
1087
+ function setSoundEnable(enable) { soundEnable = enable; }
1088
+
1089
+ /** Set volume scale to apply to all sound, music and speech
1090
+ * @param {Number} volume
1091
+ * @memberof Settings */
1092
+ function setSoundVolume(volume) { soundVolume = volume; }
1093
+
1094
+ /** Set default range where sound no longer plays
1095
+ * @param {Number} range
1096
+ * @memberof Settings */
1097
+ function setSoundDefaultRange(range) { soundDefaultRange = range; }
1098
+
1099
+ /** Set default range percent to start tapering off sound
1100
+ * @param {Number} taper
1101
+ * @memberof Settings */
1102
+ function setSoundDefaultTaper(taper) { soundDefaultTaper = taper; }
1103
+
1104
+ /** Set how long to show medals for in seconds
1105
+ * @param {Number} time
1106
+ * @memberof Settings */
1107
+ function setMedalDisplayTime(time) { medalDisplayTime = time; }
1108
+
1109
+ /** Set how quickly to slide on/off medals in seconds
1110
+ * @param {Number} time
1111
+ * @memberof Settings */
1112
+ function setMedalDisplaySlideTime(time) { medalDisplaySlideTime = time; }
1113
+
1114
+ /** Set size of medal display
1115
+ * @param {Vector2} size
1116
+ * @memberof Settings */
1117
+ function setMedalDisplaySize(size) { medalDisplaySize = size; }
1118
+
1119
+ /** Set size of icon in medal display
1120
+ * @param {Number} size
1121
+ * @memberof Settings */
1122
+ function setMedalDisplayIconSize(size) { medalDisplayIconSize = size; }
1123
+
1124
+ /** Set to stop medals from being unlockable
1125
+ * @param {Boolean} preventUnlock
1126
+ * @memberof Settings */
1127
+ function setMedalsPreventUnlock(preventUnlock) { medalsPreventUnlock = preventUnlock; }
1128
+
1129
+ /** Set if watermark with FPS should be shown
1130
+ * @param {Boolean} show
1131
+ * @memberof Debug */
1132
+ function setShowWatermark(show) { showWatermark = show; }
1133
+
1134
+ /** Set key code used to toggle debug mode, Esc by default
1135
+ * @param {Number} key
1136
+ * @memberof Debug */
1137
+ function setDebugKey(key) { debugKey = key; }
945
1138
  /**
946
1139
  * LittleJS Object System
947
1140
  */
@@ -976,18 +1169,19 @@ let medalsPreventUnlock;
976
1169
  class EngineObject
977
1170
  {
978
1171
  /** Create an engine object and adds it to the list of objects
979
- * @param {Vector2} [position=Vector2()] - World space position of the object
980
- * @param {Vector2} [size=Vector2(1,1)] - World space size of the object
981
- * @param {Number} [tileIndex=-1] - Tile to use to render object (-1 is untextured)
982
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
983
- * @param {Number} [angle=0] - Angle the object is rotated by
984
- * @param {Color} [color=Color()] - Color to apply to tile when rendered
985
- * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
1172
+ * @param {Vector2} [pos=Vector2()] - World space position of the object
1173
+ * @param {Vector2} [size=Vector2(1,1)] - World space size of the object
1174
+ * @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
1175
+ * @param {Number} [angle=0] - Angle the object is rotated by
1176
+ * @param {Color} [color=Color()] - Color to apply to tile when rendered
1177
+ * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
986
1178
  */
987
- constructor(pos=vec2(), size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, angle=0, color, renderOrder=0)
1179
+ constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color, renderOrder=0)
988
1180
  {
989
1181
  // set passed in params
990
1182
  ASSERT(isVector2(pos) && isVector2(size)); // ensure pos and size are vec2s
1183
+ ASSERT(typeof tileInfo !== 'number' || !tileInfo); // prevent old style calls
1184
+ // to fix old calls, replace with tile(tileIndex, tileSize)
991
1185
 
992
1186
  /** @property {Vector2} - World space position of the object */
993
1187
  this.pos = pos.copy();
@@ -995,10 +1189,8 @@ class EngineObject
995
1189
  this.size = size;
996
1190
  /** @property {Vector2} - Size of object used for drawing, uses size if not set */
997
1191
  this.drawSize;
998
- /** @property {Number} - Tile to use to render object (-1 is untextured) */
999
- this.tileIndex = tileIndex;
1000
- /** @property {Vector2} - Size of tile in source pixels */
1001
- this.tileSize = tileSize;
1192
+ /** @property {TileInfo} - Tile info to render object (undefined is untextured) */
1193
+ this.tileInfo = tileInfo;
1002
1194
  /** @property {Number} - Angle to rotate the object */
1003
1195
  this.angle = angle;
1004
1196
  /** @property {Color} - Color to apply when rendered */
@@ -1214,7 +1406,7 @@ class EngineObject
1214
1406
  render()
1215
1407
  {
1216
1408
  // default object render
1217
- drawTile(this.pos, this.drawSize || this.size, this.tileIndex, this.tileSize, this.color, this.angle, this.mirror, this.additiveColor);
1409
+ drawTile(this.pos, this.drawSize || this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
1218
1410
  }
1219
1411
 
1220
1412
  /** Destroy this object, destroy it's children, detach it's parent, and mark it for removal */
@@ -1369,13 +1561,109 @@ let overlayContext;
1369
1561
  * @memberof Draw */
1370
1562
  let mainCanvasSize = vec2();
1371
1563
 
1372
- /** Tile sheet for batch rendering system
1373
- * @type {CanvasImageSource}
1564
+ /** Array containing texture info for batch rendering system
1565
+ * @type {Array}
1374
1566
  * @memberof Draw */
1375
- const tileImage = new Image;
1567
+ let textureInfos = [];
1376
1568
 
1377
1569
  // Engine internal variables not exposed to documentation
1378
- let tileImageSize, tileImageFixBleed, drawCount;
1570
+ let drawCount;
1571
+
1572
+ ///////////////////////////////////////////////////////////////////////////////
1573
+
1574
+ /**
1575
+ * Create a tile info object
1576
+ * - This can take vecs or floats for easier use and conversion
1577
+ * - If an index is passed in, the tile size and index will determine the position
1578
+ * @param {(Number|Vector2)} [pos=Vector2()] - Top left corner of tile in pixels or index
1579
+ * @param {(Number|Vector2)} [size=tileSizeDefault] - Size of tile in pixels
1580
+ * @param {Number} [textureIndex=0] - Texture index to use
1581
+ * @return {TileInfo}
1582
+ * @example
1583
+ * tile(2) // a tile at index 2 using the default tile size of 16
1584
+ * tile(5, 8) // a tile at index 5 using a tile size of 8
1585
+ * tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
1586
+ * tile(vec2(4,8), vec2(30,10)) // a tile at pixel location (4,8) with a size of (30,10)
1587
+ * @memberof Draw
1588
+ */
1589
+ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
1590
+ {
1591
+ // if size is a number, make it a vector
1592
+ if (size.x == undefined)
1593
+ {
1594
+ ASSERT(size > 0);
1595
+ size = vec2(size);
1596
+ }
1597
+
1598
+ // if pos is a number, use it as a tile index
1599
+ if (pos.x == undefined)
1600
+ {
1601
+ const textureInfo = textureInfos[textureIndex];
1602
+ if (textureInfo)
1603
+ {
1604
+ const cols = textureInfo.size.x / size.x |0;
1605
+ pos = vec2((pos%cols)*size.x, (pos/cols|0)*size.y);
1606
+ }
1607
+ else
1608
+ pos = vec2();
1609
+ }
1610
+
1611
+ // return a tile info object
1612
+ return new TileInfo(pos, size, textureIndex);
1613
+ }
1614
+
1615
+ /**
1616
+ * Tile Info - Stores info about how to draw a tile
1617
+ */
1618
+ class TileInfo
1619
+ {
1620
+ /** Create a tile info object
1621
+ * @param {Vector2} [pos=Vector2()] - Top left corner of tile in pixels
1622
+ * @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
1623
+ * @param {Number} [textureIndex=0] - Texture index to use
1624
+ */
1625
+ constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
1626
+ {
1627
+ /** @property {Vector2} - Top left corner of tile in pixels */
1628
+ this.pos = pos;
1629
+ /** @property {Vector2} - Size of tile in pixels */
1630
+ this.size = size;
1631
+ /** @property {Number} - Texture index to use */
1632
+ this.textureIndex = textureIndex;
1633
+ }
1634
+
1635
+ /** Returns an offset copy of this tile, useful for animation
1636
+ * @param {Vector2} offset - Offset to apply in pixels
1637
+ * @return {TileInfo}
1638
+ */
1639
+ offset(offset)
1640
+ { return new TileInfo(this.pos.add(offset), this.size, this.textureIndex); }
1641
+
1642
+ /** Returns the texture info for this tile
1643
+ * @return {TextureInfo}
1644
+ */
1645
+ getTextureInfo()
1646
+ { return textureInfos[this.textureIndex]; }
1647
+ }
1648
+
1649
+ /** Texture Info - Stores info about each texture */
1650
+ class TextureInfo
1651
+ {
1652
+ // create a TextureInfo, called automatically by the engine
1653
+ constructor(image)
1654
+ {
1655
+ /** @property {CanvasImageSource} - image source */
1656
+ this.image = image;
1657
+ /** @property {Vector2} - size of the image */
1658
+ this.size = vec2(image.width, image.height);
1659
+ /** @property {WebGLTexture} - webgl texture */
1660
+ this.glTexture = glEnable && glCreateTexture(image);
1661
+ /** @property {Vector2} - size to adjust tile to fix bleeding */
1662
+ this.fixBleedSize = vec2(tileFixBleedScale).divide(this.size);
1663
+ }
1664
+ }
1665
+
1666
+ ///////////////////////////////////////////////////////////////////////////////
1379
1667
 
1380
1668
  /** Convert from screen to world space coordinates
1381
1669
  * @param {Vector2} screenPos
@@ -1406,7 +1694,7 @@ function worldToScreen(worldPos)
1406
1694
  /** Draw textured tile centered in world space, with color applied if using WebGL
1407
1695
  * @param {Vector2} pos - Center of the tile in world space
1408
1696
  * @param {Vector2} [size=Vector2(1,1)] - Size of the tile in world space
1409
- * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
1697
+ * @param {TileInfo}[tileInfo] - Tile info to use, untextured if undefined
1410
1698
  * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
1411
1699
  * @param {Color} [color=Color()] - Color to modulate with
1412
1700
  * @param {Number} [angle=0] - Angle to rotate by
@@ -1415,11 +1703,14 @@ function worldToScreen(worldPos)
1415
1703
  * @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
1416
1704
  * @param {Boolean} [screenSpace=0] - If true the pos and size are in screen space
1417
1705
  * @memberof Draw */
1418
- function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color,
1706
+ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
1419
1707
  angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace)
1420
1708
  {
1709
+ ASSERT(typeof tileInfo !== 'number' || !tileInfo); // prevent old style calls
1710
+ // to fix old calls, replace with tile(tileIndex, tileSize)
1711
+
1421
1712
  showWatermark && ++drawCount;
1422
-
1713
+ const textureInfo = tileInfo && tileInfo.getTextureInfo();
1423
1714
  if (glEnable && useWebGL)
1424
1715
  {
1425
1716
  if (screenSpace)
@@ -1428,46 +1719,48 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
1428
1719
  pos = screenToWorld(pos);
1429
1720
  size = size.scale(1/cameraScale);
1430
1721
  }
1431
- if (tileIndex < 0 || !tileImage.width)
1432
- {
1433
- // if negative tile index or image not found, force untextured
1434
- glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
1435
- }
1436
- else
1722
+
1723
+ if (textureInfo)
1437
1724
  {
1438
1725
  // calculate uvs and render
1439
- const cols = tileImageSize.x / tileSize.x |0;
1440
- const uvSizeX = tileSize.x / tileImageSize.x;
1441
- const uvSizeY = tileSize.y / tileImageSize.y;
1442
- const uvX = (tileIndex%cols)*uvSizeX, uvY = (tileIndex/cols|0)*uvSizeY;
1443
-
1726
+ const x = tileInfo.pos.x / textureInfo.size.x;
1727
+ const y = tileInfo.pos.y / textureInfo.size.y;
1728
+ const w = tileInfo.size.x / textureInfo.size.x;
1729
+ const h = tileInfo.size.y / textureInfo.size.y;
1730
+ const tileImageFixBleed = textureInfo.fixBleedSize;
1731
+ glSetTexture(textureInfo.glTexture);
1444
1732
  glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
1445
- uvX + tileImageFixBleed.x, uvY + tileImageFixBleed.y,
1446
- uvX - tileImageFixBleed.x + uvSizeX, uvY - tileImageFixBleed.y + uvSizeY,
1733
+ x + tileImageFixBleed.x, y + tileImageFixBleed.y,
1734
+ x - tileImageFixBleed.x + w, y - tileImageFixBleed.y + h,
1447
1735
  color.rgbaInt(), additiveColor.rgbaInt());
1448
1736
  }
1737
+ else
1738
+ {
1739
+ // if no tile info, force untextured
1740
+ glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
1741
+ }
1449
1742
  }
1450
1743
  else
1451
1744
  {
1452
1745
  // normal canvas 2D rendering method (slower)
1453
1746
  drawCanvas2D(pos, size, angle, mirror, (context)=>
1454
1747
  {
1455
- if (tileIndex < 0)
1748
+ if (textureInfo)
1456
1749
  {
1457
- // if negative tile index, force untextured
1458
- context.fillStyle = color;
1459
- context.fillRect(-.5, -.5, 1, 1);
1750
+ // calculate uvs and render
1751
+ const x = tileInfo.pos.x + tileFixBleedScale;
1752
+ const y = tileInfo.pos.y + tileFixBleedScale;
1753
+ const w = tileInfo.size.x - 2*tileFixBleedScale;
1754
+ const h = tileInfo.size.y - 2*tileFixBleedScale;
1755
+ context.globalAlpha = color.a; // only alpha is supported
1756
+ context.drawImage(textureInfo.image, x, y, w, h, -.5, -.5, 1, 1);
1757
+ context.globalAlpha = 1; // set back to full alpha
1460
1758
  }
1461
1759
  else
1462
1760
  {
1463
- // calculate uvs and render
1464
- const cols = tileImageSize.x / tileSize.x |0;
1465
- const sX = (tileIndex%cols)*tileSize.x + tileFixBleedScale;
1466
- const sY = (tileIndex/cols|0)*tileSize.y + tileFixBleedScale;
1467
- const sWidth = tileSize.x - 2*tileFixBleedScale;
1468
- const sHeight = tileSize.y - 2*tileFixBleedScale;
1469
- context.globalAlpha = color.a; // only alpha is supported
1470
- context.drawImage(tileImage, sX, sY, sWidth, sHeight, -.5, -.5, 1, 1);
1761
+ // if no tile info, force untextured
1762
+ context.fillStyle = color;
1763
+ context.fillRect(-.5, -.5, 1, 1);
1471
1764
  }
1472
1765
  }, undefined, screenSpace);
1473
1766
  }
@@ -1482,7 +1775,7 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
1482
1775
  * @param {Boolean} [screenSpace=0]
1483
1776
  * @memberof Draw */
1484
1777
  function drawRect(pos, size, color, angle, useWebGL, screenSpace)
1485
- { drawTile(pos, size, -1, tileSizeDefault, color, angle, 0, undefined, useWebGL, screenSpace); }
1778
+ { drawTile(pos, size, undefined, color, angle, 0, undefined, useWebGL, screenSpace); }
1486
1779
 
1487
1780
  /** Draw colored polygon using passed in points
1488
1781
  * @param {Array} points - Array of Vector2 points
@@ -1627,10 +1920,9 @@ class FontImage
1627
1920
  * @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
1628
1921
  * @param {Vector2} [tileSize=vec2(8)] - Size of the font source tiles
1629
1922
  * @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
1630
- * @param {Number} [startTileIndex=0] - Tile index in image where font starts
1631
1923
  * @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
1632
1924
  */
1633
- constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
1925
+ constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), context=overlayContext)
1634
1926
  {
1635
1927
  // load default font image
1636
1928
  if (!engineFontImage)
@@ -1639,7 +1931,6 @@ class FontImage
1639
1931
  this.image = image || engineFontImage;
1640
1932
  this.tileSize = tileSize;
1641
1933
  this.paddingSize = paddingSize;
1642
- this.startTileIndex = startTileIndex;
1643
1934
  this.context = context;
1644
1935
  }
1645
1936
 
@@ -1680,7 +1971,7 @@ class FontImage
1680
1971
  charCode = 127; // unknown character
1681
1972
 
1682
1973
  // get the character source location and draw it
1683
- const tile = this.startTileIndex + charCode - 32;
1974
+ const tile = charCode - 32;
1684
1975
  const x = tile % cols;
1685
1976
  const y = tile / cols |0;
1686
1977
  const drawPos = pos.add(vec2(j,i).multiply(drawSize));
@@ -1712,8 +2003,7 @@ function toggleFullscreen()
1712
2003
  }
1713
2004
  else if (document.body.requestFullscreen)
1714
2005
  document.body.requestFullscreen();
1715
- }
1716
-
2006
+ }
1717
2007
  /**
1718
2008
  * LittleJS Input System
1719
2009
  * - Tracks keyboard down, pressed, and released
@@ -2871,7 +3161,7 @@ class TileLayerData
2871
3161
  }
2872
3162
 
2873
3163
  /**
2874
- * Tile layer object - cached rendering system for tile layers
3164
+ * Tile Layer - cached rendering system for tile layers
2875
3165
  * - Each Tile layer is rendered to an off screen canvas
2876
3166
  * - To allow dynamic modifications, layers are rendered using canvas 2d
2877
3167
  * - Some devices like mobile phones are limited to 4k texture resolution
@@ -2887,13 +3177,13 @@ class TileLayer extends EngineObject
2887
3177
  /** Create a tile layer object
2888
3178
  * @param {Vector2} [position=Vector2()] - World space position
2889
3179
  * @param {Vector2} [size=tileCollisionSize] - World space size
2890
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
3180
+ * @param {TileInfo} [tileInfo] - Tile info for layer
2891
3181
  * @param {Vector2} [scale=Vector2(1,1)] - How much to scale this layer when rendered
2892
3182
  * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
2893
3183
  */
2894
- constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
3184
+ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderOrder=0)
2895
3185
  {
2896
- super(pos, size, -1, tileSize, 0, undefined, renderOrder);
3186
+ super(pos, size, tileInfo, 0, undefined, renderOrder);
2897
3187
 
2898
3188
  /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
2899
3189
  this.canvas = document.createElement('canvas');
@@ -2970,13 +3260,13 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
2970
3260
  mainCanvas = this.canvas;
2971
3261
  mainContext = this.context;
2972
3262
  cameraPos = this.size.scale(.5);
2973
- cameraScale = this.tileSize.x;
3263
+ cameraScale = this.tileInfo.size.x;
2974
3264
 
2975
3265
  if (clear)
2976
3266
  {
2977
3267
  // clear and set size
2978
- mainCanvas.width = this.size.x * this.tileSize.x;
2979
- mainCanvas.height = this.size.y * this.tileSize.y;
3268
+ mainCanvas.width = this.size.x * this.tileInfo.size.x;
3269
+ mainCanvas.height = this.size.y * this.tileInfo.size.y;
2980
3270
  }
2981
3271
 
2982
3272
  // begin a new render for the tile canvas
@@ -3007,7 +3297,8 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3007
3297
  if (d.tile != undefined)
3008
3298
  {
3009
3299
  ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
3010
- drawTile(pos, vec2(1), d.tile, this.tileSize, d.color, d.direction*PI/2, d.mirror);
3300
+ const tileInfo = tile(d.tile, this.tileInfo.size, this.tileInfo.textureIndex);
3301
+ drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
3011
3302
  }
3012
3303
  }
3013
3304
 
@@ -3029,8 +3320,8 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3029
3320
  {
3030
3321
  const context = this.context;
3031
3322
  context.save();
3032
- pos = pos.subtract(this.pos).multiply(this.tileSize);
3033
- size = size.multiply(this.tileSize);
3323
+ pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
3324
+ size = size.multiply(this.tileInfo.size);
3034
3325
  context.translate(pos.x, this.canvas.height - pos.y);
3035
3326
  context.rotate(angle);
3036
3327
  context.scale(mirror ? -size.x : size.x, size.y);
@@ -3041,28 +3332,28 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3041
3332
  /** Draw a tile directly onto the layer canvas
3042
3333
  * @param {Vector2} pos
3043
3334
  * @param {Vector2} [size=Vector2(1,1)]
3044
- * @param {Number} [tileIndex=-1]
3045
- * @param {Vector2} [tileSize=tileSizeDefault]
3335
+ * @param {TileInfo} [tileInfo]
3046
3336
  * @param {Color} [color=Color()]
3047
3337
  * @param {Number} [angle=0]
3048
3338
  * @param {Boolean} [mirror=0] */
3049
- drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle, mirror)
3339
+ drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle, mirror)
3050
3340
  {
3051
3341
  this.drawCanvas2D(pos, size, angle, mirror, (context)=>
3052
3342
  {
3053
- if (tileIndex < 0)
3343
+ const textureInfo = tileInfo && tileInfo.getTextureInfo();
3344
+ if (textureInfo)
3054
3345
  {
3055
- // untextured
3056
- context.fillStyle = color;
3057
- context.fillRect(-.5, -.5, 1, 1);
3346
+ context.globalAlpha = color.a; // only alpha is supported
3347
+ context.drawImage(textureInfo.image,
3348
+ tileInfo.pos.x, tileInfo.pos.y,
3349
+ tileInfo.size.x, tileInfo.size.y, -.5, -.5, 1, 1);
3350
+ context.globalAlpha = 1;
3058
3351
  }
3059
3352
  else
3060
3353
  {
3061
- const cols = tileImage.width/tileSize.x;
3062
- context.globalAlpha = color.a; // only alpha, no color, is supported in this mode
3063
- context.drawImage(tileImage,
3064
- (tileIndex%cols)*tileSize.x, (tileIndex/cols|0)*tileSize.y,
3065
- tileSize.x, tileSize.y, -.5, -.5, 1, 1);
3354
+ // untextured
3355
+ context.fillStyle = color;
3356
+ context.fillRect(-.5, -.5, 1, 1);
3066
3357
  }
3067
3358
  });
3068
3359
  }
@@ -3090,7 +3381,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3090
3381
  * let particleEmiter = new ParticleEmitter
3091
3382
  * (
3092
3383
  * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
3093
- * 0, vec2(16), // tileIndex, tileSize
3384
+ * tile(0, 16), // tileInfo
3094
3385
  * new Color(1,1,1), new Color(0,0,0), // colorStartA, colorStartB
3095
3386
  * new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
3096
3387
  * 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
@@ -3107,8 +3398,7 @@ class ParticleEmitter extends EngineObject
3107
3398
  * @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
3108
3399
  * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
3109
3400
  * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
3110
- * @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied
3111
- * @param {Vector2} [tileSize=tileSizeDefault] - Tile size for particles
3401
+ * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
3112
3402
  * @param {Color} [colorStartA=Color()] - Color at start of life 1, randomized between start colors
3113
3403
  * @param {Color} [colorStartB=Color()] - Color at start of life 2, randomized between start colors
3114
3404
  * @param {Color} [colorEndA=Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
@@ -3138,8 +3428,7 @@ class ParticleEmitter extends EngineObject
3138
3428
  emitTime = 0,
3139
3429
  emitRate = 100,
3140
3430
  emitConeAngle = PI,
3141
- tileIndex = -1,
3142
- tileSize = tileSizeDefault,
3431
+ tileInfo,
3143
3432
  colorStartA = new Color,
3144
3433
  colorStartB = new Color,
3145
3434
  colorEndA = new Color(1,1,1,0),
@@ -3162,7 +3451,7 @@ class ParticleEmitter extends EngineObject
3162
3451
  localSpace
3163
3452
  )
3164
3453
  {
3165
- super(pos, vec2(), tileIndex, tileSize, angle, undefined, renderOrder);
3454
+ super(pos, vec2(), tileInfo, angle, undefined, renderOrder);
3166
3455
 
3167
3456
  // emitter settings
3168
3457
  /** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
@@ -3261,7 +3550,7 @@ class ParticleEmitter extends EngineObject
3261
3550
  angle += this.angle;
3262
3551
  }
3263
3552
 
3264
- const particle = new Particle(pos, this.tileIndex, this.tileSize, angle);
3553
+ const particle = new Particle(pos, this.tileInfo, this.tileSize, angle);
3265
3554
 
3266
3555
  // randomness scales each paremeter by a percentage
3267
3556
  const randomness = this.randomness;
@@ -3321,12 +3610,11 @@ class Particle extends EngineObject
3321
3610
  /**
3322
3611
  * Create a particle with the given settings
3323
3612
  * @param {Vector2} position - World space position of the particle
3324
- * @param {Number} [tileIndex=-1] - Tile to use to render, untextured if -1
3325
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
3613
+ * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
3326
3614
  * @param {Number} [angle=0] - Angle to rotate the particle
3327
3615
  */
3328
- constructor(pos, tileIndex, tileSize, angle)
3329
- { super(pos, vec2(), tileIndex, tileSize, angle); }
3616
+ constructor(pos, tileInfo, angle)
3617
+ { super(pos, vec2(), tileInfo, angle); }
3330
3618
 
3331
3619
  /** Render the particle, automatically called each frame, sorted by renderOrder */
3332
3620
  render()
@@ -3360,14 +3648,17 @@ class Particle extends EngineObject
3360
3648
  if (this.localSpaceEmitter)
3361
3649
  velocity = velocity.rotate(-this.localSpaceEmitter.angle);
3362
3650
  const speed = velocity.length();
3363
- const direction = velocity.scale(1/speed);
3364
- const trailLength = speed * this.trailScale;
3365
- size.y = max(size.x, trailLength);
3366
- angle = direction.angle();
3367
- drawTile(pos.add(direction.multiply(vec2(0,-trailLength/2))), size, this.tileIndex, this.tileSize, color, angle, this.mirror);
3651
+ if (speed)
3652
+ {
3653
+ const direction = velocity.scale(1/speed);
3654
+ const trailLength = speed * this.trailScale;
3655
+ size.y = max(size.x, trailLength);
3656
+ angle = direction.angle();
3657
+ drawTile(pos.add(direction.multiply(vec2(0,-trailLength/2))), size, this.tileInfo, color, angle, this.mirror);
3658
+ }
3368
3659
  }
3369
3660
  else
3370
- drawTile(pos, size, this.tileIndex, this.tileSize, color, angle, this.mirror);
3661
+ drawTile(pos, size, this.tileInfo, color, angle, this.mirror);
3371
3662
  this.additive && setBlendMode();
3372
3663
  debugParticles && debugRect(pos, size, '#f005', 0, angle);
3373
3664
 
@@ -3414,7 +3705,7 @@ function medalsInit(saveName)
3414
3705
  }
3415
3706
 
3416
3707
  /**
3417
- * Medal Object - Tracks an unlockable medal
3708
+ * Medal - Tracks an unlockable medal
3418
3709
  * @example
3419
3710
  * // create a medal
3420
3711
  * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
@@ -3427,7 +3718,7 @@ function medalsInit(saveName)
3427
3718
  */
3428
3719
  class Medal
3429
3720
  {
3430
- /** Create an medal object and adds it to the list of medals
3721
+ /** Create a medal object and adds it to the list of medals
3431
3722
  * @param {Number} id - The unique identifier of the medal
3432
3723
  * @param {String} name - Name of the medal
3433
3724
  * @param {String} [description] - Description of the medal
@@ -3539,33 +3830,33 @@ let newgrounds;
3539
3830
  /** This can used to enable Newgrounds functionality
3540
3831
  * @param {Number} app_id - The newgrounds App ID
3541
3832
  * @param {String} [cipher] - The encryption Key (AES-128/Base64)
3833
+ * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher
3542
3834
  * @memberof Medals */
3543
- function newgroundsInit(app_id, cipher) { newgrounds = new Newgrounds(app_id, cipher); }
3835
+ function newgroundsInit(app_id, cipher, cryptoJS)
3836
+ { newgrounds = new Newgrounds(app_id, cipher, cryptoJS); }
3544
3837
 
3545
3838
  /**
3546
3839
  * Newgrounds API wrapper object
3547
3840
  * @example
3548
- * // create a newgrounds object, replace the app id and cipher with your own
3841
+ * // create a newgrounds object, replace the app id with your own
3549
3842
  * const app_id = '53123:1ZuSTQ9l';
3550
- * const cipher = 'enF0vGH@Mj/FRASKL23Q==';
3551
- * newgrounds = new Newgrounds(app_id, cipher);
3843
+ * newgrounds = new Newgrounds(app_id);
3552
3844
  */
3553
3845
  class Newgrounds
3554
3846
  {
3555
3847
  /** Create a newgrounds object
3556
3848
  * @param {Number} app_id - The newgrounds App ID
3557
- * @param {String} [cipher] - The encryption Key (AES-128/Base64) */
3558
- constructor(app_id, cipher)
3849
+ * @param {String} [cipher] - The encryption Key (AES-128/Base64)
3850
+ * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher */
3851
+ constructor(app_id, cipher, cryptoJS)
3559
3852
  {
3560
- ASSERT(!newgrounds && app_id);
3853
+ ASSERT(!newgrounds && app_id); // can only be one newgrounds object
3854
+ ASSERT(!cipher || cryptoJS); // must provide cryptojs if there is a cipher
3561
3855
 
3562
3856
  this.app_id = app_id;
3563
3857
  this.cipher = cipher;
3858
+ this.cryptoJS = cryptoJS;
3564
3859
  this.host = location ? location.hostname : '';
3565
-
3566
- // create an instance of CryptoJS for encrypted calls
3567
- if (cipher)
3568
- this.cryptoJS = this.CryptoJS();
3569
3860
 
3570
3861
  // get session id from url search params
3571
3862
  const url = new URL(location.href);
@@ -3669,38 +3960,6 @@ class Newgrounds
3669
3960
  debugMedals && console.log(xmlHttp.responseText);
3670
3961
  return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
3671
3962
  }
3672
-
3673
- CryptoJS()
3674
- {
3675
- ///////////////////////////////////////////////////////////////////////////////
3676
- // Crypto-JS - https://github.com/brix/crypto-js - MIT License
3677
- //
3678
- // [The MIT License (MIT)](http://opensource.org/licenses/MIT)
3679
- //
3680
- // Copyright (c) 2009-2013 Jeff Mott
3681
- // Copyright (c) 2013-2016 Evan Vosberg
3682
- //
3683
- // Permission is hereby granted, free of charge, to any person obtaining a copy
3684
- // of this software and associated documentation files (the "Software"), to deal
3685
- // in the Software without restriction, including without limitation the rights
3686
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
3687
- // copies of the Software, and to permit persons to whom the Software is
3688
- // furnished to do so, subject to the following conditions:
3689
- //
3690
- // The above copyright notice and this permission notice shall be included in
3691
- // all copies or substantial portions of the Software.
3692
- //
3693
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
3694
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
3695
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
3696
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
3697
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3698
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
3699
- // THE SOFTWARE.
3700
- 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));
3701
- // end of Crypto-JS
3702
- ///////////////////////////////////////////////////////////////////////////////
3703
- }
3704
3963
  }
3705
3964
  /**
3706
3965
  * LittleJS WebGL Interface
@@ -3710,6 +3969,7 @@ class Newgrounds
3710
3969
  * - Can be disabled with glEnable to revert to 2D canvas rendering
3711
3970
  * - Batches sprite rendering on GPU for incredibly fast performance
3712
3971
  * - Sprite transform math is done in the shader where possible
3972
+ * - Supports shadertoy style post processing shaders
3713
3973
  * @namespace WebGL
3714
3974
  */
3715
3975
 
@@ -3725,11 +3985,6 @@ let glCanvas;
3725
3985
  * @memberof WebGL */
3726
3986
  let glContext;
3727
3987
 
3728
- /** Main tile sheet texture automatically loaded by engine
3729
- * @type {WebGLTexture}
3730
- * @memberof WebGL */
3731
- let glTileTexture;
3732
-
3733
3988
  // WebGL internal variables not exposed to documentation
3734
3989
  let glActiveTexture, glShader, glArrayBuffer, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
3735
3990
 
@@ -3738,32 +3993,34 @@ let glActiveTexture, glShader, glArrayBuffer, glPositionData, glColorData, glBat
3738
3993
  // Initalize WebGL, called automatically by the engine
3739
3994
  function glInit()
3740
3995
  {
3741
- // create the canvas and tile texture
3996
+ // create the canvas and textures
3742
3997
  glCanvas = document.createElement('canvas');
3743
- glContext = glCanvas.getContext('webgl', {antialias: false});
3744
- glTileTexture = glCreateTexture(tileImage);
3998
+ glContext = glCanvas.getContext('webgl2');
3745
3999
 
3746
4000
  // some browsers are much faster without copying the gl buffer so we just overlay it instead
3747
4001
  glOverlay && document.body.appendChild(glCanvas);
3748
4002
 
3749
4003
  // setup vertex and fragment shaders
3750
4004
  glShader = glCreateProgram(
4005
+ '#version 300 es\n' + // specify GLSL ES version
3751
4006
  'precision highp float;'+ // use highp for better accuracy
3752
4007
  'uniform mat4 m;'+ // transform matrix
3753
- 'attribute vec2 p,t;'+ // position, uv
3754
- 'attribute vec4 c,a;'+ // color, additiveColor
3755
- 'varying vec4 v,d,e;'+ // return uv, color, additiveColor
4008
+ 'in vec2 p,t;'+ // position, uv
4009
+ 'in vec4 c,a;'+ // color, additiveColor
4010
+ 'out vec4 v,d,e;'+ // return uv, color, additiveColor
3756
4011
  'void main(){'+ // shader entry point
3757
4012
  'gl_Position=m*vec4(p,1,1);'+ // transform position
3758
4013
  'v=vec4(t,p);d=c;e=a;'+ // pass stuff to fragment shader
3759
4014
  '}' // end of shader
3760
4015
  ,
3761
- 'precision highp float;'+ // use highp for better accuracy
3762
- 'varying vec4 v,d,e;'+ // uv, color, additiveColor
3763
- 'uniform sampler2D s;'+ // texture
3764
- 'void main(){'+ // shader entry point
3765
- 'gl_FragColor=texture2D(s,v.xy)*d+e;'+ // modulate texture by color plus additive
3766
- '}' // end of shader
4016
+ '#version 300 es\n' + // specify GLSL ES version
4017
+ 'precision highp float;'+ // use highp for better accuracy
4018
+ 'in vec4 v,d,e;'+ // uv, color, additiveColor
4019
+ 'uniform sampler2D s;'+ // texture
4020
+ 'out vec4 c;'+ // out color
4021
+ 'void main(){'+ // shader entry point
4022
+ 'c=texture(s,v.xy)*d+e;'+ // modulate texture by color plus additive
4023
+ '}' // end of shader
3767
4024
  );
3768
4025
 
3769
4026
  // init buffers
@@ -3784,7 +4041,7 @@ function glPreRender()
3784
4041
  // set up the shader
3785
4042
  glContext.useProgram(glShader);
3786
4043
  glContext.activeTexture(gl_TEXTURE0);
3787
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
4044
+ glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
3788
4045
  glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
3789
4046
  glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
3790
4047
  glSetBlendMode();
@@ -3819,22 +4076,23 @@ function glPreRender()
3819
4076
  /** Set the WebGl blend mode, normally you should call setBlendMode instead
3820
4077
  * @param {Boolean} [additive=0]
3821
4078
  * @memberof WebGL */
3822
- function glSetBlendMode(additive)
4079
+ function glSetBlendMode(additive=0)
3823
4080
  {
3824
4081
  // setup blending
3825
4082
  glAdditive = additive;
3826
4083
  }
3827
4084
 
3828
- /** Set the WebGl texture, not normally necessary unless multiple tile sheets are used
4085
+ /** Set the WebGl texture, called automatically if using multiple textures
3829
4086
  * - This may also flush the gl buffer resulting in more draw calls and worse performance
3830
- * @param {WebGLTexture} [texture=glTileTexture]
4087
+ * @param {WebGLTexture} texture
3831
4088
  * @memberof WebGL */
3832
- function glSetTexture(texture=glTileTexture)
4089
+ function glSetTexture(texture)
3833
4090
  {
3834
4091
  // must flush cache with the old texture to set a new one
3835
- if (texture != glActiveTexture)
3836
- glFlush();
4092
+ if (texture == glActiveTexture)
4093
+ return;
3837
4094
 
4095
+ glFlush();
3838
4096
  glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = texture);
3839
4097
  }
3840
4098
 
@@ -4013,25 +4271,28 @@ function glInitPostProcess(shaderCode, includeOverlay)
4013
4271
  {
4014
4272
  ASSERT(!glPostShader); // can only have 1 post effects shader
4015
4273
 
4016
- if (!shaderCode) // default shader
4017
- shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture2D(iChannel0,p/iResolution.xy);}';
4274
+ if (!shaderCode) // default shader pass through
4275
+ shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
4018
4276
 
4019
4277
  // create the shader
4020
4278
  glPostShader = glCreateProgram(
4279
+ '#version 300 es\n' + // specify GLSL ES version
4021
4280
  'precision highp float;'+ // use highp for better accuracy
4022
- 'attribute vec2 p;'+ // position
4281
+ 'in vec2 p;'+ // position
4023
4282
  'void main(){'+ // shader entry point
4024
4283
  'gl_Position=vec4(p,1,1);'+ // set position
4025
4284
  '}' // end of shader
4026
4285
  ,
4286
+ '#version 300 es\n' + // specify GLSL ES version
4027
4287
  'precision highp float;'+ // use highp for better accuracy
4028
4288
  'uniform sampler2D iChannel0;'+ // input texture
4029
4289
  'uniform vec3 iResolution;'+ // size of output texture
4030
4290
  'uniform float iTime;'+ // time passed
4291
+ 'out vec4 c;'+ // out color
4031
4292
  '\n' + shaderCode + '\n'+ // insert custom shader code
4032
4293
  'void main(){'+ // shader entry point
4033
- 'mainImage(gl_FragColor,gl_FragCoord.xy);'+ // call post process function
4034
- 'gl_FragColor.a=1.;'+ // always use full alpha
4294
+ 'mainImage(c,gl_FragCoord.xy);'+ // call post process function
4295
+ 'c.a=1.;'+ // always use full alpha
4035
4296
  '}' // end of shader
4036
4297
  );
4037
4298
 
@@ -4104,7 +4365,6 @@ gl_ONE_MINUS_SRC_ALPHA = 771,
4104
4365
  gl_BLEND = 3042,
4105
4366
  gl_TEXTURE_2D = 3553,
4106
4367
  gl_UNSIGNED_BYTE = 5121,
4107
- gl_BYTE = 5120,
4108
4368
  gl_FLOAT = 5126,
4109
4369
  gl_RGBA = 6408,
4110
4370
  gl_NEAREST = 9728,
@@ -4116,7 +4376,6 @@ gl_TEXTURE_WRAP_T = 10243,
4116
4376
  gl_COLOR_BUFFER_BIT = 16384,
4117
4377
  gl_CLAMP_TO_EDGE = 33071,
4118
4378
  gl_TEXTURE0 = 33984,
4119
- gl_TEXTURE1 = 33985,
4120
4379
  gl_ARRAY_BUFFER = 34962,
4121
4380
  gl_STATIC_DRAW = 35044,
4122
4381
  gl_DYNAMIC_DRAW = 35048,
@@ -4127,7 +4386,6 @@ gl_LINK_STATUS = 35714,
4127
4386
  gl_UNPACK_FLIP_Y_WEBGL = 37440,
4128
4387
 
4129
4388
  // constants for batch rendering
4130
- gl_VERTICES_PER_QUAD = 6,
4131
4389
  gl_INDICIES_PER_VERT = 6,
4132
4390
  gl_MAX_BATCH = 1e5,
4133
4391
  gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
@@ -4164,7 +4422,7 @@ const engineName = 'LittleJS';
4164
4422
  * @type {String}
4165
4423
  * @default
4166
4424
  * @memberof Engine */
4167
- const engineVersion = '1.7.21';
4425
+ const engineVersion = '1.8.1';
4168
4426
 
4169
4427
  /** Frames per second to update objects
4170
4428
  * @type {Number}
@@ -4214,6 +4472,9 @@ let paused = 0;
4214
4472
  * @memberof Engine */
4215
4473
  function setPaused(_paused) { paused = _paused; }
4216
4474
 
4475
+ // Frame time tracking
4476
+ let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
4477
+
4217
4478
  ///////////////////////////////////////////////////////////////////////////////
4218
4479
 
4219
4480
  /** Start up LittleJS engine with your callback functions
@@ -4222,52 +4483,13 @@ function setPaused(_paused) { paused = _paused; }
4222
4483
  * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
4223
4484
  * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
4224
4485
  * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
4225
- * @param {String} [tileImageSource] - Tile image to use, everything starts when the image is finished loading
4486
+ * @param {String} [imageSources='tiles.png'] - Image to load
4226
4487
  * @memberof Engine */
4227
- function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, tileImageSource)
4488
+ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=['tiles.png'])
4228
4489
  {
4229
- // init engine when tiles load or fail to load
4230
- tileImage.onerror = tileImage.onload = ()=>
4231
- {
4232
- // save tile image info
4233
- tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
4234
- debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
4235
-
4236
- // setup html
4237
- const styleBody =
4238
- 'margin:0;overflow:hidden;' + // fill the window
4239
- 'background:#000;' + // set background color
4240
- 'touch-action:none;' + // prevent mobile pinch to resize
4241
- 'user-select:none;' + // prevent mobile hold to select
4242
- '-webkit-user-select:none;' + // compatibility for ios
4243
- '-webkit-touch-callout:none'; // compatibility for ios
4244
- document.body.style = styleBody;
4245
- document.body.appendChild(mainCanvas = document.createElement('canvas'));
4246
- mainContext = mainCanvas.getContext('2d');
4247
-
4248
- // init stuff and start engine
4249
- debugInit();
4250
- glEnable && glInit();
4251
-
4252
- // create overlay canvas for hud to appear above gl canvas
4253
- document.body.appendChild(overlayCanvas = document.createElement('canvas'));
4254
- overlayContext = overlayCanvas.getContext('2d');
4255
-
4256
- // set canvas style
4257
- const styleCanvas =
4258
- 'position:absolute;' + // position canvas
4259
- 'top:50%;left:50%;transform:translate(-50%,-50%);' + // center the canvas
4260
- (canvasPixelated?'image-rendering:pixelated':''); // set pixelated rendering
4261
- (glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
4262
-
4263
- gameInit();
4264
- engineUpdate();
4265
- };
4490
+ ASSERT(Array.isArray(imageSources)); // pass in images as array
4266
4491
 
4267
- // frame time tracking
4268
- let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
4269
-
4270
- // main update loop
4492
+ // internal update loop for engine
4271
4493
  function engineUpdate(frameTimeMS=0)
4272
4494
  {
4273
4495
  // update time keeping
@@ -4298,9 +4520,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4298
4520
  }
4299
4521
  else
4300
4522
  {
4301
- // clear canvas and set size to same as window
4302
- mainCanvas.width = min(innerWidth, canvasMaxSize.x);
4303
- mainCanvas.height = min(innerHeight, canvasMaxSize.y);
4523
+ // clear canvas and set size to same as window
4524
+ mainCanvas.width = min(innerWidth, canvasMaxSize.x);
4525
+ mainCanvas.height = min(innerHeight, canvasMaxSize.y);
4304
4526
  }
4305
4527
 
4306
4528
  // clear overlay canvas and set size
@@ -4332,6 +4554,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4332
4554
  // update multiple frames if necessary in case of slow framerate
4333
4555
  for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / frameRate)
4334
4556
  {
4557
+ // increment frame and update time
4558
+ time = frame++ / frameRate;
4559
+
4335
4560
  // update game and objects
4336
4561
  inputUpdate();
4337
4562
  gameUpdate();
@@ -4379,8 +4604,51 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4379
4604
  requestAnimationFrame(engineUpdate);
4380
4605
  }
4381
4606
 
4382
- // set tile image source to load the image and start the engine
4383
- tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
4607
+ // setup html
4608
+ const styleBody =
4609
+ 'margin:0;overflow:hidden;' + // fill the window
4610
+ 'background:#000;' + // set background color
4611
+ 'touch-action:none;' + // prevent mobile pinch to resize
4612
+ 'user-select:none;' + // prevent mobile hold to select
4613
+ '-webkit-user-select:none;' + // compatibility for ios
4614
+ '-webkit-touch-callout:none'; // compatibility for ios
4615
+ document.body.style = styleBody;
4616
+ document.body.appendChild(mainCanvas = document.createElement('canvas'));
4617
+ mainContext = mainCanvas.getContext('2d');
4618
+
4619
+ // init stuff and start engine
4620
+ debugInit();
4621
+ glEnable && glInit();
4622
+
4623
+ // create overlay canvas for hud to appear above gl canvas
4624
+ document.body.appendChild(overlayCanvas = document.createElement('canvas'));
4625
+ overlayContext = overlayCanvas.getContext('2d');
4626
+
4627
+ // set canvas style
4628
+ const styleCanvas =
4629
+ 'position:absolute;' + // position
4630
+ 'top:50%;left:50%;transform:translate(-50%,-50%);' + // center
4631
+ (canvasPixelated?'image-rendering:pixelated':''); // pixelated rendering
4632
+ (glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
4633
+
4634
+ // load all of the images
4635
+ Promise.all(imageSources.map((src, textureIndex)=>
4636
+ new Promise((resolve, reject)=>
4637
+ {
4638
+ const image = new Image;
4639
+ image.onerror = image.onload = ()=>
4640
+ {
4641
+ textureInfos[textureIndex] = new TextureInfo(image);
4642
+ resolve();
4643
+ }
4644
+ image.src = src;
4645
+ })
4646
+ )).then(()=>
4647
+ {
4648
+ // start the engine
4649
+ gameInit();
4650
+ engineUpdate();
4651
+ });
4384
4652
  }
4385
4653
 
4386
4654
  // Called automatically by engine to setup render system
@@ -4418,9 +4686,6 @@ function engineObjectsUpdate()
4418
4686
 
4419
4687
  // remove destroyed objects
4420
4688
  engineObjects = engineObjects.filter(o=>!o.destroyed);
4421
-
4422
- // increment frame and update time
4423
- time = ++frame / frameRate;
4424
4689
  }
4425
4690
 
4426
4691
  /** Destroy and remove all objects