littlejsengine 1.7.13 → 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 (58) hide show
  1. package/README.md +27 -55
  2. package/build/littlejs.d.ts +253 -76
  3. package/build/littlejs.esm.js +644 -523
  4. package/build/littlejs.esm.min.js +1 -1
  5. package/build/littlejs.js +640 -332
  6. package/build/littlejs.min.js +1 -1
  7. package/build/littlejs.release.js +640 -332
  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/build.js +2 -0
  15. package/examples/electron/game.js +3 -2
  16. package/examples/electron/index.html +2 -2
  17. package/examples/empty/game.js +1 -1
  18. package/examples/favicon.png +0 -0
  19. package/examples/js13k/build.js +2 -0
  20. package/examples/js13k/game.js +20 -15
  21. package/examples/js13k/index.html +14 -14
  22. package/examples/module/game.js +4 -3
  23. package/examples/module/index.html +1 -1
  24. package/examples/particles/index.html +19 -22
  25. package/examples/platformer/game.js +3 -3
  26. package/examples/platformer/gameEffects.js +21 -19
  27. package/examples/platformer/gameLevel.js +6 -6
  28. package/examples/platformer/gameObjects.js +18 -18
  29. package/examples/platformer/gamePlayer.js +4 -3
  30. package/examples/platformer/index.html +6 -6
  31. package/examples/platformer/tiles.png +0 -0
  32. package/examples/platformer/tilesLevel.png +0 -0
  33. package/examples/puzzle/game.js +10 -8
  34. package/examples/puzzle/index.html +2 -2
  35. package/examples/screenshot.jpg +0 -0
  36. package/examples/starter/build.js +2 -0
  37. package/examples/starter/game.js +32 -21
  38. package/examples/starter/index.html +13 -13
  39. package/examples/starter/tiles.png +0 -0
  40. package/examples/stress/index.html +2 -2
  41. package/examples/typescript/build.js +2 -0
  42. package/examples/typescript/game.js +4 -3
  43. package/examples/typescript/game.ts +4 -3
  44. package/examples/typescript/index.html +1 -1
  45. package/package.json +1 -1
  46. package/src/engine.js +59 -49
  47. package/src/engineAudio.js +67 -41
  48. package/src/engineBuild.js +23 -7
  49. package/src/engineDraw.js +139 -40
  50. package/src/engineExport.js +4 -191
  51. package/src/engineInput.js +22 -10
  52. package/src/engineMedals.js +13 -45
  53. package/src/engineObject.js +12 -13
  54. package/src/engineParticles.js +17 -17
  55. package/src/engineSettings.js +195 -2
  56. package/src/engineTileLayer.js +23 -22
  57. package/src/engineUtilities.js +6 -6
  58. package/src/engineWebGL.js +73 -74
@@ -1,12 +1,14 @@
1
1
  // LittleJS - MIT License - Copyright 2021 Frank Force
2
2
 
3
+ 'use strict';
4
+
3
5
  /**
4
6
  * LittleJS - Release Mode
5
7
  * - This file is used for release builds in place of engineDebug.js
6
8
  * - Debug functionality is disabled to reduce size and increase performance
7
9
  */
8
10
 
9
- 'use strict';
11
+
10
12
 
11
13
  let showWatermark = 0;
12
14
  let debugKeyCode = 0;
@@ -41,7 +43,7 @@ function debugSaveCanvas (){}
41
43
  * @namespace Utilities
42
44
  */
43
45
 
44
- 'use strict';
46
+
45
47
 
46
48
  /** A shortcut to get Math.PI
47
49
  * @type {Number}
@@ -238,10 +240,10 @@ function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear)
238
240
  * - Can be used to create a deterministic random number sequence
239
241
  * @example
240
242
  * let r = new RandomGenerator(123); // random number generator with seed 123
241
- * let a = r.rand(); // random value between 0 and 1
242
- * 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
243
245
  * r.seed = 123; // reset the seed
244
- * let c = r.rand(); // the same value as a
246
+ * let c = r.float(); // the same value as a
245
247
  */
246
248
  class RandomGenerator
247
249
  {
@@ -270,18 +272,18 @@ class RandomGenerator
270
272
  * @param {Number} valueA
271
273
  * @param {Number} [valueB=0]
272
274
  * @return {Number} */
273
- int(valueA, valueB=0) { return Math.floor(this.rand(valueA, valueB)); }
275
+ int(valueA, valueB=0) { return Math.floor(this.float(valueA, valueB)); }
274
276
 
275
277
  /** Randomly returns either -1 or 1 deterministically
276
278
  * @return {Number} */
277
- sign() { return this.randInt(2) * 2 - 1; }
279
+ sign() { return this.int(2) * 2 - 1; }
278
280
  }
279
281
 
280
282
  ///////////////////////////////////////////////////////////////////////////////
281
283
 
282
284
  /**
283
285
  * Create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
284
- * @param {Number} [x=0]
286
+ * @param {(Number|Vector2)} [x=0]
285
287
  * @param {Number} [y=0]
286
288
  * @return {Vector2}
287
289
  * @example
@@ -696,7 +698,7 @@ class Timer
696
698
  * @namespace Settings
697
699
  */
698
700
 
699
- 'use strict';
701
+
700
702
 
701
703
  ///////////////////////////////////////////////////////////////////////////////
702
704
  // Camera settings
@@ -765,7 +767,7 @@ let glOverlay = 1;
765
767
  * @memberof Settings */
766
768
  let tileSizeDefault = vec2(16);
767
769
 
768
- /** Prevent tile bleeding from neighbors in pixels
770
+ /** How many pixels smaller to draw tiles to prevent bleeding from neighbors
769
771
  * @type {Number}
770
772
  * @default
771
773
  * @memberof Settings */
@@ -939,12 +941,205 @@ let medalDisplayIconSize = 50;
939
941
  * @type {Boolean}
940
942
  * @default 0
941
943
  * @memberof Settings */
942
- 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; }
943
1138
  /**
944
1139
  * LittleJS Object System
945
1140
  */
946
1141
 
947
- 'use strict';
1142
+
948
1143
 
949
1144
  /**
950
1145
  * LittleJS Object Base Object Class
@@ -974,18 +1169,19 @@ let medalsPreventUnlock;
974
1169
  class EngineObject
975
1170
  {
976
1171
  /** Create an engine object and adds it to the list of objects
977
- * @param {Vector2} [position=Vector2()] - World space position of the object
978
- * @param {Vector2} [size=Vector2(1,1)] - World space size of the object
979
- * @param {Number} [tileIndex=-1] - Tile to use to render object (-1 is untextured)
980
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
981
- * @param {Number} [angle=0] - Angle the object is rotated by
982
- * @param {Color} [color=Color()] - Color to apply to tile when rendered
983
- * @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
984
1178
  */
985
- 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)
986
1180
  {
987
1181
  // set passed in params
988
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)
989
1185
 
990
1186
  /** @property {Vector2} - World space position of the object */
991
1187
  this.pos = pos.copy();
@@ -993,10 +1189,8 @@ class EngineObject
993
1189
  this.size = size;
994
1190
  /** @property {Vector2} - Size of object used for drawing, uses size if not set */
995
1191
  this.drawSize;
996
- /** @property {Number} - Tile to use to render object (-1 is untextured) */
997
- this.tileIndex = tileIndex;
998
- /** @property {Vector2} - Size of tile in source pixels */
999
- this.tileSize = tileSize;
1192
+ /** @property {TileInfo} - Tile info to render object (undefined is untextured) */
1193
+ this.tileInfo = tileInfo;
1000
1194
  /** @property {Number} - Angle to rotate the object */
1001
1195
  this.angle = angle;
1002
1196
  /** @property {Color} - Color to apply when rendered */
@@ -1212,7 +1406,7 @@ class EngineObject
1212
1406
  render()
1213
1407
  {
1214
1408
  // default object render
1215
- 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);
1216
1410
  }
1217
1411
 
1218
1412
  /** Destroy this object, destroy it's children, detach it's parent, and mark it for removal */
@@ -1340,7 +1534,7 @@ class EngineObject
1340
1534
  * @namespace Draw
1341
1535
  */
1342
1536
 
1343
- 'use strict';
1537
+
1344
1538
 
1345
1539
  /** The primary 2D canvas visible to the user
1346
1540
  * @type {HTMLCanvasElement}
@@ -1367,13 +1561,109 @@ let overlayContext;
1367
1561
  * @memberof Draw */
1368
1562
  let mainCanvasSize = vec2();
1369
1563
 
1370
- /** Tile sheet for batch rendering system
1371
- * @type {CanvasImageSource}
1564
+ /** Array containing texture info for batch rendering system
1565
+ * @type {Array}
1372
1566
  * @memberof Draw */
1373
- const tileImage = new Image;
1567
+ let textureInfos = [];
1374
1568
 
1375
1569
  // Engine internal variables not exposed to documentation
1376
- 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
+ ///////////////////////////////////////////////////////////////////////////////
1377
1667
 
1378
1668
  /** Convert from screen to world space coordinates
1379
1669
  * @param {Vector2} screenPos
@@ -1404,7 +1694,7 @@ function worldToScreen(worldPos)
1404
1694
  /** Draw textured tile centered in world space, with color applied if using WebGL
1405
1695
  * @param {Vector2} pos - Center of the tile in world space
1406
1696
  * @param {Vector2} [size=Vector2(1,1)] - Size of the tile in world space
1407
- * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
1697
+ * @param {TileInfo}[tileInfo] - Tile info to use, untextured if undefined
1408
1698
  * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
1409
1699
  * @param {Color} [color=Color()] - Color to modulate with
1410
1700
  * @param {Number} [angle=0] - Angle to rotate by
@@ -1413,11 +1703,14 @@ function worldToScreen(worldPos)
1413
1703
  * @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
1414
1704
  * @param {Boolean} [screenSpace=0] - If true the pos and size are in screen space
1415
1705
  * @memberof Draw */
1416
- function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color,
1706
+ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
1417
1707
  angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace)
1418
1708
  {
1709
+ ASSERT(typeof tileInfo !== 'number' || !tileInfo); // prevent old style calls
1710
+ // to fix old calls, replace with tile(tileIndex, tileSize)
1711
+
1419
1712
  showWatermark && ++drawCount;
1420
-
1713
+ const textureInfo = tileInfo && tileInfo.getTextureInfo();
1421
1714
  if (glEnable && useWebGL)
1422
1715
  {
1423
1716
  if (screenSpace)
@@ -1426,46 +1719,48 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
1426
1719
  pos = screenToWorld(pos);
1427
1720
  size = size.scale(1/cameraScale);
1428
1721
  }
1429
- if (tileIndex < 0 || !tileImage.width)
1430
- {
1431
- // if negative tile index or image not found, force untextured
1432
- glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
1433
- }
1434
- else
1722
+
1723
+ if (textureInfo)
1435
1724
  {
1436
1725
  // calculate uvs and render
1437
- const cols = tileImageSize.x / tileSize.x |0;
1438
- const uvSizeX = tileSize.x / tileImageSize.x;
1439
- const uvSizeY = tileSize.y / tileImageSize.y;
1440
- const uvX = (tileIndex%cols)*uvSizeX, uvY = (tileIndex/cols|0)*uvSizeY;
1441
-
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);
1442
1732
  glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
1443
- uvX + tileImageFixBleed.x, uvY + tileImageFixBleed.y,
1444
- uvX - tileImageFixBleed.x + uvSizeX, uvY - tileImageFixBleed.y + uvSizeY,
1733
+ x + tileImageFixBleed.x, y + tileImageFixBleed.y,
1734
+ x - tileImageFixBleed.x + w, y - tileImageFixBleed.y + h,
1445
1735
  color.rgbaInt(), additiveColor.rgbaInt());
1446
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
+ }
1447
1742
  }
1448
1743
  else
1449
1744
  {
1450
1745
  // normal canvas 2D rendering method (slower)
1451
1746
  drawCanvas2D(pos, size, angle, mirror, (context)=>
1452
1747
  {
1453
- if (tileIndex < 0)
1748
+ if (textureInfo)
1454
1749
  {
1455
- // if negative tile index, force untextured
1456
- context.fillStyle = color;
1457
- 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
1458
1758
  }
1459
1759
  else
1460
1760
  {
1461
- // calculate uvs and render
1462
- const cols = tileImageSize.x / tileSize.x |0;
1463
- const sX = (tileIndex%cols)*tileSize.x + tileFixBleedScale;
1464
- const sY = (tileIndex/cols|0)*tileSize.y + tileFixBleedScale;
1465
- const sWidth = tileSize.x - 2*tileFixBleedScale;
1466
- const sHeight = tileSize.y - 2*tileFixBleedScale;
1467
- context.globalAlpha = color.a; // only alpha is supported
1468
- 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);
1469
1764
  }
1470
1765
  }, undefined, screenSpace);
1471
1766
  }
@@ -1480,7 +1775,7 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
1480
1775
  * @param {Boolean} [screenSpace=0]
1481
1776
  * @memberof Draw */
1482
1777
  function drawRect(pos, size, color, angle, useWebGL, screenSpace)
1483
- { drawTile(pos, size, -1, tileSizeDefault, color, angle, 0, undefined, useWebGL, screenSpace); }
1778
+ { drawTile(pos, size, undefined, color, angle, 0, undefined, useWebGL, screenSpace); }
1484
1779
 
1485
1780
  /** Draw colored polygon using passed in points
1486
1781
  * @param {Array} points - Array of Vector2 points
@@ -1531,12 +1826,12 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
1531
1826
  {
1532
1827
  if (!screenSpace)
1533
1828
  {
1534
- // create canvas transform from world space to screen space
1829
+ // transform from world space to screen space
1535
1830
  pos = worldToScreen(pos);
1536
1831
  size = size.scale(cameraScale);
1537
1832
  }
1538
1833
  context.save();
1539
- context.translate(pos.x+.5|0, pos.y+.5|0);
1834
+ context.translate(pos.x+.5, pos.y+.5);
1540
1835
  context.rotate(angle);
1541
1836
  context.scale(mirror ? -size.x : size.x, size.y);
1542
1837
  drawFunction(context);
@@ -1625,10 +1920,9 @@ class FontImage
1625
1920
  * @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
1626
1921
  * @param {Vector2} [tileSize=vec2(8)] - Size of the font source tiles
1627
1922
  * @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
1628
- * @param {Number} [startTileIndex=0] - Tile index in image where font starts
1629
1923
  * @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
1630
1924
  */
1631
- 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)
1632
1926
  {
1633
1927
  // load default font image
1634
1928
  if (!engineFontImage)
@@ -1637,7 +1931,6 @@ class FontImage
1637
1931
  this.image = image || engineFontImage;
1638
1932
  this.tileSize = tileSize;
1639
1933
  this.paddingSize = paddingSize;
1640
- this.startTileIndex = startTileIndex;
1641
1934
  this.context = context;
1642
1935
  }
1643
1936
 
@@ -1678,7 +1971,7 @@ class FontImage
1678
1971
  charCode = 127; // unknown character
1679
1972
 
1680
1973
  // get the character source location and draw it
1681
- const tile = this.startTileIndex + charCode - 32;
1974
+ const tile = charCode - 32;
1682
1975
  const x = tile % cols;
1683
1976
  const y = tile / cols |0;
1684
1977
  const drawPos = pos.add(vec2(j,i).multiply(drawSize));
@@ -1710,8 +2003,7 @@ function toggleFullscreen()
1710
2003
  }
1711
2004
  else if (document.body.requestFullscreen)
1712
2005
  document.body.requestFullscreen();
1713
- }
1714
-
2006
+ }
1715
2007
  /**
1716
2008
  * LittleJS Input System
1717
2009
  * - Tracks keyboard down, pressed, and released
@@ -1721,7 +2013,7 @@ function toggleFullscreen()
1721
2013
  * @namespace Input
1722
2014
  */
1723
2015
 
1724
- 'use strict';
2016
+
1725
2017
 
1726
2018
  /** Returns true if device key is down
1727
2019
  * @param {Number} key
@@ -1905,18 +2197,26 @@ function mouseToScreen(mousePos)
1905
2197
  const stickData = [];
1906
2198
  function gamepadsUpdate()
1907
2199
  {
1908
- if (touchGamepadEnable && touchGamepadTimer.isSet())
2200
+ // update touch gamepad if enabled
2201
+ if (touchGamepadEnable && isTouchDevice)
1909
2202
  {
1910
- // read virtual analog stick
1911
- const sticks = stickData[0] || (stickData[0] = []);
1912
- sticks[0] = vec2(touchGamepadStick.x, -touchGamepadStick.y); // flip vertical
2203
+ // create the touch gamepad if it doesn't exist
2204
+ if (!touchGamepadButtons)
2205
+ createTouchGamepad();
1913
2206
 
1914
- // read virtual gamepad buttons
1915
- const data = inputData[1] || (inputData[1] = []);
1916
- for (let i=10; i--;)
2207
+ if (touchGamepadTimer.isSet())
1917
2208
  {
1918
- const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
1919
- data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
2209
+ // read virtual analog stick
2210
+ const sticks = stickData[0] || (stickData[0] = []);
2211
+ sticks[0] = vec2(touchGamepadStick.x, -touchGamepadStick.y); // flip vertical
2212
+
2213
+ // read virtual gamepad buttons
2214
+ const data = inputData[1] || (inputData[1] = []);
2215
+ for (let i=10; i--;)
2216
+ {
2217
+ const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
2218
+ data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
2219
+ }
1920
2220
  }
1921
2221
  }
1922
2222
 
@@ -2013,6 +2313,10 @@ if (isTouchDevice)
2013
2313
  // set was touching
2014
2314
  wasTouching = touching;
2015
2315
 
2316
+ // prevent default handling like copy and magnifier lens
2317
+ if (document.hasFocus()) // allow document to get focus
2318
+ e.preventDefault();
2319
+
2016
2320
  // must return true so the document will get focus
2017
2321
  return true;
2018
2322
  }
@@ -2025,7 +2329,7 @@ if (isTouchDevice)
2025
2329
  let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
2026
2330
 
2027
2331
  // create the touch gamepad, called automatically by the engine
2028
- if (touchGamepadEnable)
2332
+ function createTouchGamepad()
2029
2333
  {
2030
2334
  // touch input internal variables
2031
2335
  touchGamepadButtons = [];
@@ -2159,7 +2463,7 @@ function touchGamepadRender()
2159
2463
  * @namespace Audio
2160
2464
  */
2161
2465
 
2162
- 'use strict';
2466
+
2163
2467
 
2164
2468
  /**
2165
2469
  * Sound Object - Stores a zzfx sound for later use and can be played positionally
@@ -2195,8 +2499,9 @@ class Sound
2195
2499
  if (zzfxSound)
2196
2500
  {
2197
2501
  // generate zzfx sound now for fast playback
2198
- this.randomness = zzfxSound[1] || (zzfxSound[1] = 0);
2199
- this.cachedSamples = zzfxSound && zzfxG(...zzfxSound);
2502
+ this.randomness = zzfxSound[1] || (zzfxSound[1] = 0);
2503
+ this.sampleChannels = [zzfxG(...zzfxSound)];
2504
+ this.sampleRate = zzfxR;
2200
2505
  }
2201
2506
  }
2202
2507
 
@@ -2205,11 +2510,12 @@ class Sound
2205
2510
  * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
2206
2511
  * @param {Number} [pitch=1] - How much to scale pitch by (also adjusted by this.randomness)
2207
2512
  * @param {Number} [randomnessScale=1] - How much to scale randomness
2208
- * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
2513
+ * @param {Boolean} [loop=0] - Should the sound loop
2514
+ * @return {AudioBufferSourceNode} - The audio source node
2209
2515
  */
2210
- play(pos, volume=1, pitch=1, randomnessScale=1)
2516
+ play(pos, volume=1, pitch=1, randomnessScale=1, loop=0)
2211
2517
  {
2212
- if (!soundEnable || !this.cachedSamples) return;
2518
+ if (!soundEnable || !this.sampleChannels) return;
2213
2519
 
2214
2520
  let pan;
2215
2521
  if (pos)
@@ -2232,43 +2538,80 @@ class Sound
2232
2538
 
2233
2539
  // play the sound
2234
2540
  const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
2235
- return playSamples([this.cachedSamples], volume, playbackRate, pan);
2541
+ return this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate);
2542
+ }
2543
+
2544
+ /** Stop the last instance of this sound that was played */
2545
+ stop()
2546
+ {
2547
+ if (this.source)
2548
+ this.source.stop();
2549
+ this.source = 0;
2236
2550
  }
2237
2551
 
2238
2552
  /** Play the sound as a note with a semitone offset
2239
2553
  * @param {Number} semitoneOffset - How many semitones to offset pitch
2240
2554
  * @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null
2241
2555
  * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
2242
- * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
2556
+ * @return {AudioBufferSourceNode} - The audio source node
2243
2557
  */
2244
2558
  playNote(semitoneOffset, pos, volume)
2245
2559
  { return this.play(pos, volume, 2**(semitoneOffset/12), 0); }
2560
+
2561
+ /** Get how long this sound is in seconds
2562
+ * @return {Number} - How long the sound is in seconds (undefined if loading)
2563
+ */
2564
+ getDuration()
2565
+ { return this.sampleChannels && this.sampleChannels[0].length / this.sampleRate; }
2566
+
2567
+ /** Check if the last instance of this sound is playing
2568
+ * @return {Boolean} - True if the sound is playing
2569
+ */
2570
+ isPlaying() { return this.source && !this.source.ended; }
2571
+
2572
+ /** Check if sound is loading, for sounds fetched from a url
2573
+ * @return {Boolean} - True if sound is loading and not ready to play
2574
+ */
2575
+ isLoading() { return !this.sampleChannels; }
2246
2576
  }
2247
2577
 
2248
2578
  /**
2249
2579
  * Sound Wave Object - Stores a wave sound for later use and can be played positionally
2580
+ * - this can be used to play wave, mp3, and ogg files
2581
+ * @example
2582
+ * // create a sound
2583
+ * const sound_example = new SoundWave('sound.mp3');
2584
+ *
2585
+ * // play the sound
2586
+ * sound_example.play();
2250
2587
  */
2251
2588
  class SoundWave extends Sound
2252
2589
  {
2253
2590
  /** Create a sound object and cache the wave file for later use
2254
- * @param {String} waveFilename - Filename of wave file to load
2255
- * @param {Number} [randomness=.05] - How much to randomize frequency each time sound plays
2591
+ * @param {String} filename - Filename of audio file to load
2592
+ * @param {Number} [randomness=0] - How much to randomize frequency each time sound plays
2256
2593
  * @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
2257
2594
  * @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
2258
2595
  */
2259
- constructor(waveFilename, randomness=.05, range, taper)
2596
+ constructor(filename, randomness=0, range, taper)
2260
2597
  {
2261
2598
  super(0, range, taper);
2262
2599
  this.randomness = randomness;
2263
2600
 
2264
2601
  if (!soundEnable) return;
2265
- if (!soundWaveDecoderContext)
2602
+ if (!soundDecoderContext)
2266
2603
  soundDecoderContext = new AudioContext;
2267
2604
 
2268
- fetch(waveFilename)
2605
+ fetch(filename)
2269
2606
  .then(response => response.arrayBuffer())
2270
- .then(arrayBuffer => soundWaveDecoderContext.decodeAudioData(arrayBuffer))
2271
- .then(audioBuffer => this.cachedSamples = audioBuffer.getChannelData(0));
2607
+ .then(arrayBuffer => soundDecoderContext.decodeAudioData(arrayBuffer))
2608
+ .then(audioBuffer =>
2609
+ {
2610
+ this.sampleChannels = [];
2611
+ for (let i = audioBuffer.numberOfChannels; i--;)
2612
+ this.sampleChannels[i] = audioBuffer.getChannelData(i);
2613
+ this.sampleRate = audioBuffer.sampleRate;
2614
+ });
2272
2615
  }
2273
2616
  }
2274
2617
  let soundDecoderContext; // audio context used only to decode audio files
@@ -2303,48 +2646,34 @@ let soundDecoderContext; // audio context used only to decode audio files
2303
2646
  * // play the music
2304
2647
  * music_example.play();
2305
2648
  */
2306
- class Music
2649
+ class Music extends Sound
2307
2650
  {
2308
2651
  /** Create a music object and cache the zzfx music samples for later use
2309
2652
  * @param {Array} zzfxMusic - Array of zzfx music parameters
2310
2653
  */
2311
2654
  constructor(zzfxMusic)
2312
2655
  {
2313
- if (!soundEnable) return;
2656
+ super();
2314
2657
 
2315
- this.cachedSamples = zzfxM(...zzfxMusic);
2658
+ if (!soundEnable) return;
2659
+ this.randomness = 0;
2660
+ this.sampleChannels = zzfxM(...zzfxMusic);
2661
+ this.sampleRate = zzfxR;
2316
2662
  }
2317
2663
 
2318
2664
  /** Play the music
2319
2665
  * @param {Number} [volume=1] - How much to scale volume by
2320
- * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
2321
- * @return {AudioBufferSourceNode} - The audio node, can be used to stop sound later
2666
+ * @param {Boolean} [loop=1] - True if the music should loop
2667
+ * @return {AudioBufferSourceNode} - The audio source node
2322
2668
  */
2323
2669
  play(volume, loop = 1)
2324
- {
2325
- if (!soundEnable) return;
2326
-
2327
- return this.source = playSamples(this.cachedSamples, volume, 1, 0, loop);
2328
- }
2329
-
2330
- /** Stop the music */
2331
- stop()
2332
- {
2333
- if (this.source)
2334
- this.source.stop();
2335
- this.source = 0;
2336
- }
2337
-
2338
- /** Check if music is playing
2339
- * @return {Boolean}
2340
- */
2341
- isPlaying() { return this.source; }
2670
+ { return super.play(0, volume, 1, 1, loop); }
2342
2671
  }
2343
2672
 
2344
- /** Play an mp3 or wav audio from a local file or url
2673
+ /** Play an mp3, ogg, or wav audio from a local file or url
2345
2674
  * @param {String} url - Location of sound file to play
2346
2675
  * @param {Number} [volume=1] - How much to scale volume by
2347
- * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
2676
+ * @param {Boolean} [loop=1] - True if the music should loop
2348
2677
  * @return {HTMLAudioElement} - The audio element for this sound
2349
2678
  * @memberof Audio */
2350
2679
  function playAudioFile(url, volume=1, loop=1)
@@ -2408,9 +2737,10 @@ let audioContext;
2408
2737
  * @param {Number} [rate=1] - The playback rate to use
2409
2738
  * @param {Number} [pan=0] - How much to apply stereo panning
2410
2739
  * @param {Boolean} [loop=0] - True if the sound should loop when it reaches the end
2740
+ * @param {Number} [sampleRate=44100] - Sample rate for the sound
2411
2741
  * @return {AudioBufferSourceNode} - The audio node of the sound played
2412
2742
  * @memberof Audio */
2413
- function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
2743
+ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0, sampleRate=zzfxR)
2414
2744
  {
2415
2745
  if (!soundEnable) return;
2416
2746
 
@@ -2427,7 +2757,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
2427
2757
  }
2428
2758
 
2429
2759
  // create buffer and source
2430
- const buffer = audioContext.createBuffer(sampleChannels.length, sampleChannels[0].length, zzfxR),
2760
+ const buffer = audioContext.createBuffer(sampleChannels.length, sampleChannels[0].length, sampleRate),
2431
2761
  source = audioContext.createBufferSource();
2432
2762
 
2433
2763
  // copy samples to buffer and setup source
@@ -2686,7 +3016,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
2686
3016
  * @namespace TileCollision
2687
3017
  */
2688
3018
 
2689
- 'use strict';
3019
+
2690
3020
 
2691
3021
  /** The tile collision layer array, use setTileCollisionData and getTileCollisionData to access
2692
3022
  * @type {Array}
@@ -2831,7 +3161,7 @@ class TileLayerData
2831
3161
  }
2832
3162
 
2833
3163
  /**
2834
- * Tile layer object - cached rendering system for tile layers
3164
+ * Tile Layer - cached rendering system for tile layers
2835
3165
  * - Each Tile layer is rendered to an off screen canvas
2836
3166
  * - To allow dynamic modifications, layers are rendered using canvas 2d
2837
3167
  * - Some devices like mobile phones are limited to 4k texture resolution
@@ -2847,13 +3177,13 @@ class TileLayer extends EngineObject
2847
3177
  /** Create a tile layer object
2848
3178
  * @param {Vector2} [position=Vector2()] - World space position
2849
3179
  * @param {Vector2} [size=tileCollisionSize] - World space size
2850
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
3180
+ * @param {TileInfo} [tileInfo] - Tile info for layer
2851
3181
  * @param {Vector2} [scale=Vector2(1,1)] - How much to scale this layer when rendered
2852
3182
  * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
2853
3183
  */
2854
- constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
3184
+ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderOrder=0)
2855
3185
  {
2856
- super(pos, size, -1, tileSize, 0, undefined, renderOrder);
3186
+ super(pos, size, tileInfo, 0, undefined, renderOrder);
2857
3187
 
2858
3188
  /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
2859
3189
  this.canvas = document.createElement('canvas');
@@ -2930,13 +3260,13 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
2930
3260
  mainCanvas = this.canvas;
2931
3261
  mainContext = this.context;
2932
3262
  cameraPos = this.size.scale(.5);
2933
- cameraScale = this.tileSize.x;
3263
+ cameraScale = this.tileInfo.size.x;
2934
3264
 
2935
3265
  if (clear)
2936
3266
  {
2937
3267
  // clear and set size
2938
- mainCanvas.width = this.size.x * this.tileSize.x;
2939
- 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;
2940
3270
  }
2941
3271
 
2942
3272
  // begin a new render for the tile canvas
@@ -2967,7 +3297,8 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
2967
3297
  if (d.tile != undefined)
2968
3298
  {
2969
3299
  ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
2970
- 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);
2971
3302
  }
2972
3303
  }
2973
3304
 
@@ -2989,8 +3320,8 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
2989
3320
  {
2990
3321
  const context = this.context;
2991
3322
  context.save();
2992
- pos = pos.subtract(this.pos).multiply(this.tileSize);
2993
- size = size.multiply(this.tileSize);
3323
+ pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
3324
+ size = size.multiply(this.tileInfo.size);
2994
3325
  context.translate(pos.x, this.canvas.height - pos.y);
2995
3326
  context.rotate(angle);
2996
3327
  context.scale(mirror ? -size.x : size.x, size.y);
@@ -3001,28 +3332,28 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3001
3332
  /** Draw a tile directly onto the layer canvas
3002
3333
  * @param {Vector2} pos
3003
3334
  * @param {Vector2} [size=Vector2(1,1)]
3004
- * @param {Number} [tileIndex=-1]
3005
- * @param {Vector2} [tileSize=tileSizeDefault]
3335
+ * @param {TileInfo} [tileInfo]
3006
3336
  * @param {Color} [color=Color()]
3007
3337
  * @param {Number} [angle=0]
3008
3338
  * @param {Boolean} [mirror=0] */
3009
- 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)
3010
3340
  {
3011
3341
  this.drawCanvas2D(pos, size, angle, mirror, (context)=>
3012
3342
  {
3013
- if (tileIndex < 0)
3343
+ const textureInfo = tileInfo && tileInfo.getTextureInfo();
3344
+ if (textureInfo)
3014
3345
  {
3015
- // untextured
3016
- context.fillStyle = color;
3017
- 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;
3018
3351
  }
3019
3352
  else
3020
3353
  {
3021
- const cols = tileImage.width/tileSize.x;
3022
- context.globalAlpha = color.a; // only alpha, no color, is supported in this mode
3023
- context.drawImage(tileImage,
3024
- (tileIndex%cols)*tileSize.x, (tileIndex/cols|0)*tileSize.y,
3025
- tileSize.x, tileSize.y, -.5, -.5, 1, 1);
3354
+ // untextured
3355
+ context.fillStyle = color;
3356
+ context.fillRect(-.5, -.5, 1, 1);
3026
3357
  }
3027
3358
  });
3028
3359
  }
@@ -3039,7 +3370,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3039
3370
  * LittleJS Particle System
3040
3371
  */
3041
3372
 
3042
- 'use strict';
3373
+
3043
3374
 
3044
3375
  /**
3045
3376
  * Particle Emitter - Spawns particles with the given settings
@@ -3050,7 +3381,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3050
3381
  * let particleEmiter = new ParticleEmitter
3051
3382
  * (
3052
3383
  * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
3053
- * 0, vec2(16), // tileIndex, tileSize
3384
+ * tile(0, 16), // tileInfo
3054
3385
  * new Color(1,1,1), new Color(0,0,0), // colorStartA, colorStartB
3055
3386
  * new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
3056
3387
  * 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
@@ -3067,8 +3398,7 @@ class ParticleEmitter extends EngineObject
3067
3398
  * @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
3068
3399
  * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
3069
3400
  * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
3070
- * @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied
3071
- * @param {Vector2} [tileSize=tileSizeDefault] - Tile size for particles
3401
+ * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
3072
3402
  * @param {Color} [colorStartA=Color()] - Color at start of life 1, randomized between start colors
3073
3403
  * @param {Color} [colorStartB=Color()] - Color at start of life 2, randomized between start colors
3074
3404
  * @param {Color} [colorEndA=Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
@@ -3098,8 +3428,7 @@ class ParticleEmitter extends EngineObject
3098
3428
  emitTime = 0,
3099
3429
  emitRate = 100,
3100
3430
  emitConeAngle = PI,
3101
- tileIndex = -1,
3102
- tileSize = tileSizeDefault,
3431
+ tileInfo,
3103
3432
  colorStartA = new Color,
3104
3433
  colorStartB = new Color,
3105
3434
  colorEndA = new Color(1,1,1,0),
@@ -3122,7 +3451,7 @@ class ParticleEmitter extends EngineObject
3122
3451
  localSpace
3123
3452
  )
3124
3453
  {
3125
- super(pos, vec2(), tileIndex, tileSize, angle, undefined, renderOrder);
3454
+ super(pos, vec2(), tileInfo, angle, undefined, renderOrder);
3126
3455
 
3127
3456
  // emitter settings
3128
3457
  /** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
@@ -3221,7 +3550,7 @@ class ParticleEmitter extends EngineObject
3221
3550
  angle += this.angle;
3222
3551
  }
3223
3552
 
3224
- const particle = new Particle(pos, this.tileIndex, this.tileSize, angle);
3553
+ const particle = new Particle(pos, this.tileInfo, this.tileSize, angle);
3225
3554
 
3226
3555
  // randomness scales each paremeter by a percentage
3227
3556
  const randomness = this.randomness;
@@ -3281,12 +3610,11 @@ class Particle extends EngineObject
3281
3610
  /**
3282
3611
  * Create a particle with the given settings
3283
3612
  * @param {Vector2} position - World space position of the particle
3284
- * @param {Number} [tileIndex=-1] - Tile to use to render, untextured if -1
3285
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
3613
+ * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
3286
3614
  * @param {Number} [angle=0] - Angle to rotate the particle
3287
3615
  */
3288
- constructor(pos, tileIndex, tileSize, angle)
3289
- { super(pos, vec2(), tileIndex, tileSize, angle); }
3616
+ constructor(pos, tileInfo, angle)
3617
+ { super(pos, vec2(), tileInfo, angle); }
3290
3618
 
3291
3619
  /** Render the particle, automatically called each frame, sorted by renderOrder */
3292
3620
  render()
@@ -3320,14 +3648,17 @@ class Particle extends EngineObject
3320
3648
  if (this.localSpaceEmitter)
3321
3649
  velocity = velocity.rotate(-this.localSpaceEmitter.angle);
3322
3650
  const speed = velocity.length();
3323
- const direction = velocity.scale(1/speed);
3324
- const trailLength = speed * this.trailScale;
3325
- size.y = max(size.x, trailLength);
3326
- angle = direction.angle();
3327
- 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
+ }
3328
3659
  }
3329
3660
  else
3330
- drawTile(pos, size, this.tileIndex, this.tileSize, color, angle, this.mirror);
3661
+ drawTile(pos, size, this.tileInfo, color, angle, this.mirror);
3331
3662
  this.additive && setBlendMode();
3332
3663
  debugParticles && debugRect(pos, size, '#f005', 0, angle);
3333
3664
 
@@ -3349,7 +3680,7 @@ class Particle extends EngineObject
3349
3680
  * @namespace Medals
3350
3681
  */
3351
3682
 
3352
- 'use strict';
3683
+
3353
3684
 
3354
3685
  /** List of all medals
3355
3686
  * @type {Array}
@@ -3374,7 +3705,7 @@ function medalsInit(saveName)
3374
3705
  }
3375
3706
 
3376
3707
  /**
3377
- * Medal Object - Tracks an unlockable medal
3708
+ * Medal - Tracks an unlockable medal
3378
3709
  * @example
3379
3710
  * // create a medal
3380
3711
  * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
@@ -3387,7 +3718,7 @@ function medalsInit(saveName)
3387
3718
  */
3388
3719
  class Medal
3389
3720
  {
3390
- /** 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
3391
3722
  * @param {Number} id - The unique identifier of the medal
3392
3723
  * @param {String} name - Name of the medal
3393
3724
  * @param {String} [description] - Description of the medal
@@ -3499,33 +3830,33 @@ let newgrounds;
3499
3830
  /** This can used to enable Newgrounds functionality
3500
3831
  * @param {Number} app_id - The newgrounds App ID
3501
3832
  * @param {String} [cipher] - The encryption Key (AES-128/Base64)
3833
+ * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher
3502
3834
  * @memberof Medals */
3503
- 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); }
3504
3837
 
3505
3838
  /**
3506
3839
  * Newgrounds API wrapper object
3507
3840
  * @example
3508
- * // 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
3509
3842
  * const app_id = '53123:1ZuSTQ9l';
3510
- * const cipher = 'enF0vGH@Mj/FRASKL23Q==';
3511
- * newgrounds = new Newgrounds(app_id, cipher);
3843
+ * newgrounds = new Newgrounds(app_id);
3512
3844
  */
3513
3845
  class Newgrounds
3514
3846
  {
3515
3847
  /** Create a newgrounds object
3516
3848
  * @param {Number} app_id - The newgrounds App ID
3517
- * @param {String} [cipher] - The encryption Key (AES-128/Base64) */
3518
- 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)
3519
3852
  {
3520
- 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
3521
3855
 
3522
3856
  this.app_id = app_id;
3523
3857
  this.cipher = cipher;
3858
+ this.cryptoJS = cryptoJS;
3524
3859
  this.host = location ? location.hostname : '';
3525
-
3526
- // create an instance of CryptoJS for encrypted calls
3527
- if (cipher)
3528
- this.cryptoJS = this.CryptoJS();
3529
3860
 
3530
3861
  // get session id from url search params
3531
3862
  const url = new URL(location.href);
@@ -3629,38 +3960,6 @@ class Newgrounds
3629
3960
  debugMedals && console.log(xmlHttp.responseText);
3630
3961
  return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
3631
3962
  }
3632
-
3633
- CryptoJS()
3634
- {
3635
- ///////////////////////////////////////////////////////////////////////////////
3636
- // Crypto-JS - https://github.com/brix/crypto-js - MIT License
3637
- //
3638
- // [The MIT License (MIT)](http://opensource.org/licenses/MIT)
3639
- //
3640
- // Copyright (c) 2009-2013 Jeff Mott
3641
- // Copyright (c) 2013-2016 Evan Vosberg
3642
- //
3643
- // Permission is hereby granted, free of charge, to any person obtaining a copy
3644
- // of this software and associated documentation files (the "Software"), to deal
3645
- // in the Software without restriction, including without limitation the rights
3646
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
3647
- // copies of the Software, and to permit persons to whom the Software is
3648
- // furnished to do so, subject to the following conditions:
3649
- //
3650
- // The above copyright notice and this permission notice shall be included in
3651
- // all copies or substantial portions of the Software.
3652
- //
3653
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
3654
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
3655
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
3656
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
3657
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3658
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
3659
- // THE SOFTWARE.
3660
- 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));
3661
- // end of Crypto-JS
3662
- ///////////////////////////////////////////////////////////////////////////////
3663
- }
3664
3963
  }
3665
3964
  /**
3666
3965
  * LittleJS WebGL Interface
@@ -3670,10 +3969,11 @@ class Newgrounds
3670
3969
  * - Can be disabled with glEnable to revert to 2D canvas rendering
3671
3970
  * - Batches sprite rendering on GPU for incredibly fast performance
3672
3971
  * - Sprite transform math is done in the shader where possible
3972
+ * - Supports shadertoy style post processing shaders
3673
3973
  * @namespace WebGL
3674
3974
  */
3675
3975
 
3676
- 'use strict';
3976
+
3677
3977
 
3678
3978
  /** The WebGL canvas which appears above the main canvas and below the overlay canvas
3679
3979
  * @type {HTMLCanvasElement}
@@ -3685,45 +3985,42 @@ let glCanvas;
3685
3985
  * @memberof WebGL */
3686
3986
  let glContext;
3687
3987
 
3688
- /** Main tile sheet texture automatically loaded by engine
3689
- * @type {WebGLTexture}
3690
- * @memberof WebGL */
3691
- let glTileTexture;
3692
-
3693
3988
  // WebGL internal variables not exposed to documentation
3694
3989
  let glActiveTexture, glShader, glArrayBuffer, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
3695
3990
 
3696
3991
  ///////////////////////////////////////////////////////////////////////////////
3697
3992
 
3698
- // Init WebGL, called automatically by the engine
3993
+ // Initalize WebGL, called automatically by the engine
3699
3994
  function glInit()
3700
3995
  {
3701
- // create the canvas and tile texture
3996
+ // create the canvas and textures
3702
3997
  glCanvas = document.createElement('canvas');
3703
- glContext = glCanvas.getContext('webgl', {antialias: false});
3704
- glTileTexture = glCreateTexture(tileImage);
3998
+ glContext = glCanvas.getContext('webgl2');
3705
3999
 
3706
4000
  // some browsers are much faster without copying the gl buffer so we just overlay it instead
3707
4001
  glOverlay && document.body.appendChild(glCanvas);
3708
4002
 
3709
4003
  // setup vertex and fragment shaders
3710
4004
  glShader = glCreateProgram(
4005
+ '#version 300 es\n' + // specify GLSL ES version
3711
4006
  'precision highp float;'+ // use highp for better accuracy
3712
4007
  'uniform mat4 m;'+ // transform matrix
3713
- 'attribute vec2 p,t;'+ // position, uv
3714
- 'attribute vec4 c,a;'+ // color, additiveColor
3715
- '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
3716
4011
  'void main(){'+ // shader entry point
3717
4012
  'gl_Position=m*vec4(p,1,1);'+ // transform position
3718
4013
  'v=vec4(t,p);d=c;e=a;'+ // pass stuff to fragment shader
3719
4014
  '}' // end of shader
3720
4015
  ,
3721
- 'precision highp float;'+ // use highp for better accuracy
3722
- 'varying vec4 v,d,e;'+ // uv, color, additiveColor
3723
- 'uniform sampler2D s;'+ // texture
3724
- 'void main(){'+ // shader entry point
3725
- 'gl_FragColor=texture2D(s,v.xy)*d+e;'+ // modulate texture by color plus additive
3726
- '}' // 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
3727
4024
  );
3728
4025
 
3729
4026
  // init buffers
@@ -3734,25 +4031,68 @@ function glInit()
3734
4031
  glBatchCount = 0;
3735
4032
  }
3736
4033
 
4034
+ // Setup render each frame, called automatically by engine
4035
+ function glPreRender()
4036
+ {
4037
+ // clear and set to same size as main canvas
4038
+ glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
4039
+ glContext.clear(gl_COLOR_BUFFER_BIT);
4040
+
4041
+ // set up the shader
4042
+ glContext.useProgram(glShader);
4043
+ glContext.activeTexture(gl_TEXTURE0);
4044
+ glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
4045
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
4046
+ glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
4047
+ glSetBlendMode();
4048
+
4049
+ // set vertex attributes
4050
+ let offset = 0;
4051
+ const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
4052
+ {
4053
+ const location = glContext.getAttribLocation(glShader, name);
4054
+ glContext.enableVertexAttribArray(location);
4055
+ glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
4056
+ offset += size*typeSize;
4057
+ }
4058
+ initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
4059
+ initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
4060
+ initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
4061
+ initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
4062
+
4063
+ // build the transform matrix
4064
+ const sx = 2 * cameraScale / mainCanvas.width;
4065
+ const sy = 2 * cameraScale / mainCanvas.height;
4066
+ glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
4067
+ new Float32Array([
4068
+ sx, 0, 0, 0,
4069
+ 0, sy, 0, 0,
4070
+ 1, 1, -1, 1,
4071
+ -1-sx*cameraPos.x, -1-sy*cameraPos.y, 0, 0
4072
+ ])
4073
+ );
4074
+ }
4075
+
3737
4076
  /** Set the WebGl blend mode, normally you should call setBlendMode instead
3738
4077
  * @param {Boolean} [additive=0]
3739
4078
  * @memberof WebGL */
3740
- function glSetBlendMode(additive)
4079
+ function glSetBlendMode(additive=0)
3741
4080
  {
3742
4081
  // setup blending
3743
4082
  glAdditive = additive;
3744
4083
  }
3745
4084
 
3746
- /** Set the WebGl texture, not normally necessary unless multiple tile sheets are used
4085
+ /** Set the WebGl texture, called automatically if using multiple textures
3747
4086
  * - This may also flush the gl buffer resulting in more draw calls and worse performance
3748
- * @param {WebGLTexture} [texture=glTileTexture]
4087
+ * @param {WebGLTexture} texture
3749
4088
  * @memberof WebGL */
3750
- function glSetTexture(texture=glTileTexture)
4089
+ function glSetTexture(texture)
3751
4090
  {
3752
4091
  // must flush cache with the old texture to set a new one
3753
- if (texture != glActiveTexture)
3754
- glFlush();
4092
+ if (texture == glActiveTexture)
4093
+ return;
3755
4094
 
4095
+ glFlush();
3756
4096
  glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = texture);
3757
4097
  }
3758
4098
 
@@ -3813,48 +4153,6 @@ function glCreateTexture(image)
3813
4153
  return texture;
3814
4154
  }
3815
4155
 
3816
- // called automatically by engine before render
3817
- function glPreRender()
3818
- {
3819
- // clear and set to same size as main canvas
3820
- glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
3821
- glContext.clear(gl_COLOR_BUFFER_BIT);
3822
-
3823
- // set up the shader
3824
- glContext.useProgram(glShader);
3825
- glContext.activeTexture(gl_TEXTURE0);
3826
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
3827
- glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
3828
- glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
3829
- glSetBlendMode();
3830
-
3831
- // set vertex attributes
3832
- let offset = 0;
3833
- const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
3834
- {
3835
- const location = glContext.getAttribLocation(glShader, name);
3836
- glContext.enableVertexAttribArray(location);
3837
- glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
3838
- offset += size*typeSize;
3839
- }
3840
- initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
3841
- initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
3842
- initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
3843
- initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
3844
-
3845
- // build the transform matrix
3846
- const sx = 2 * cameraScale / mainCanvas.width;
3847
- const sy = 2 * cameraScale / mainCanvas.height;
3848
- glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
3849
- new Float32Array([
3850
- sx, 0, 0, 0,
3851
- 0, sy, 0, 0,
3852
- 1, 1, -1, 1,
3853
- -1-sx*cameraPos.x, -1-sy*cameraPos.y, 0, 0
3854
- ])
3855
- );
3856
- }
3857
-
3858
4156
  /** Draw all sprites and clear out the buffer, called automatically by the system whenever necessary
3859
4157
  * @memberof WebGL */
3860
4158
  function glFlush()
@@ -3973,25 +4271,28 @@ function glInitPostProcess(shaderCode, includeOverlay)
3973
4271
  {
3974
4272
  ASSERT(!glPostShader); // can only have 1 post effects shader
3975
4273
 
3976
- if (!shaderCode) // default shader
3977
- 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);}';
3978
4276
 
3979
4277
  // create the shader
3980
4278
  glPostShader = glCreateProgram(
4279
+ '#version 300 es\n' + // specify GLSL ES version
3981
4280
  'precision highp float;'+ // use highp for better accuracy
3982
- 'attribute vec2 p;'+ // position
4281
+ 'in vec2 p;'+ // position
3983
4282
  'void main(){'+ // shader entry point
3984
4283
  'gl_Position=vec4(p,1,1);'+ // set position
3985
4284
  '}' // end of shader
3986
4285
  ,
4286
+ '#version 300 es\n' + // specify GLSL ES version
3987
4287
  'precision highp float;'+ // use highp for better accuracy
3988
4288
  'uniform sampler2D iChannel0;'+ // input texture
3989
4289
  'uniform vec3 iResolution;'+ // size of output texture
3990
4290
  'uniform float iTime;'+ // time passed
4291
+ 'out vec4 c;'+ // out color
3991
4292
  '\n' + shaderCode + '\n'+ // insert custom shader code
3992
4293
  'void main(){'+ // shader entry point
3993
- 'mainImage(gl_FragColor,gl_FragCoord.xy);'+ // call post process function
3994
- '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
3995
4296
  '}' // end of shader
3996
4297
  );
3997
4298
 
@@ -4064,7 +4365,6 @@ gl_ONE_MINUS_SRC_ALPHA = 771,
4064
4365
  gl_BLEND = 3042,
4065
4366
  gl_TEXTURE_2D = 3553,
4066
4367
  gl_UNSIGNED_BYTE = 5121,
4067
- gl_BYTE = 5120,
4068
4368
  gl_FLOAT = 5126,
4069
4369
  gl_RGBA = 6408,
4070
4370
  gl_NEAREST = 9728,
@@ -4076,7 +4376,6 @@ gl_TEXTURE_WRAP_T = 10243,
4076
4376
  gl_COLOR_BUFFER_BIT = 16384,
4077
4377
  gl_CLAMP_TO_EDGE = 33071,
4078
4378
  gl_TEXTURE0 = 33984,
4079
- gl_TEXTURE1 = 33985,
4080
4379
  gl_ARRAY_BUFFER = 34962,
4081
4380
  gl_STATIC_DRAW = 35044,
4082
4381
  gl_DYNAMIC_DRAW = 35048,
@@ -4087,7 +4386,6 @@ gl_LINK_STATUS = 35714,
4087
4386
  gl_UNPACK_FLIP_Y_WEBGL = 37440,
4088
4387
 
4089
4388
  // constants for batch rendering
4090
- gl_VERTICES_PER_QUAD = 6,
4091
4389
  gl_INDICIES_PER_VERT = 6,
4092
4390
  gl_MAX_BATCH = 1e5,
4093
4391
  gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
@@ -4112,7 +4410,7 @@ gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTEX_BYTE_STRIDE;
4112
4410
  * @namespace Engine
4113
4411
  */
4114
4412
 
4115
- 'use strict';
4413
+
4116
4414
 
4117
4415
  /** Name of engine
4118
4416
  * @type {String}
@@ -4124,7 +4422,7 @@ const engineName = 'LittleJS';
4124
4422
  * @type {String}
4125
4423
  * @default
4126
4424
  * @memberof Engine */
4127
- const engineVersion = '1.7.13';
4425
+ const engineVersion = '1.8.1';
4128
4426
 
4129
4427
  /** Frames per second to update objects
4130
4428
  * @type {Number}
@@ -4174,6 +4472,9 @@ let paused = 0;
4174
4472
  * @memberof Engine */
4175
4473
  function setPaused(_paused) { paused = _paused; }
4176
4474
 
4475
+ // Frame time tracking
4476
+ let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
4477
+
4177
4478
  ///////////////////////////////////////////////////////////////////////////////
4178
4479
 
4179
4480
  /** Start up LittleJS engine with your callback functions
@@ -4182,49 +4483,13 @@ function setPaused(_paused) { paused = _paused; }
4182
4483
  * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
4183
4484
  * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
4184
4485
  * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
4185
- * @param {String} [tileImageSource] - Tile image to use, everything starts when the image is finished loading
4486
+ * @param {String} [imageSources='tiles.png'] - Image to load
4186
4487
  * @memberof Engine */
4187
- function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, tileImageSource)
4488
+ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=['tiles.png'])
4188
4489
  {
4189
- // init engine when tiles load or fail to load
4190
- tileImage.onerror = tileImage.onload = ()=>
4191
- {
4192
- // save tile image info
4193
- tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
4194
- debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
4195
-
4196
- // setup html
4197
- const styleBody = 'margin:0;overflow:hidden;' + // fill the window
4198
- 'background:#000;' + // set background color
4199
- 'touch-action:none;' + // prevent mobile pinch to resize
4200
- 'user-select:none;' + // prevent mobile hold to select
4201
- '-webkit-user-select:none'; // compatibility for ios
4202
- document.body.style = styleBody;
4203
- document.body.appendChild(mainCanvas = document.createElement('canvas'));
4204
- mainContext = mainCanvas.getContext('2d');
4205
-
4206
- // init stuff and start engine
4207
- debugInit();
4208
- glEnable && glInit();
4209
-
4210
- // create overlay canvas for hud to appear above gl canvas
4211
- document.body.appendChild(overlayCanvas = document.createElement('canvas'));
4212
- overlayContext = overlayCanvas.getContext('2d');
4213
-
4214
- // set canvas style
4215
- const styleCanvas = 'position:absolute;' +
4216
- 'top:50%;left:50%;transform:translate(-50%,-50%);' + // center the canvas
4217
- (canvasPixelated?'image-rendering:pixelated':''); // set pixelated rendering
4218
- (glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
4219
-
4220
- gameInit();
4221
- engineUpdate();
4222
- };
4490
+ ASSERT(Array.isArray(imageSources)); // pass in images as array
4223
4491
 
4224
- // frame time tracking
4225
- let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
4226
-
4227
- // main update loop
4492
+ // internal update loop for engine
4228
4493
  function engineUpdate(frameTimeMS=0)
4229
4494
  {
4230
4495
  // update time keeping
@@ -4255,9 +4520,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4255
4520
  }
4256
4521
  else
4257
4522
  {
4258
- // clear canvas and set size to same as window
4259
- mainCanvas.width = min(innerWidth, canvasMaxSize.x);
4260
- 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);
4261
4526
  }
4262
4527
 
4263
4528
  // clear overlay canvas and set size
@@ -4289,6 +4554,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4289
4554
  // update multiple frames if necessary in case of slow framerate
4290
4555
  for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / frameRate)
4291
4556
  {
4557
+ // increment frame and update time
4558
+ time = frame++ / frameRate;
4559
+
4292
4560
  // update game and objects
4293
4561
  inputUpdate();
4294
4562
  gameUpdate();
@@ -4336,8 +4604,51 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4336
4604
  requestAnimationFrame(engineUpdate);
4337
4605
  }
4338
4606
 
4339
- // set tile image source to load the image and start the engine
4340
- 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
+ });
4341
4652
  }
4342
4653
 
4343
4654
  // Called automatically by engine to setup render system
@@ -4375,9 +4686,6 @@ function engineObjectsUpdate()
4375
4686
 
4376
4687
  // remove destroyed objects
4377
4688
  engineObjects = engineObjects.filter(o=>!o.destroyed);
4378
-
4379
- // increment frame and update time
4380
- time = ++frame / frameRate;
4381
4689
  }
4382
4690
 
4383
4691
  /** Destroy and remove all objects