littlejsengine 1.7.21 → 1.8.3

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 (52) hide show
  1. package/README.md +28 -55
  2. package/build/littlejs.d.ts +595 -552
  3. package/build/littlejs.esm.js +626 -535
  4. package/build/littlejs.esm.min.js +1 -1
  5. package/build/littlejs.js +542 -264
  6. package/build/littlejs.min.js +1 -1
  7. package/build/littlejs.release.js +542 -264
  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 +3 -3
  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 +5 -4
  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 +4 -4
  38. package/examples/typescript/game.js +4 -3
  39. package/examples/typescript/game.ts +5 -4
  40. package/examples/typescript/index.html +1 -1
  41. package/package.json +2 -1
  42. package/src/engine.js +59 -52
  43. package/src/engineAudio.js +2 -1
  44. package/src/engineDraw.js +171 -55
  45. package/src/engineExport.js +84 -271
  46. package/src/engineMedals.js +13 -45
  47. package/src/engineObject.js +13 -13
  48. package/src/engineParticles.js +17 -17
  49. package/src/engineSettings.js +195 -2
  50. package/src/engineTileLayer.js +23 -22
  51. package/src/engineUtilities.js +6 -6
  52. package/src/engineWebGL.js +43 -50
@@ -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,20 @@ 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
+ ASSERT(!(renderOrder instanceof Color)); // prevent old style calls
1185
+ // to fix old calls, replace with tile(tileIndex, tileSize)
991
1186
 
992
1187
  /** @property {Vector2} - World space position of the object */
993
1188
  this.pos = pos.copy();
@@ -995,10 +1190,8 @@ class EngineObject
995
1190
  this.size = size;
996
1191
  /** @property {Vector2} - Size of object used for drawing, uses size if not set */
997
1192
  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;
1193
+ /** @property {TileInfo} - Tile info to render object (undefined is untextured) */
1194
+ this.tileInfo = tileInfo;
1002
1195
  /** @property {Number} - Angle to rotate the object */
1003
1196
  this.angle = angle;
1004
1197
  /** @property {Color} - Color to apply when rendered */
@@ -1214,7 +1407,7 @@ class EngineObject
1214
1407
  render()
1215
1408
  {
1216
1409
  // default object render
1217
- drawTile(this.pos, this.drawSize || this.size, this.tileIndex, this.tileSize, this.color, this.angle, this.mirror, this.additiveColor);
1410
+ drawTile(this.pos, this.drawSize || this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
1218
1411
  }
1219
1412
 
1220
1413
  /** Destroy this object, destroy it's children, detach it's parent, and mark it for removal */
@@ -1369,13 +1562,109 @@ let overlayContext;
1369
1562
  * @memberof Draw */
1370
1563
  let mainCanvasSize = vec2();
1371
1564
 
1372
- /** Tile sheet for batch rendering system
1373
- * @type {CanvasImageSource}
1565
+ /** Array containing texture info for batch rendering system
1566
+ * @type {Array}
1374
1567
  * @memberof Draw */
1375
- const tileImage = new Image;
1568
+ let textureInfos = [];
1376
1569
 
1377
1570
  // Engine internal variables not exposed to documentation
1378
- let tileImageSize, tileImageFixBleed, drawCount;
1571
+ let drawCount;
1572
+
1573
+ ///////////////////////////////////////////////////////////////////////////////
1574
+
1575
+ /**
1576
+ * Create a tile info object
1577
+ * - This can take vecs or floats for easier use and conversion
1578
+ * - If an index is passed in, the tile size and index will determine the position
1579
+ * @param {(Number|Vector2)} [pos=Vector2()] - Top left corner of tile in pixels or index
1580
+ * @param {(Number|Vector2)} [size=tileSizeDefault] - Size of tile in pixels
1581
+ * @param {Number} [textureIndex=0] - Texture index to use
1582
+ * @return {TileInfo}
1583
+ * @example
1584
+ * tile(2) // a tile at index 2 using the default tile size of 16
1585
+ * tile(5, 8) // a tile at index 5 using a tile size of 8
1586
+ * tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
1587
+ * tile(vec2(4,8), vec2(30,10)) // a tile at pixel location (4,8) with a size of (30,10)
1588
+ * @memberof Draw
1589
+ */
1590
+ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
1591
+ {
1592
+ // if size is a number, make it a vector
1593
+ if (size.x == undefined)
1594
+ {
1595
+ ASSERT(size > 0);
1596
+ size = vec2(size);
1597
+ }
1598
+
1599
+ // if pos is a number, use it as a tile index
1600
+ if (pos.x == undefined)
1601
+ {
1602
+ const textureInfo = textureInfos[textureIndex];
1603
+ if (textureInfo)
1604
+ {
1605
+ const cols = textureInfo.size.x / size.x |0;
1606
+ pos = vec2((pos%cols)*size.x, (pos/cols|0)*size.y);
1607
+ }
1608
+ else
1609
+ pos = vec2();
1610
+ }
1611
+
1612
+ // return a tile info object
1613
+ return new TileInfo(pos, size, textureIndex);
1614
+ }
1615
+
1616
+ /**
1617
+ * Tile Info - Stores info about how to draw a tile
1618
+ */
1619
+ class TileInfo
1620
+ {
1621
+ /** Create a tile info object
1622
+ * @param {Vector2} [pos=Vector2()] - Top left corner of tile in pixels
1623
+ * @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
1624
+ * @param {Number} [textureIndex=0] - Texture index to use
1625
+ */
1626
+ constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
1627
+ {
1628
+ /** @property {Vector2} - Top left corner of tile in pixels */
1629
+ this.pos = pos;
1630
+ /** @property {Vector2} - Size of tile in pixels */
1631
+ this.size = size;
1632
+ /** @property {Number} - Texture index to use */
1633
+ this.textureIndex = textureIndex;
1634
+ }
1635
+
1636
+ /** Returns an offset copy of this tile, useful for animation
1637
+ * @param {Vector2} offset - Offset to apply in pixels
1638
+ * @return {TileInfo}
1639
+ */
1640
+ offset(offset)
1641
+ { return new TileInfo(this.pos.add(offset), this.size, this.textureIndex); }
1642
+
1643
+ /** Returns the texture info for this tile
1644
+ * @return {TextureInfo}
1645
+ */
1646
+ getTextureInfo()
1647
+ { return textureInfos[this.textureIndex]; }
1648
+ }
1649
+
1650
+ /** Texture Info - Stores info about each texture */
1651
+ class TextureInfo
1652
+ {
1653
+ // create a TextureInfo, called automatically by the engine
1654
+ constructor(image)
1655
+ {
1656
+ /** @property {CanvasImageSource} - image source */
1657
+ this.image = image;
1658
+ /** @property {Vector2} - size of the image */
1659
+ this.size = vec2(image.width, image.height);
1660
+ /** @property {WebGLTexture} - webgl texture */
1661
+ this.glTexture = glEnable && glCreateTexture(image);
1662
+ /** @property {Vector2} - size to adjust tile to fix bleeding */
1663
+ this.fixBleedSize = vec2(tileFixBleedScale).divide(this.size);
1664
+ }
1665
+ }
1666
+
1667
+ ///////////////////////////////////////////////////////////////////////////////
1379
1668
 
1380
1669
  /** Convert from screen to world space coordinates
1381
1670
  * @param {Vector2} screenPos
@@ -1406,7 +1695,7 @@ function worldToScreen(worldPos)
1406
1695
  /** Draw textured tile centered in world space, with color applied if using WebGL
1407
1696
  * @param {Vector2} pos - Center of the tile in world space
1408
1697
  * @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
1698
+ * @param {TileInfo}[tileInfo] - Tile info to use, untextured if undefined
1410
1699
  * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
1411
1700
  * @param {Color} [color=Color()] - Color to modulate with
1412
1701
  * @param {Number} [angle=0] - Angle to rotate by
@@ -1414,13 +1703,18 @@ function worldToScreen(worldPos)
1414
1703
  * @param {Color} [additiveColor=Color(0,0,0,0)] - Additive color to be applied
1415
1704
  * @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
1416
1705
  * @param {Boolean} [screenSpace=0] - If true the pos and size are in screen space
1706
+ * @param {CanvasRenderingContext2D} [context] - Canvas 2D context to draw to
1417
1707
  * @memberof Draw */
1418
- function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color,
1419
- angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace)
1708
+ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
1709
+ angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace, context)
1420
1710
  {
1711
+ ASSERT(!context || !useWebGL); // context only supported in canvas 2D mode
1712
+ ASSERT(typeof tileInfo !== 'number' || !tileInfo); // prevent old style calls
1713
+ // to fix old calls, replace with tile(tileIndex, tileSize)
1714
+
1421
1715
  showWatermark && ++drawCount;
1422
-
1423
- if (glEnable && useWebGL)
1716
+ const textureInfo = tileInfo && tileInfo.getTextureInfo();
1717
+ if (useWebGL)
1424
1718
  {
1425
1719
  if (screenSpace)
1426
1720
  {
@@ -1428,48 +1722,50 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
1428
1722
  pos = screenToWorld(pos);
1429
1723
  size = size.scale(1/cameraScale);
1430
1724
  }
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
1725
+
1726
+ if (textureInfo)
1437
1727
  {
1438
1728
  // 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
-
1729
+ const x = tileInfo.pos.x / textureInfo.size.x;
1730
+ const y = tileInfo.pos.y / textureInfo.size.y;
1731
+ const w = tileInfo.size.x / textureInfo.size.x;
1732
+ const h = tileInfo.size.y / textureInfo.size.y;
1733
+ const tileImageFixBleed = textureInfo.fixBleedSize;
1734
+ glSetTexture(textureInfo.glTexture);
1444
1735
  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,
1736
+ x + tileImageFixBleed.x, y + tileImageFixBleed.y,
1737
+ x - tileImageFixBleed.x + w, y - tileImageFixBleed.y + h,
1447
1738
  color.rgbaInt(), additiveColor.rgbaInt());
1448
1739
  }
1740
+ else
1741
+ {
1742
+ // if no tile info, force untextured
1743
+ glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
1744
+ }
1449
1745
  }
1450
1746
  else
1451
1747
  {
1452
1748
  // normal canvas 2D rendering method (slower)
1453
1749
  drawCanvas2D(pos, size, angle, mirror, (context)=>
1454
1750
  {
1455
- if (tileIndex < 0)
1751
+ if (textureInfo)
1456
1752
  {
1457
- // if negative tile index, force untextured
1458
- context.fillStyle = color;
1459
- context.fillRect(-.5, -.5, 1, 1);
1753
+ // calculate uvs and render
1754
+ const x = tileInfo.pos.x + tileFixBleedScale;
1755
+ const y = tileInfo.pos.y + tileFixBleedScale;
1756
+ const w = tileInfo.size.x - 2*tileFixBleedScale;
1757
+ const h = tileInfo.size.y - 2*tileFixBleedScale;
1758
+ context.globalAlpha = color.a; // only alpha is supported
1759
+ context.drawImage(textureInfo.image, x, y, w, h, -.5, -.5, 1, 1);
1760
+ context.globalAlpha = 1; // set back to full alpha
1460
1761
  }
1461
1762
  else
1462
1763
  {
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);
1764
+ // if no tile info, force untextured
1765
+ context.fillStyle = color;
1766
+ context.fillRect(-.5, -.5, 1, 1);
1471
1767
  }
1472
- }, undefined, screenSpace);
1768
+ }, screenSpace, context);
1473
1769
  }
1474
1770
  }
1475
1771
 
@@ -1480,28 +1776,36 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
1480
1776
  * @param {Number} [angle=0]
1481
1777
  * @param {Boolean} [useWebGL=glEnable]
1482
1778
  * @param {Boolean} [screenSpace=0]
1779
+ * @param {CanvasRenderingContext2D} [context]
1483
1780
  * @memberof Draw */
1484
- function drawRect(pos, size, color, angle, useWebGL, screenSpace)
1485
- { drawTile(pos, size, -1, tileSizeDefault, color, angle, 0, undefined, useWebGL, screenSpace); }
1781
+ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
1782
+ {
1783
+ drawTile(pos, size, undefined, color, angle, 0, undefined, useWebGL, screenSpace, context);
1784
+ }
1486
1785
 
1487
1786
  /** Draw colored polygon using passed in points
1488
1787
  * @param {Array} points - Array of Vector2 points
1489
1788
  * @param {Color} [color=Color()]
1490
1789
  * @param {Boolean} [useWebGL=glEnable]
1491
1790
  * @param {Boolean} [screenSpace=0]
1791
+ * @param {CanvasRenderingContext2D} [context]
1492
1792
  * @memberof Draw */
1493
- function drawPoly(points, color=new Color, useWebGL=glEnable, screenSpace)
1793
+ function drawPoly(points, color=new Color, useWebGL=glEnable, screenSpace, context)
1494
1794
  {
1795
+ ASSERT(!context || !useWebGL); // context only supported in canvas 2D mode
1796
+
1495
1797
  if (useWebGL)
1496
1798
  glDrawPoints(screenSpace ? points.map(screenToWorld) : points, color.rgbaInt());
1497
1799
  else
1498
1800
  {
1499
1801
  // draw using canvas
1500
- mainContext.fillStyle = color;
1501
- mainContext.beginPath();
1802
+ if (!context)
1803
+ context = mainContext;
1804
+ context.fillStyle = color;
1805
+ context.beginPath();
1502
1806
  for (const point of screenSpace ? points : points.map(worldToScreen))
1503
- mainContext.lineTo(point.x, point.y);
1504
- mainContext.fill();
1807
+ context.lineTo(point.x, point.y);
1808
+ context.fill();
1505
1809
  }
1506
1810
  }
1507
1811
 
@@ -1512,12 +1816,13 @@ function drawPoly(points, color=new Color, useWebGL=glEnable, screenSpace)
1512
1816
  * @param {Color} [color=Color()]
1513
1817
  * @param {Boolean} [useWebGL=glEnable]
1514
1818
  * @param {Boolean} [screenSpace=0]
1819
+ * @param {CanvasRenderingContext2D} [context]
1515
1820
  * @memberof Draw */
1516
- function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace)
1821
+ function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
1517
1822
  {
1518
1823
  const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
1519
1824
  const size = vec2(thickness, halfDelta.length()*2);
1520
- drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), useWebGL, screenSpace);
1825
+ drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), useWebGL, screenSpace, context);
1521
1826
  }
1522
1827
 
1523
1828
  /** Draw directly to a 2d canvas context in world space
@@ -1526,10 +1831,10 @@ function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace)
1526
1831
  * @param {Number} angle
1527
1832
  * @param {Boolean} mirror
1528
1833
  * @param {Function} drawFunction
1529
- * @param {CanvasRenderingContext2D} [context=mainContext]
1530
1834
  * @param {Boolean} [screenSpace=0]
1835
+ * @param {CanvasRenderingContext2D} [context=mainContext]
1531
1836
  * @memberof Draw */
1532
- function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainContext, screenSpace)
1837
+ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, context=mainContext)
1533
1838
  {
1534
1839
  if (!screenSpace)
1535
1840
  {
@@ -1548,13 +1853,19 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
1548
1853
  /** Enable normal or additive blend mode
1549
1854
  * @param {Boolean} [additive=0]
1550
1855
  * @param {Boolean} [useWebGL=glEnable]
1856
+ * @param {CanvasRenderingContext2D} [context=mainContext]
1551
1857
  * @memberof Draw */
1552
- function setBlendMode(additive, useWebGL=glEnable)
1858
+ function setBlendMode(additive, useWebGL=glEnable, context)
1553
1859
  {
1554
- if (glEnable && useWebGL)
1555
- glSetBlendMode(additive);
1860
+ ASSERT(!context || !useWebGL); // context only supported in canvas 2D mode
1861
+ if (useWebGL)
1862
+ glAdditive = additive;
1556
1863
  else
1557
- mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
1864
+ {
1865
+ if (!context)
1866
+ context = mainContext;
1867
+ context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
1868
+ }
1558
1869
  }
1559
1870
 
1560
1871
  /** Draw text on overlay canvas in world space
@@ -1627,10 +1938,9 @@ class FontImage
1627
1938
  * @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
1628
1939
  * @param {Vector2} [tileSize=vec2(8)] - Size of the font source tiles
1629
1940
  * @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
1941
  * @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
1632
1942
  */
1633
- constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
1943
+ constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), context=overlayContext)
1634
1944
  {
1635
1945
  // load default font image
1636
1946
  if (!engineFontImage)
@@ -1639,7 +1949,6 @@ class FontImage
1639
1949
  this.image = image || engineFontImage;
1640
1950
  this.tileSize = tileSize;
1641
1951
  this.paddingSize = paddingSize;
1642
- this.startTileIndex = startTileIndex;
1643
1952
  this.context = context;
1644
1953
  }
1645
1954
 
@@ -1680,7 +1989,7 @@ class FontImage
1680
1989
  charCode = 127; // unknown character
1681
1990
 
1682
1991
  // get the character source location and draw it
1683
- const tile = this.startTileIndex + charCode - 32;
1992
+ const tile = charCode - 32;
1684
1993
  const x = tile % cols;
1685
1994
  const y = tile / cols |0;
1686
1995
  const drawPos = pos.add(vec2(j,i).multiply(drawSize));
@@ -1712,8 +2021,7 @@ function toggleFullscreen()
1712
2021
  }
1713
2022
  else if (document.body.requestFullscreen)
1714
2023
  document.body.requestFullscreen();
1715
- }
1716
-
2024
+ }
1717
2025
  /**
1718
2026
  * LittleJS Input System
1719
2027
  * - Tracks keyboard down, pressed, and released
@@ -2209,7 +2517,8 @@ class Sound
2209
2517
  if (zzfxSound)
2210
2518
  {
2211
2519
  // generate zzfx sound now for fast playback
2212
- this.randomness = zzfxSound[1] || (zzfxSound[1] = 0);
2520
+ this.randomness = zzfxSound[1];
2521
+ zzfxSound[1] = 0; // generate without randomness
2213
2522
  this.sampleChannels = [zzfxG(...zzfxSound)];
2214
2523
  this.sampleRate = zzfxR;
2215
2524
  }
@@ -2871,7 +3180,7 @@ class TileLayerData
2871
3180
  }
2872
3181
 
2873
3182
  /**
2874
- * Tile layer object - cached rendering system for tile layers
3183
+ * Tile Layer - cached rendering system for tile layers
2875
3184
  * - Each Tile layer is rendered to an off screen canvas
2876
3185
  * - To allow dynamic modifications, layers are rendered using canvas 2d
2877
3186
  * - Some devices like mobile phones are limited to 4k texture resolution
@@ -2887,13 +3196,13 @@ class TileLayer extends EngineObject
2887
3196
  /** Create a tile layer object
2888
3197
  * @param {Vector2} [position=Vector2()] - World space position
2889
3198
  * @param {Vector2} [size=tileCollisionSize] - World space size
2890
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
3199
+ * @param {TileInfo} [tileInfo] - Tile info for layer
2891
3200
  * @param {Vector2} [scale=Vector2(1,1)] - How much to scale this layer when rendered
2892
3201
  * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
2893
3202
  */
2894
- constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
3203
+ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderOrder=0)
2895
3204
  {
2896
- super(pos, size, -1, tileSize, 0, undefined, renderOrder);
3205
+ super(pos, size, tileInfo, 0, undefined, renderOrder);
2897
3206
 
2898
3207
  /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
2899
3208
  this.canvas = document.createElement('canvas');
@@ -2970,13 +3279,13 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
2970
3279
  mainCanvas = this.canvas;
2971
3280
  mainContext = this.context;
2972
3281
  cameraPos = this.size.scale(.5);
2973
- cameraScale = this.tileSize.x;
3282
+ cameraScale = this.tileInfo.size.x;
2974
3283
 
2975
3284
  if (clear)
2976
3285
  {
2977
3286
  // clear and set size
2978
- mainCanvas.width = this.size.x * this.tileSize.x;
2979
- mainCanvas.height = this.size.y * this.tileSize.y;
3287
+ mainCanvas.width = this.size.x * this.tileInfo.size.x;
3288
+ mainCanvas.height = this.size.y * this.tileInfo.size.y;
2980
3289
  }
2981
3290
 
2982
3291
  // begin a new render for the tile canvas
@@ -3007,7 +3316,8 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3007
3316
  if (d.tile != undefined)
3008
3317
  {
3009
3318
  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);
3319
+ const tileInfo = tile(d.tile, this.tileInfo.size, this.tileInfo.textureIndex);
3320
+ drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
3011
3321
  }
3012
3322
  }
3013
3323
 
@@ -3029,8 +3339,8 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3029
3339
  {
3030
3340
  const context = this.context;
3031
3341
  context.save();
3032
- pos = pos.subtract(this.pos).multiply(this.tileSize);
3033
- size = size.multiply(this.tileSize);
3342
+ pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
3343
+ size = size.multiply(this.tileInfo.size);
3034
3344
  context.translate(pos.x, this.canvas.height - pos.y);
3035
3345
  context.rotate(angle);
3036
3346
  context.scale(mirror ? -size.x : size.x, size.y);
@@ -3041,28 +3351,28 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3041
3351
  /** Draw a tile directly onto the layer canvas
3042
3352
  * @param {Vector2} pos
3043
3353
  * @param {Vector2} [size=Vector2(1,1)]
3044
- * @param {Number} [tileIndex=-1]
3045
- * @param {Vector2} [tileSize=tileSizeDefault]
3354
+ * @param {TileInfo} [tileInfo]
3046
3355
  * @param {Color} [color=Color()]
3047
3356
  * @param {Number} [angle=0]
3048
3357
  * @param {Boolean} [mirror=0] */
3049
- drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle, mirror)
3358
+ drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle, mirror)
3050
3359
  {
3051
3360
  this.drawCanvas2D(pos, size, angle, mirror, (context)=>
3052
3361
  {
3053
- if (tileIndex < 0)
3362
+ const textureInfo = tileInfo && tileInfo.getTextureInfo();
3363
+ if (textureInfo)
3054
3364
  {
3055
- // untextured
3056
- context.fillStyle = color;
3057
- context.fillRect(-.5, -.5, 1, 1);
3365
+ context.globalAlpha = color.a; // only alpha is supported
3366
+ context.drawImage(textureInfo.image,
3367
+ tileInfo.pos.x, tileInfo.pos.y,
3368
+ tileInfo.size.x, tileInfo.size.y, -.5, -.5, 1, 1);
3369
+ context.globalAlpha = 1;
3058
3370
  }
3059
3371
  else
3060
3372
  {
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);
3373
+ // untextured
3374
+ context.fillStyle = color;
3375
+ context.fillRect(-.5, -.5, 1, 1);
3066
3376
  }
3067
3377
  });
3068
3378
  }
@@ -3090,7 +3400,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3090
3400
  * let particleEmiter = new ParticleEmitter
3091
3401
  * (
3092
3402
  * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
3093
- * 0, vec2(16), // tileIndex, tileSize
3403
+ * tile(0, 16), // tileInfo
3094
3404
  * new Color(1,1,1), new Color(0,0,0), // colorStartA, colorStartB
3095
3405
  * new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
3096
3406
  * 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
@@ -3107,8 +3417,7 @@ class ParticleEmitter extends EngineObject
3107
3417
  * @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
3108
3418
  * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
3109
3419
  * @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
3420
+ * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
3112
3421
  * @param {Color} [colorStartA=Color()] - Color at start of life 1, randomized between start colors
3113
3422
  * @param {Color} [colorStartB=Color()] - Color at start of life 2, randomized between start colors
3114
3423
  * @param {Color} [colorEndA=Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
@@ -3138,8 +3447,7 @@ class ParticleEmitter extends EngineObject
3138
3447
  emitTime = 0,
3139
3448
  emitRate = 100,
3140
3449
  emitConeAngle = PI,
3141
- tileIndex = -1,
3142
- tileSize = tileSizeDefault,
3450
+ tileInfo,
3143
3451
  colorStartA = new Color,
3144
3452
  colorStartB = new Color,
3145
3453
  colorEndA = new Color(1,1,1,0),
@@ -3162,7 +3470,7 @@ class ParticleEmitter extends EngineObject
3162
3470
  localSpace
3163
3471
  )
3164
3472
  {
3165
- super(pos, vec2(), tileIndex, tileSize, angle, undefined, renderOrder);
3473
+ super(pos, vec2(), tileInfo, angle, undefined, renderOrder);
3166
3474
 
3167
3475
  // emitter settings
3168
3476
  /** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
@@ -3261,7 +3569,7 @@ class ParticleEmitter extends EngineObject
3261
3569
  angle += this.angle;
3262
3570
  }
3263
3571
 
3264
- const particle = new Particle(pos, this.tileIndex, this.tileSize, angle);
3572
+ const particle = new Particle(pos, this.tileInfo, this.tileSize, angle);
3265
3573
 
3266
3574
  // randomness scales each paremeter by a percentage
3267
3575
  const randomness = this.randomness;
@@ -3321,12 +3629,11 @@ class Particle extends EngineObject
3321
3629
  /**
3322
3630
  * Create a particle with the given settings
3323
3631
  * @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
3632
+ * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
3326
3633
  * @param {Number} [angle=0] - Angle to rotate the particle
3327
3634
  */
3328
- constructor(pos, tileIndex, tileSize, angle)
3329
- { super(pos, vec2(), tileIndex, tileSize, angle); }
3635
+ constructor(pos, tileInfo, angle)
3636
+ { super(pos, vec2(), tileInfo, angle); }
3330
3637
 
3331
3638
  /** Render the particle, automatically called each frame, sorted by renderOrder */
3332
3639
  render()
@@ -3360,14 +3667,17 @@ class Particle extends EngineObject
3360
3667
  if (this.localSpaceEmitter)
3361
3668
  velocity = velocity.rotate(-this.localSpaceEmitter.angle);
3362
3669
  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);
3670
+ if (speed)
3671
+ {
3672
+ const direction = velocity.scale(1/speed);
3673
+ const trailLength = speed * this.trailScale;
3674
+ size.y = max(size.x, trailLength);
3675
+ angle = direction.angle();
3676
+ drawTile(pos.add(direction.multiply(vec2(0,-trailLength/2))), size, this.tileInfo, color, angle, this.mirror);
3677
+ }
3368
3678
  }
3369
3679
  else
3370
- drawTile(pos, size, this.tileIndex, this.tileSize, color, angle, this.mirror);
3680
+ drawTile(pos, size, this.tileInfo, color, angle, this.mirror);
3371
3681
  this.additive && setBlendMode();
3372
3682
  debugParticles && debugRect(pos, size, '#f005', 0, angle);
3373
3683
 
@@ -3414,7 +3724,7 @@ function medalsInit(saveName)
3414
3724
  }
3415
3725
 
3416
3726
  /**
3417
- * Medal Object - Tracks an unlockable medal
3727
+ * Medal - Tracks an unlockable medal
3418
3728
  * @example
3419
3729
  * // create a medal
3420
3730
  * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
@@ -3427,7 +3737,7 @@ function medalsInit(saveName)
3427
3737
  */
3428
3738
  class Medal
3429
3739
  {
3430
- /** Create an medal object and adds it to the list of medals
3740
+ /** Create a medal object and adds it to the list of medals
3431
3741
  * @param {Number} id - The unique identifier of the medal
3432
3742
  * @param {String} name - Name of the medal
3433
3743
  * @param {String} [description] - Description of the medal
@@ -3539,33 +3849,33 @@ let newgrounds;
3539
3849
  /** This can used to enable Newgrounds functionality
3540
3850
  * @param {Number} app_id - The newgrounds App ID
3541
3851
  * @param {String} [cipher] - The encryption Key (AES-128/Base64)
3852
+ * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher
3542
3853
  * @memberof Medals */
3543
- function newgroundsInit(app_id, cipher) { newgrounds = new Newgrounds(app_id, cipher); }
3854
+ function newgroundsInit(app_id, cipher, cryptoJS)
3855
+ { newgrounds = new Newgrounds(app_id, cipher, cryptoJS); }
3544
3856
 
3545
3857
  /**
3546
3858
  * Newgrounds API wrapper object
3547
3859
  * @example
3548
- * // create a newgrounds object, replace the app id and cipher with your own
3860
+ * // create a newgrounds object, replace the app id with your own
3549
3861
  * const app_id = '53123:1ZuSTQ9l';
3550
- * const cipher = 'enF0vGH@Mj/FRASKL23Q==';
3551
- * newgrounds = new Newgrounds(app_id, cipher);
3862
+ * newgrounds = new Newgrounds(app_id);
3552
3863
  */
3553
3864
  class Newgrounds
3554
3865
  {
3555
3866
  /** Create a newgrounds object
3556
3867
  * @param {Number} app_id - The newgrounds App ID
3557
- * @param {String} [cipher] - The encryption Key (AES-128/Base64) */
3558
- constructor(app_id, cipher)
3868
+ * @param {String} [cipher] - The encryption Key (AES-128/Base64)
3869
+ * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher */
3870
+ constructor(app_id, cipher, cryptoJS)
3559
3871
  {
3560
- ASSERT(!newgrounds && app_id);
3872
+ ASSERT(!newgrounds && app_id); // can only be one newgrounds object
3873
+ ASSERT(!cipher || cryptoJS); // must provide cryptojs if there is a cipher
3561
3874
 
3562
3875
  this.app_id = app_id;
3563
3876
  this.cipher = cipher;
3877
+ this.cryptoJS = cryptoJS;
3564
3878
  this.host = location ? location.hostname : '';
3565
-
3566
- // create an instance of CryptoJS for encrypted calls
3567
- if (cipher)
3568
- this.cryptoJS = this.CryptoJS();
3569
3879
 
3570
3880
  // get session id from url search params
3571
3881
  const url = new URL(location.href);
@@ -3669,38 +3979,6 @@ class Newgrounds
3669
3979
  debugMedals && console.log(xmlHttp.responseText);
3670
3980
  return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
3671
3981
  }
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
3982
  }
3705
3983
  /**
3706
3984
  * LittleJS WebGL Interface
@@ -3710,6 +3988,7 @@ class Newgrounds
3710
3988
  * - Can be disabled with glEnable to revert to 2D canvas rendering
3711
3989
  * - Batches sprite rendering on GPU for incredibly fast performance
3712
3990
  * - Sprite transform math is done in the shader where possible
3991
+ * - Supports shadertoy style post processing shaders
3713
3992
  * @namespace WebGL
3714
3993
  */
3715
3994
 
@@ -3725,11 +4004,6 @@ let glCanvas;
3725
4004
  * @memberof WebGL */
3726
4005
  let glContext;
3727
4006
 
3728
- /** Main tile sheet texture automatically loaded by engine
3729
- * @type {WebGLTexture}
3730
- * @memberof WebGL */
3731
- let glTileTexture;
3732
-
3733
4007
  // WebGL internal variables not exposed to documentation
3734
4008
  let glActiveTexture, glShader, glArrayBuffer, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
3735
4009
 
@@ -3738,32 +4012,33 @@ let glActiveTexture, glShader, glArrayBuffer, glPositionData, glColorData, glBat
3738
4012
  // Initalize WebGL, called automatically by the engine
3739
4013
  function glInit()
3740
4014
  {
3741
- // create the canvas and tile texture
4015
+ // create the canvas and textures
3742
4016
  glCanvas = document.createElement('canvas');
3743
- glContext = glCanvas.getContext('webgl', {antialias: false});
3744
- glTileTexture = glCreateTexture(tileImage);
4017
+ glContext = glCanvas.getContext('webgl2');
3745
4018
 
3746
4019
  // some browsers are much faster without copying the gl buffer so we just overlay it instead
3747
4020
  glOverlay && document.body.appendChild(glCanvas);
3748
4021
 
3749
4022
  // setup vertex and fragment shaders
3750
4023
  glShader = glCreateProgram(
4024
+ '#version 300 es\n' + // specify GLSL ES version
3751
4025
  'precision highp float;'+ // use highp for better accuracy
3752
4026
  '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
4027
+ 'in vec4 p,c,a;'+ // position, uv, color, additiveColor
4028
+ 'out vec4 v,d,e;'+ // return uv, color, additiveColor
3756
4029
  'void main(){'+ // shader entry point
3757
- 'gl_Position=m*vec4(p,1,1);'+ // transform position
3758
- 'v=vec4(t,p);d=c;e=a;'+ // pass stuff to fragment shader
4030
+ 'gl_Position=m*vec4(p.xy,1,1);'+ // transform position
4031
+ 'v=p;d=c;e=a;'+ // pass stuff to fragment shader
3759
4032
  '}' // end of shader
3760
4033
  ,
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
4034
+ '#version 300 es\n' + // specify GLSL ES version
4035
+ 'precision highp float;'+ // use highp for better accuracy
4036
+ 'in vec4 v,d,e;'+ // position, uv, color, additiveColor
4037
+ 'uniform sampler2D s;'+ // texture
4038
+ 'out vec4 c;'+ // out color
4039
+ 'void main(){'+ // shader entry point
4040
+ 'c=texture(s,v.zw)*d+e;'+ // modulate texture by color plus additive
4041
+ '}' // end of shader
3767
4042
  );
3768
4043
 
3769
4044
  // init buffers
@@ -3784,10 +4059,10 @@ function glPreRender()
3784
4059
  // set up the shader
3785
4060
  glContext.useProgram(glShader);
3786
4061
  glContext.activeTexture(gl_TEXTURE0);
3787
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
4062
+ glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
3788
4063
  glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
3789
4064
  glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
3790
- glSetBlendMode();
4065
+ glAdditive = 0;
3791
4066
 
3792
4067
  // set vertex attributes
3793
4068
  let offset = 0;
@@ -3798,43 +4073,36 @@ function glPreRender()
3798
4073
  glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
3799
4074
  offset += size*typeSize;
3800
4075
  }
3801
- initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
3802
- initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
4076
+ initVertexAttribArray('p', gl_FLOAT, 4, 4); // position & texture coords
3803
4077
  initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
3804
4078
  initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
3805
4079
 
3806
4080
  // build the transform matrix
3807
4081
  const sx = 2 * cameraScale / mainCanvas.width;
3808
4082
  const sy = 2 * cameraScale / mainCanvas.height;
4083
+ const cx = -1 - sx*cameraPos.x;
4084
+ const cy = -1 - sy*cameraPos.y;
3809
4085
  glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
3810
4086
  new Float32Array([
3811
- sx, 0, 0, 0,
3812
- 0, sy, 0, 0,
3813
- 1, 1, -1, 1,
3814
- -1-sx*cameraPos.x, -1-sy*cameraPos.y, 0, 0
4087
+ sx, 0, 0, 0,
4088
+ 0, sy, 0, 0,
4089
+ 1, 1, -1, 1,
4090
+ cx, cy, 0, 0
3815
4091
  ])
3816
4092
  );
3817
4093
  }
3818
4094
 
3819
- /** Set the WebGl blend mode, normally you should call setBlendMode instead
3820
- * @param {Boolean} [additive=0]
3821
- * @memberof WebGL */
3822
- function glSetBlendMode(additive)
3823
- {
3824
- // setup blending
3825
- glAdditive = additive;
3826
- }
3827
-
3828
- /** Set the WebGl texture, not normally necessary unless multiple tile sheets are used
4095
+ /** Set the WebGl texture, called automatically if using multiple textures
3829
4096
  * - This may also flush the gl buffer resulting in more draw calls and worse performance
3830
- * @param {WebGLTexture} [texture=glTileTexture]
4097
+ * @param {WebGLTexture} texture
3831
4098
  * @memberof WebGL */
3832
- function glSetTexture(texture=glTileTexture)
4099
+ function glSetTexture(texture)
3833
4100
  {
3834
4101
  // must flush cache with the old texture to set a new one
3835
- if (texture != glActiveTexture)
3836
- glFlush();
4102
+ if (texture == glActiveTexture)
4103
+ return;
3837
4104
 
4105
+ glFlush();
3838
4106
  glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = texture);
3839
4107
  }
3840
4108
 
@@ -4013,25 +4281,28 @@ function glInitPostProcess(shaderCode, includeOverlay)
4013
4281
  {
4014
4282
  ASSERT(!glPostShader); // can only have 1 post effects shader
4015
4283
 
4016
- if (!shaderCode) // default shader
4017
- shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture2D(iChannel0,p/iResolution.xy);}';
4284
+ if (!shaderCode) // default shader pass through
4285
+ shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
4018
4286
 
4019
4287
  // create the shader
4020
4288
  glPostShader = glCreateProgram(
4289
+ '#version 300 es\n' + // specify GLSL ES version
4021
4290
  'precision highp float;'+ // use highp for better accuracy
4022
- 'attribute vec2 p;'+ // position
4291
+ 'in vec2 p;'+ // position
4023
4292
  'void main(){'+ // shader entry point
4024
4293
  'gl_Position=vec4(p,1,1);'+ // set position
4025
4294
  '}' // end of shader
4026
4295
  ,
4296
+ '#version 300 es\n' + // specify GLSL ES version
4027
4297
  'precision highp float;'+ // use highp for better accuracy
4028
4298
  'uniform sampler2D iChannel0;'+ // input texture
4029
4299
  'uniform vec3 iResolution;'+ // size of output texture
4030
4300
  'uniform float iTime;'+ // time passed
4301
+ 'out vec4 c;'+ // out color
4031
4302
  '\n' + shaderCode + '\n'+ // insert custom shader code
4032
4303
  '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
4304
+ 'mainImage(c,gl_FragCoord.xy);'+ // call post process function
4305
+ 'c.a=1.;'+ // always use full alpha
4035
4306
  '}' // end of shader
4036
4307
  );
4037
4308
 
@@ -4056,8 +4327,11 @@ function glRenderPostProcess()
4056
4327
  glFlush(); // clear out the buffer
4057
4328
  mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
4058
4329
  }
4059
- else // set viewport
4330
+ else
4331
+ {
4332
+ // set the viewport
4060
4333
  glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
4334
+ }
4061
4335
 
4062
4336
  if (glPostIncludeOverlay)
4063
4337
  {
@@ -4104,7 +4378,6 @@ gl_ONE_MINUS_SRC_ALPHA = 771,
4104
4378
  gl_BLEND = 3042,
4105
4379
  gl_TEXTURE_2D = 3553,
4106
4380
  gl_UNSIGNED_BYTE = 5121,
4107
- gl_BYTE = 5120,
4108
4381
  gl_FLOAT = 5126,
4109
4382
  gl_RGBA = 6408,
4110
4383
  gl_NEAREST = 9728,
@@ -4116,7 +4389,6 @@ gl_TEXTURE_WRAP_T = 10243,
4116
4389
  gl_COLOR_BUFFER_BIT = 16384,
4117
4390
  gl_CLAMP_TO_EDGE = 33071,
4118
4391
  gl_TEXTURE0 = 33984,
4119
- gl_TEXTURE1 = 33985,
4120
4392
  gl_ARRAY_BUFFER = 34962,
4121
4393
  gl_STATIC_DRAW = 35044,
4122
4394
  gl_DYNAMIC_DRAW = 35048,
@@ -4127,7 +4399,6 @@ gl_LINK_STATUS = 35714,
4127
4399
  gl_UNPACK_FLIP_Y_WEBGL = 37440,
4128
4400
 
4129
4401
  // constants for batch rendering
4130
- gl_VERTICES_PER_QUAD = 6,
4131
4402
  gl_INDICIES_PER_VERT = 6,
4132
4403
  gl_MAX_BATCH = 1e5,
4133
4404
  gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
@@ -4164,7 +4435,7 @@ const engineName = 'LittleJS';
4164
4435
  * @type {String}
4165
4436
  * @default
4166
4437
  * @memberof Engine */
4167
- const engineVersion = '1.7.21';
4438
+ const engineVersion = '1.8.3';
4168
4439
 
4169
4440
  /** Frames per second to update objects
4170
4441
  * @type {Number}
@@ -4214,6 +4485,9 @@ let paused = 0;
4214
4485
  * @memberof Engine */
4215
4486
  function setPaused(_paused) { paused = _paused; }
4216
4487
 
4488
+ // Frame time tracking
4489
+ let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
4490
+
4217
4491
  ///////////////////////////////////////////////////////////////////////////////
4218
4492
 
4219
4493
  /** Start up LittleJS engine with your callback functions
@@ -4222,52 +4496,13 @@ function setPaused(_paused) { paused = _paused; }
4222
4496
  * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
4223
4497
  * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
4224
4498
  * @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
4499
+ * @param {Array} [imageSources=['tiles.png']] - Image to load
4226
4500
  * @memberof Engine */
4227
- function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, tileImageSource)
4501
+ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=['tiles.png'])
4228
4502
  {
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
- };
4266
-
4267
- // frame time tracking
4268
- let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
4503
+ ASSERT(Array.isArray(imageSources)); // pass in images as array
4269
4504
 
4270
- // main update loop
4505
+ // internal update loop for engine
4271
4506
  function engineUpdate(frameTimeMS=0)
4272
4507
  {
4273
4508
  // update time keeping
@@ -4298,9 +4533,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4298
4533
  }
4299
4534
  else
4300
4535
  {
4301
- // clear canvas and set size to same as window
4302
- mainCanvas.width = min(innerWidth, canvasMaxSize.x);
4303
- mainCanvas.height = min(innerHeight, canvasMaxSize.y);
4536
+ // clear canvas and set size to same as window
4537
+ mainCanvas.width = min(innerWidth, canvasMaxSize.x);
4538
+ mainCanvas.height = min(innerHeight, canvasMaxSize.y);
4304
4539
  }
4305
4540
 
4306
4541
  // clear overlay canvas and set size
@@ -4332,6 +4567,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4332
4567
  // update multiple frames if necessary in case of slow framerate
4333
4568
  for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / frameRate)
4334
4569
  {
4570
+ // increment frame and update time
4571
+ time = frame++ / frameRate;
4572
+
4335
4573
  // update game and objects
4336
4574
  inputUpdate();
4337
4575
  gameUpdate();
@@ -4379,8 +4617,51 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4379
4617
  requestAnimationFrame(engineUpdate);
4380
4618
  }
4381
4619
 
4382
- // set tile image source to load the image and start the engine
4383
- tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
4620
+ // setup html
4621
+ const styleBody =
4622
+ 'margin:0;overflow:hidden;' + // fill the window
4623
+ 'background:#000;' + // set background color
4624
+ 'touch-action:none;' + // prevent mobile pinch to resize
4625
+ 'user-select:none;' + // prevent mobile hold to select
4626
+ '-webkit-user-select:none;' + // compatibility for ios
4627
+ '-webkit-touch-callout:none'; // compatibility for ios
4628
+ document.body.style = styleBody;
4629
+ document.body.appendChild(mainCanvas = document.createElement('canvas'));
4630
+ mainContext = mainCanvas.getContext('2d');
4631
+
4632
+ // init stuff and start engine
4633
+ debugInit();
4634
+ glEnable && glInit();
4635
+
4636
+ // create overlay canvas for hud to appear above gl canvas
4637
+ document.body.appendChild(overlayCanvas = document.createElement('canvas'));
4638
+ overlayContext = overlayCanvas.getContext('2d');
4639
+
4640
+ // set canvas style
4641
+ const styleCanvas =
4642
+ 'position:absolute;' + // position
4643
+ 'top:50%;left:50%;transform:translate(-50%,-50%);' + // center
4644
+ (canvasPixelated?'image-rendering:pixelated':''); // pixelated rendering
4645
+ (glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
4646
+
4647
+ // load all of the images
4648
+ Promise.all(imageSources.map((src, textureIndex)=>
4649
+ new Promise((resolve, reject)=>
4650
+ {
4651
+ const image = new Image;
4652
+ image.onerror = image.onload = ()=>
4653
+ {
4654
+ textureInfos[textureIndex] = new TextureInfo(image);
4655
+ resolve();
4656
+ }
4657
+ image.src = src;
4658
+ })
4659
+ )).then(()=>
4660
+ {
4661
+ // start the engine
4662
+ gameInit();
4663
+ engineUpdate();
4664
+ });
4384
4665
  }
4385
4666
 
4386
4667
  // Called automatically by engine to setup render system
@@ -4418,9 +4699,6 @@ function engineObjectsUpdate()
4418
4699
 
4419
4700
  // remove destroyed objects
4420
4701
  engineObjects = engineObjects.filter(o=>!o.destroyed);
4421
-
4422
- // increment frame and update time
4423
- time = ++frame / frameRate;
4424
4702
  }
4425
4703
 
4426
4704
  /** Destroy and remove all objects