littlejsengine 1.14.11 → 1.14.16

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 (74) hide show
  1. package/dist/littlejs.d.ts +252 -109
  2. package/dist/littlejs.esm.js +559 -252
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +557 -251
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +482 -206
  7. package/examples/box2d/gameObjects.js +2 -2
  8. package/examples/breakout/gameObjects.js +2 -2
  9. package/examples/breakoutTutorial/README.md +32 -32
  10. package/examples/breakoutTutorial/game.js +1 -1
  11. package/examples/electron/game.js +3 -3
  12. package/examples/electron/index.html +2 -2
  13. package/examples/electron/package.json +1 -8
  14. package/examples/index.html +10 -7
  15. package/examples/module/game.js +3 -3
  16. package/examples/platformer/gameEffects.js +4 -4
  17. package/examples/platformer/gameLevel.js +1 -1
  18. package/examples/platformer/gameObjects.js +4 -4
  19. package/examples/puzzle/game.js +1 -1
  20. package/examples/shorts/animation.js +1 -1
  21. package/examples/shorts/base.html +1 -1
  22. package/examples/shorts/blending.js +8 -14
  23. package/examples/shorts/box2d.js +7 -3
  24. package/examples/shorts/box2dCar.js +2 -1
  25. package/examples/shorts/empty.js +30 -0
  26. package/examples/shorts/helloWorld.js +1 -1
  27. package/examples/shorts/hillGlideGame.js +10 -4
  28. package/examples/shorts/landerGame.js +11 -8
  29. package/examples/shorts/medals.js +4 -4
  30. package/examples/shorts/music.js +29 -56
  31. package/examples/shorts/musicPlayer.js +134 -0
  32. package/examples/shorts/nineSlice.js +34 -15
  33. package/examples/shorts/parallax.js +4 -3
  34. package/examples/shorts/particles.js +15 -15
  35. package/examples/shorts/piano.js +15 -21
  36. package/examples/shorts/pongGame.js +6 -4
  37. package/examples/shorts/raycasting.js +9 -4
  38. package/examples/shorts/sequencer.js +122 -0
  39. package/examples/shorts/shapes.js +7 -4
  40. package/examples/shorts/slidingPuzzle.js +4 -2
  41. package/examples/shorts/sound.js +18 -9
  42. package/examples/shorts/spaceGame.js +12 -8
  43. package/examples/shorts/spriteAtlas.js +7 -7
  44. package/examples/shorts/starfield.js +1 -1
  45. package/examples/shorts/systemFont.js +2 -2
  46. package/examples/shorts/texture.js +2 -2
  47. package/examples/shorts/tileLayer.js +10 -10
  48. package/examples/shorts/tiltedView.js +14 -3
  49. package/examples/shorts/timers.js +10 -0
  50. package/examples/shorts/topDown.js +4 -1
  51. package/examples/shorts/uiSystem.js +8 -5
  52. package/examples/starter/game.js +3 -3
  53. package/examples/starter/index.html +2 -2
  54. package/examples/typescript/game.js +3 -3
  55. package/examples/typescript/game.ts +3 -3
  56. package/examples/uiSystem/game.js +3 -3
  57. package/package.json +4 -2
  58. package/plugins/box2d.js +17 -2
  59. package/plugins/newgrounds.js +7 -5
  60. package/plugins/postProcess.js +5 -2
  61. package/plugins/uiSystem.js +120 -34
  62. package/plugins/zzfxm.js +5 -1
  63. package/reference.md +1 -1
  64. package/src/engine.js +35 -12
  65. package/src/engineAudio.js +31 -14
  66. package/src/engineDebug.js +75 -45
  67. package/src/engineDraw.js +60 -31
  68. package/src/engineExport.js +2 -1
  69. package/src/engineMedals.js +10 -2
  70. package/src/engineObject.js +14 -10
  71. package/src/engineParticles.js +59 -46
  72. package/src/engineSettings.js +7 -7
  73. package/src/engineTileLayer.js +45 -20
  74. package/src/engineUtilities.js +67 -20
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
33
33
  * @type {string}
34
34
  * @default
35
35
  * @memberof Engine */
36
- const engineVersion = '1.14.11';
36
+ const engineVersion = '1.14.16';
37
37
 
38
38
  /** Frames per second to update
39
39
  * @type {number}
@@ -96,9 +96,14 @@ let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
96
96
 
97
97
  const pluginUpdateList = [], pluginRenderList = [];
98
98
 
99
+ /**
100
+ * @callback PluginCallback - Update or render function for a plugin
101
+ * @memberof Engine
102
+ */
103
+
99
104
  /** Add a new update function for a plugin
100
- * @param {Function} [updateFunction]
101
- * @param {Function} [renderFunction]
105
+ * @param {PluginCallback} [updateFunction]
106
+ * @param {PluginCallback} [renderFunction]
102
107
  * @memberof Engine */
103
108
  function engineAddPlugin(updateFunction, renderFunction)
104
109
  {
@@ -111,12 +116,22 @@ function engineAddPlugin(updateFunction, renderFunction)
111
116
  ///////////////////////////////////////////////////////////////////////////////
112
117
  // Main Engine Functions
113
118
 
119
+ /**
120
+ * @callback GameInitCallback - Called after the engine starts, can be async
121
+ * @returns {void|Promise<void>}
122
+ * @memberof Engine
123
+ */
124
+ /**
125
+ * @callback GameCallback - Update or render function for the game
126
+ * @memberof Engine
127
+ */
128
+
114
129
  /** Startup LittleJS engine with your callback functions
115
- * @param {Function|function():Promise} gameInit - Called once after the engine starts up, can be async for loading
116
- * @param {Function} gameUpdate - Called every frame before objects are updated (60fps), use for game logic
117
- * @param {Function} gameUpdatePost - Called after physics and objects are updated, even when paused, use for UI updates
118
- * @param {Function} gameRender - Called before objects are rendered, use for drawing backgrounds/world elements
119
- * @param {Function} gameRenderPost - Called after objects are rendered, use for drawing UI/overlays
130
+ * @param {GameInitCallback} gameInit - Called once after the engine starts up, can be async for loading
131
+ * @param {GameCallback} gameUpdate - Called every frame before objects are updated (60fps), use for game logic
132
+ * @param {GameCallback} gameUpdatePost - Called after physics and objects are updated, even when paused, use for UI updates
133
+ * @param {GameCallback} gameRender - Called before objects are rendered, use for drawing backgrounds/world elements
134
+ * @param {GameCallback} gameRenderPost - Called after objects are rendered, use for drawing UI/overlays
120
135
  * @param {Array<string>} [imageSources=[]] - List of image file paths to preload (e.g., ['player.png', 'tiles.png'])
121
136
  * @param {HTMLElement} [rootElement] - Root DOM element to attach canvas to, defaults to document.body
122
137
  * @example
@@ -368,6 +383,8 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
368
383
  const promises = imageSources.map((src, textureIndex)=>
369
384
  new Promise(resolve =>
370
385
  {
386
+ ASSERT(isString(src), 'imageSources must be an array of strings');
387
+
371
388
  const image = new Image;
372
389
  image.onerror = image.onload = ()=>
373
390
  {
@@ -399,7 +416,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
399
416
  promises.push(new Promise(resolve =>
400
417
  {
401
418
  let t = 0;
402
- LOG(`${engineName} Engine v${engineVersion}`);
419
+ console.log(`${engineName} Engine v${engineVersion}`);
403
420
  updateSplash();
404
421
  function updateSplash()
405
422
  {
@@ -483,10 +500,16 @@ function engineObjectsCollect(pos, size, objects=engineObjects)
483
500
  return collectedObjects;
484
501
  }
485
502
 
503
+ /**
504
+ * @callback ObjectCallbackFunction - Function that processes an object
505
+ * @param {EngineObject} uiObjects
506
+ * @memberof Engine
507
+ */
508
+
486
509
  /** Triggers a callback for each object within a given area
487
- * @param {Vector2} [pos] - Center of test area, or undefined for all objects
488
- * @param {Vector2|number} [size] - Radius of circle if float, rectangle size if Vector2
489
- * @param {Function} [callbackFunction] - Calls this function on every object that passes the test
510
+ * @param {Vector2} [pos] - Center of test area, or undefined for all objects
511
+ * @param {Vector2|number} [size] - Radius of circle if float, rectangle size if Vector2
512
+ * @param {ObjectCallbackFunction} [callbackFunction] - Calls this function on every object that passes the test
490
513
  * @param {Array<EngineObject>} [objects=engineObjects] - List of objects to check
491
514
  * @memberof Engine */
492
515
  function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
@@ -950,6 +973,8 @@ function formatTime(t)
950
973
  async function fetchJSON(url)
951
974
  {
952
975
  const response = await fetch(url);
976
+ if (!response.ok)
977
+ throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);
953
978
  return response.json();
954
979
  }
955
980
 
@@ -960,6 +985,13 @@ async function fetchJSON(url)
960
985
  * @memberof Utilities */
961
986
  function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
962
987
 
988
+ /**
989
+ * Check if object is a valid string or can be converted to one
990
+ * @param {any} s
991
+ * @return {boolean}
992
+ * @memberof Utilities */
993
+ function isString(s) { return s !== undefined && s !== null && typeof s.toString() === 'string'; }
994
+
963
995
  ///////////////////////////////////////////////////////////////////////////////
964
996
 
965
997
  /** Random global functions
@@ -1022,6 +1054,7 @@ function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
1022
1054
  /**
1023
1055
  * Seeded random number generator
1024
1056
  * - Can be used to create a deterministic random number sequence
1057
+ * @memberof Engine
1025
1058
  * @example
1026
1059
  * let r = new RandomGenerator(123); // random number generator with seed 123
1027
1060
  * let a = r.float(); // random value between 0 and 1
@@ -1118,6 +1151,7 @@ function ASSERT_VECTOR2_NORMAL(v)
1118
1151
  /**
1119
1152
  * 2D Vector object with vector math library
1120
1153
  * - Functions do not change this so they can be chained together
1154
+ * @memberof Engine
1121
1155
  * @example
1122
1156
  * let a = new Vector2(2, 3); // vector with coordinates (2, 3)
1123
1157
  * let b = new Vector2; // vector with coordinates (0, 0)
@@ -1258,9 +1292,10 @@ class Vector2
1258
1292
  return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
1259
1293
  }
1260
1294
 
1261
- /** Set the integer direction of this vector, corresponding to multiples of 90 degree rotation (0-3)
1295
+ /** Sets this this vector to point in the specified integer direction (0-3), corresponding to multiples of 90 degree rotation
1262
1296
  * @param {number} [direction]
1263
- * @param {number} [length] */
1297
+ * @param {number} [length]
1298
+ * @return {Vector2} */
1264
1299
  setDirection(direction, length=1)
1265
1300
  {
1266
1301
  ASSERT_NUMBER_VALID(direction);
@@ -1268,8 +1303,10 @@ class Vector2
1268
1303
  direction = mod(direction, 4);
1269
1304
  ASSERT(direction===0 || direction===1 || direction===2 || direction===3,
1270
1305
  'Vector2.setDirection() direction must be an integer between 0 and 3.');
1271
- return vec2(direction%2 ? direction-1 ? -length : length : 0,
1272
- direction%2 ? 0 : direction ? -length : length);
1306
+
1307
+ this.x = direction%2 ? direction-1 ? -length : length : 0;
1308
+ this.y = direction%2 ? 0 : direction ? -length : length;
1309
+ return this;
1273
1310
  }
1274
1311
 
1275
1312
  /** Returns the integer direction of this vector, corresponding to multiples of 90 degree rotation (0-3)
@@ -1365,6 +1402,7 @@ function ASSERT_COLOR_VALID(c) { ASSERT(isColor(c), 'Color is invalid.', c); }
1365
1402
 
1366
1403
  /**
1367
1404
  * Color object (red, green, blue, alpha) with some helpful functions
1405
+ * @memberof Engine
1368
1406
  * @example
1369
1407
  * let a = new Color; // white
1370
1408
  * let b = new Color(1, 0, 0); // red
@@ -1542,7 +1580,8 @@ class Color
1542
1580
  * @return {Color} */
1543
1581
  setHex(hex)
1544
1582
  {
1545
- ASSERT(typeof hex === 'string' && hex[0] === '#', 'Color hex code must be a string starting with #');
1583
+ ASSERT(isString(hex), 'Color hex code must be a string');
1584
+ ASSERT(hex[0] === '#', 'Color hex code must start with #');
1546
1585
  ASSERT([4,5,7,9].includes(hex.length), 'Invalid hex');
1547
1586
 
1548
1587
  if (hex.length < 6)
@@ -1584,77 +1623,78 @@ class Color
1584
1623
  }
1585
1624
 
1586
1625
  ///////////////////////////////////////////////////////////////////////////////
1587
- // default colors
1626
+ // Default Colors
1588
1627
 
1589
1628
  /** Color - White #ffffff
1590
1629
  * @type {Color}
1591
1630
  * @memberof Utilities */
1592
- const WHITE = rgb();
1631
+ const WHITE = protectEngineConstant(rgb());
1593
1632
 
1594
- /** Color - Clear White #ffffff with 0 alpha
1633
+ /** Color - Clear White #757474ff with 0 alpha
1595
1634
  * @type {Color}
1596
1635
  * @memberof Utilities */
1597
- const CLEAR_WHITE = rgb(1,1,1,0);
1636
+ const CLEAR_WHITE = protectEngineConstant(rgb(1,1,1,0));
1598
1637
 
1599
1638
  /** Color - Black #000000
1600
1639
  * @type {Color}
1601
1640
  * @memberof Utilities */
1602
- const BLACK = rgb(0,0,0);
1641
+ const BLACK = protectEngineConstant(rgb(0,0,0));
1603
1642
 
1604
1643
  /** Color - Clear Black #000000 with 0 alpha
1605
1644
  * @type {Color}
1606
1645
  * @memberof Utilities */
1607
- const CLEAR_BLACK = rgb(0,0,0,0);
1646
+ const CLEAR_BLACK = protectEngineConstant(rgb(0,0,0,0));
1608
1647
 
1609
1648
  /** Color - Gray #808080
1610
1649
  * @type {Color}
1611
1650
  * @memberof Utilities */
1612
- const GRAY = rgb(.5,.5,.5);
1651
+ const GRAY = protectEngineConstant(rgb(.5,.5,.5));
1613
1652
 
1614
1653
  /** Color - Red #ff0000
1615
1654
  * @type {Color}
1616
1655
  * @memberof Utilities */
1617
- const RED = rgb(1,0,0);
1656
+ const RED = protectEngineConstant(rgb(1,0,0));
1618
1657
 
1619
1658
  /** Color - Orange #ff8000
1620
1659
  * @type {Color}
1621
1660
  * @memberof Utilities */
1622
- const ORANGE = rgb(1,.5,0);
1661
+ const ORANGE = protectEngineConstant(rgb(1,.5,0));
1623
1662
 
1624
1663
  /** Color - Yellow #ffff00
1625
1664
  * @type {Color}
1626
1665
  * @memberof Utilities */
1627
- const YELLOW = rgb(1,1,0);
1666
+ const YELLOW = protectEngineConstant(rgb(1,1,0));
1628
1667
 
1629
1668
  /** Color - Green #00ff00
1630
1669
  * @type {Color}
1631
1670
  * @memberof Utilities */
1632
- const GREEN = rgb(0,1,0);
1671
+ const GREEN = protectEngineConstant(rgb(0,1,0));
1633
1672
 
1634
1673
  /** Color - Cyan #00ffff
1635
1674
  * @type {Color}
1636
1675
  * @memberof Utilities */
1637
- const CYAN = rgb(0,1,1);
1676
+ const CYAN = protectEngineConstant(rgb(0,1,1));
1638
1677
 
1639
1678
  /** Color - Blue #0000ff
1640
1679
  * @type {Color}
1641
1680
  * @memberof Utilities */
1642
- const BLUE = rgb(0,0,1);
1681
+ const BLUE = protectEngineConstant(rgb(0,0,1));
1643
1682
 
1644
1683
  /** Color - Purple #8000ff
1645
1684
  * @type {Color}
1646
1685
  * @memberof Utilities */
1647
- const PURPLE = rgb(.5,0,1);
1686
+ const PURPLE = protectEngineConstant(rgb(.5,0,1));
1648
1687
 
1649
1688
  /** Color - Magenta #ff00ff
1650
1689
  * @type {Color}
1651
1690
  * @memberof Utilities */
1652
- const MAGENTA = rgb(1,0,1);
1691
+ const MAGENTA = protectEngineConstant(rgb(1,0,1));
1653
1692
 
1654
1693
  ///////////////////////////////////////////////////////////////////////////////
1655
1694
 
1656
1695
  /**
1657
1696
  * Timer object tracks how long has passed since it was set
1697
+ * @memberof Engine
1658
1698
  * @example
1659
1699
  * let a = new Timer; // creates a timer that is not set
1660
1700
  * a.set(3); // sets the timer to 3 seconds
@@ -1716,6 +1756,36 @@ class Timer
1716
1756
  /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
1717
1757
  * @return {number} */
1718
1758
  valueOf() { return this.get(); }
1759
+ }
1760
+
1761
+ ///////////////////////////////////////////////////////////////////////////////
1762
+ // Helper functions used by the engine
1763
+
1764
+ // make color constants immutable with debug assertions
1765
+ function protectEngineConstant(obj)
1766
+ {
1767
+ if (debug)
1768
+ {
1769
+ // get properties and store original values
1770
+ const props = Object.keys(obj), values = {};
1771
+ props.forEach(prop => values[prop] = obj[prop]);
1772
+
1773
+ // replace with getters/setters that assert
1774
+ props.forEach(prop =>
1775
+ {
1776
+ Object.defineProperty(obj, prop, {
1777
+ get: () => values[prop],
1778
+ set: (value) =>
1779
+ {
1780
+ ASSERT(false, `Cannot modify engine constant. Attempted to set constant (${obj}) property '${prop}' to '${value}'.`);
1781
+ },
1782
+ enumerable: true
1783
+ });
1784
+ });
1785
+ }
1786
+
1787
+ // freeze the object to prevent adding new properties
1788
+ return Object.freeze(obj);
1719
1789
  }
1720
1790
  /**
1721
1791
  * LittleJS Engine Settings
@@ -2023,7 +2093,7 @@ let medalsPreventUnlock = false;
2023
2093
  /** Set position of camera in world space
2024
2094
  * @param {Vector2} pos
2025
2095
  * @memberof Settings */
2026
- function setCameraPos(pos) { cameraPos = pos; }
2096
+ function setCameraPos(pos) { cameraPos = pos.copy(); }
2027
2097
 
2028
2098
  /** Set angle of camera in world space
2029
2099
  * @param {number} angle
@@ -2046,17 +2116,17 @@ function setCanvasColorTiles(colorTiles) { canvasColorTiles = colorTiles; }
2046
2116
  /** Set color to clear the canvas to before render
2047
2117
  * @param {Color} color
2048
2118
  * @memberof Settings */
2049
- function setCanvasClearColor(color) { canvasClearColor = color; }
2119
+ function setCanvasClearColor(color) { canvasClearColor = color.copy(); }
2050
2120
 
2051
2121
  /** Set max size of the canvas
2052
2122
  * @param {Vector2} size
2053
2123
  * @memberof Settings */
2054
- function setCanvasMaxSize(size) { canvasMaxSize = size; }
2124
+ function setCanvasMaxSize(size) { canvasMaxSize = size.copy(); }
2055
2125
 
2056
2126
  /** Set fixed size of the canvas
2057
2127
  * @param {Vector2} size
2058
2128
  * @memberof Settings */
2059
- function setCanvasFixedSize(size) { canvasFixedSize = size; }
2129
+ function setCanvasFixedSize(size) { canvasFixedSize = size.copy(); }
2060
2130
 
2061
2131
  /** Use nearest scaling algorithm for canvas for more pixelated look
2062
2132
  * - If enabled sets css image-rendering:pixelated
@@ -2121,7 +2191,7 @@ function setGLCircleSides(sides) { glCircleSides = sides; }
2121
2191
  /** Set default size of tiles in pixels
2122
2192
  * @param {Vector2} size
2123
2193
  * @memberof Settings */
2124
- function setTileSizeDefault(size) { tileSizeDefault = size; }
2194
+ function setTileSizeDefault(size) { tileSizeDefault = size.copy(); }
2125
2195
 
2126
2196
  /** Set to prevent tile bleeding from neighbors in pixels
2127
2197
  * @param {number} scale
@@ -2166,7 +2236,7 @@ function setObjectMaxSpeed(speed) { objectMaxSpeed = speed; }
2166
2236
  /** Set how much gravity to apply to objects
2167
2237
  * @param {Vector2} newGravity
2168
2238
  * @memberof Settings */
2169
- function setGravity(newGravity) { gravity = newGravity; }
2239
+ function setGravity(newGravity) { gravity = newGravity.copy(); }
2170
2240
 
2171
2241
  /** Set to scales emit rate of particles
2172
2242
  * @param {number} scale
@@ -2256,7 +2326,7 @@ function setMedalDisplaySlideTime(time) { medalDisplaySlideTime = time; }
2256
2326
  /** Set size of medal display
2257
2327
  * @param {Vector2} size
2258
2328
  * @memberof Settings */
2259
- function setMedalDisplaySize(size) { medalDisplaySize = size; }
2329
+ function setMedalDisplaySize(size) { medalDisplaySize = size.copy(); }
2260
2330
 
2261
2331
  /** Set to stop medals from being unlockable
2262
2332
  * @param {boolean} preventUnlock
@@ -2296,6 +2366,7 @@ function setDebugKey(key) { debugKey = key; }
2296
2366
  * - Collision for objects can be set to be solid to block other objects
2297
2367
  * - Objects may get pushed into overlapping other solid objects, if so they will push away
2298
2368
  * - Solid objects are more performance intensive and should be used sparingly
2369
+ * @memberof Engine
2299
2370
  * @example
2300
2371
  * // create an engine object, normally you would first extend the class with your own
2301
2372
  * const pos = vec2(2,3);
@@ -2304,18 +2375,18 @@ function setDebugKey(key) { debugKey = key; }
2304
2375
  class EngineObject
2305
2376
  {
2306
2377
  /** Create an engine object and adds it to the list of objects
2307
- * @param {Vector2} [pos=(0,0)] - World space position of the object
2308
- * @param {Vector2} [size=(1,1)] - World space size of the object
2309
- * @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
2310
- * @param {number} [angle] - Angle the object is rotated by
2311
- * @param {Color} [color=(1,1,1,1)] - Color to apply to tile when rendered
2312
- * @param {number} [renderOrder] - Objects sorted by renderOrder before being rendered
2378
+ * @param {Vector2} [pos=(0,0)] - World space position of the object
2379
+ * @param {Vector2} [size=(1,1)] - World space size of the object
2380
+ * @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
2381
+ * @param {number} [angle] - Angle the object is rotated by
2382
+ * @param {Color} [color=WHITE] - Color to apply to tile when rendered
2383
+ * @param {number} [renderOrder] - Objects sorted by renderOrder before being rendered
2313
2384
  */
2314
- constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color=new Color, renderOrder=0)
2385
+ constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color=WHITE, renderOrder=0)
2315
2386
  {
2316
2387
  // check passed in params
2317
- ASSERT(isVector2(pos), 'object pos should be a vec2');
2318
- ASSERT(isVector2(size), 'object size should be a vec2');
2388
+ ASSERT(isVector2(pos), 'object pos must be a vec2');
2389
+ ASSERT(isVector2(size), 'object size must be a vec2');
2319
2390
  ASSERT(!tileInfo || tileInfo instanceof TileInfo, 'object tileInfo should be a TileInfo or undefined');
2320
2391
  ASSERT(typeof angle === 'number' && isFinite(angle), 'object angle should be a number');
2321
2392
  ASSERT(isColor(color), 'object color should be a valid rgba color');
@@ -2600,7 +2671,7 @@ class EngineObject
2600
2671
  drawTile(this.pos, this.drawSize || this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
2601
2672
  }
2602
2673
 
2603
- /** Destroy this object, destroy its children, detach it's parent, and mark it for removal */
2674
+ /** Destroy this object, destroy its children, detach its parent, and mark it for removal */
2604
2675
  destroy()
2605
2676
  {
2606
2677
  if (this.destroyed)
@@ -2674,6 +2745,8 @@ class EngineObject
2674
2745
  addChild(child, localPos=vec2(), localAngle=0)
2675
2746
  {
2676
2747
  ASSERT(!child.parent && !this.children.includes(child));
2748
+ ASSERT(child instanceof EngineObject, 'child must be an EngineObject');
2749
+ ASSERT(child !== this, 'cannot add self as child');
2677
2750
  this.children.push(child);
2678
2751
  child.parent = this;
2679
2752
  child.localPos = localPos.copy();
@@ -2685,6 +2758,7 @@ class EngineObject
2685
2758
  removeChild(child)
2686
2759
  {
2687
2760
  ASSERT(child.parent === this && this.children.includes(child));
2761
+ ASSERT(child instanceof EngineObject, 'child must be an EngineObject');
2688
2762
  this.children.splice(this.children.indexOf(child), 1);
2689
2763
  child.parent = 0;
2690
2764
  }
@@ -2823,7 +2897,7 @@ let drawCount;
2823
2897
  * Create a tile info object using a grid based system
2824
2898
  * - This can take vecs or floats for easier use and conversion
2825
2899
  * - If an index is passed in, the tile size and index will determine the position
2826
- * @param {Vector2|number} [pos=0] - Index of tile in sheet
2900
+ * @param {Vector2|number} [pos=0] - Position of the tile in pixels, or tile index
2827
2901
  * @param {Vector2|number} [size=tileSizeDefault] - Size of tile in pixels
2828
2902
  * @param {number} [textureIndex] - Texture index to use
2829
2903
  * @param {number} [padding] - How many pixels padding around tiles
@@ -2868,6 +2942,7 @@ function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
2868
2942
 
2869
2943
  /**
2870
2944
  * Tile Info - Stores info about how to draw a tile
2945
+ * @memberof Draw
2871
2946
  */
2872
2947
  class TileInfo
2873
2948
  {
@@ -2928,7 +3003,10 @@ class TileInfo
2928
3003
  }
2929
3004
  }
2930
3005
 
2931
- /** Texture Info - Stores info about each texture */
3006
+ /**
3007
+ * Tile Info - Stores info about each texture
3008
+ * @memberof Draw
3009
+ */
2932
3010
  class TextureInfo
2933
3011
  {
2934
3012
  /**
@@ -2974,10 +3052,11 @@ class TextureInfo
2974
3052
  function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
2975
3053
  angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
2976
3054
  {
2977
- ASSERT(isVector2(pos), 'drawTile pos should be a vec2');
2978
- ASSERT(isVector2(size), 'drawTile size should be a vec2');
2979
- ASSERT(isColor(color) && (!additiveColor || isColor(additiveColor)), 'drawTile color is invalid');
2980
- ASSERT(isNumber(angle), 'drawTile angle should be a number');
3055
+ ASSERT(isVector2(pos), 'pos must be a vec2');
3056
+ ASSERT(isVector2(size), 'size must be a vec2');
3057
+ ASSERT(isColor(color), 'color is invalid');
3058
+ ASSERT(isNumber(angle), 'angle must be a number');
3059
+ ASSERT(!additiveColor || isColor(additiveColor), 'additiveColor must be a color');
2981
3060
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
2982
3061
 
2983
3062
  const textureInfo = tileInfo && tileInfo.textureInfo;
@@ -3072,10 +3151,10 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
3072
3151
  * @memberof Draw */
3073
3152
  function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0, useWebGL=glEnable, screenSpace=false, context)
3074
3153
  {
3075
- ASSERT(isVector2(pos), 'drawRectGradient pos should be a vec2');
3076
- ASSERT(isVector2(size), 'drawRectGradient size should be a vec2');
3077
- ASSERT(isColor(colorTop) && isColor(colorBottom), 'drawRectGradient color is invalid');
3078
- ASSERT(isNumber(angle), 'drawRectGradient angle should be a number');
3154
+ ASSERT(isVector2(pos), 'pos must be a vec2');
3155
+ ASSERT(isVector2(size), 'size must be a vec2');
3156
+ ASSERT(isColor(colorTop) && isColor(colorBottom), 'color is invalid');
3157
+ ASSERT(isNumber(angle), 'angle must be a number');
3079
3158
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
3080
3159
  if (useWebGL)
3081
3160
  {
@@ -3133,11 +3212,11 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
3133
3212
  * @memberof Draw */
3134
3213
  function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace, context)
3135
3214
  {
3136
- ASSERT(Array.isArray(points), 'drawLineList points should be an array');
3137
- ASSERT(isNumber(width), 'drawLineList width should be a number');
3138
- ASSERT(isColor(color), 'drawLineList color is invalid');
3139
- ASSERT(isVector2(pos), 'drawLineList pos should be a vec2');
3140
- ASSERT(isNumber(angle), 'drawLineList angle should be a number');
3215
+ ASSERT(Array.isArray(points), 'points must be an array');
3216
+ ASSERT(isNumber(width), 'width must be a number');
3217
+ ASSERT(isColor(color), 'color is invalid');
3218
+ ASSERT(isVector2(pos), 'pos must be a vec2');
3219
+ ASSERT(isNumber(angle), 'angle must be a number');
3141
3220
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
3142
3221
  if (useWebGL)
3143
3222
  {
@@ -3208,8 +3287,8 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
3208
3287
  * @memberof Draw */
3209
3288
  function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, lineColor=BLACK, angle=0, useWebGL=glEnable, screenSpace=false, context)
3210
3289
  {
3211
- ASSERT(isVector2(size), 'drawRegularPoly size should be a vec2');
3212
- ASSERT(isNumber(sides), 'drawRegularPoly sides should be a number');
3290
+ ASSERT(isVector2(size), 'size must be a vec2');
3291
+ ASSERT(isNumber(sides), 'sides must be a number');
3213
3292
 
3214
3293
  // build regular polygon points
3215
3294
  const points = [];
@@ -3235,12 +3314,13 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
3235
3314
  * @memberof Draw */
3236
3315
  function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace=false, context=undefined)
3237
3316
  {
3238
- ASSERT(isVector2(pos), 'drawPoly pos should be a vec2');
3239
- ASSERT(Array.isArray(points), 'drawPoly points should be an array');
3240
- ASSERT(isColor(color) && isColor(lineColor), 'drawPoly color is invalid');
3241
- ASSERT(isNumber(lineWidth), 'drawPoly lineWidth should be a number');
3242
- ASSERT(isNumber(angle), 'drawPoly angle should be a number');
3317
+ ASSERT(isVector2(pos), 'pos must be a vec2');
3318
+ ASSERT(Array.isArray(points), 'points must be an array');
3319
+ ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
3320
+ ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
3321
+ ASSERT(isNumber(angle), 'angle must be a number');
3243
3322
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
3323
+
3244
3324
  if (useWebGL)
3245
3325
  {
3246
3326
  let scale = 1;
@@ -3287,13 +3367,14 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
3287
3367
  * @memberof Draw */
3288
3368
  function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
3289
3369
  {
3290
- ASSERT(isVector2(pos), 'drawEllipse pos should be a vec2');
3291
- ASSERT(isVector2(size), 'drawEllipse size should be a vec2');
3292
- ASSERT(isColor(color) && isColor(lineColor), 'drawEllipse color is invalid');
3293
- ASSERT(isNumber(angle), 'drawEllipse angle should be a number');
3294
- ASSERT(isNumber(lineWidth), 'drawEllipse lineWidth should be a number');
3295
- ASSERT(lineWidth >= 0 && lineWidth < size.x && lineWidth < size.y, 'drawEllipse invalid lineWidth');
3370
+ ASSERT(isVector2(pos), 'pos must be a vec2');
3371
+ ASSERT(isVector2(size), 'size must be a vec2');
3372
+ ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
3373
+ ASSERT(isNumber(angle), 'angle must be a number');
3374
+ ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
3375
+ ASSERT(lineWidth >= 0 && lineWidth < size.x && lineWidth < size.y, 'invalid lineWidth');
3296
3376
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
3377
+
3297
3378
  if (useWebGL)
3298
3379
  {
3299
3380
  // draw as a regular polygon
@@ -3330,21 +3411,32 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
3330
3411
  * @memberof Draw */
3331
3412
  function drawCircle(pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
3332
3413
  {
3333
- ASSERT(isNumber(size), 'drawCircle size should be a number');
3414
+ ASSERT(isNumber(size), 'size must be a number');
3334
3415
  drawEllipse(pos, vec2(size), color, 0, lineWidth, lineColor, useWebGL, screenSpace, context);
3335
3416
  }
3336
3417
 
3418
+ /**
3419
+ * @callback Canvas2DDrawFunction - A function that draws to a 2D canvas context
3420
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
3421
+ * @memberof Draw
3422
+ */
3423
+
3337
3424
  /** Draw directly to a 2d canvas context in world space
3338
3425
  * @param {Vector2} pos
3339
3426
  * @param {Vector2} size
3340
3427
  * @param {number} angle
3341
3428
  * @param {boolean} [mirror]
3342
- * @param {Function} [drawFunction]
3429
+ * @param {Canvas2DDrawFunction} [drawFunction]
3343
3430
  * @param {boolean} [screenSpace=false]
3344
3431
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
3345
3432
  * @memberof Draw */
3346
3433
  function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpace=false, context=drawContext)
3347
3434
  {
3435
+ ASSERT(isVector2(pos), 'pos must be a vec2');
3436
+ ASSERT(isVector2(size), 'size must be a vec2');
3437
+ ASSERT(isNumber(angle), 'angle must be a number');
3438
+ ASSERT(typeof drawFunction === 'function', 'drawFunction must be a function');
3439
+
3348
3440
  if (!screenSpace)
3349
3441
  {
3350
3442
  // transform from world space to screen space
@@ -3412,6 +3504,16 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
3412
3504
  * @memberof Draw */
3413
3505
  function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, maxWidth, context=overlayContext)
3414
3506
  {
3507
+ ASSERT(isString(text), 'text must be a string');
3508
+ ASSERT(isVector2(pos), 'pos must be a vec2');
3509
+ ASSERT(isNumber(size), 'size must be a number');
3510
+ ASSERT(isColor(color), 'color must be a color');
3511
+ ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
3512
+ ASSERT(isColor(lineColor), 'lineColor must be a color');
3513
+ ASSERT(isColor(lineColor), 'lineColor must be a color');
3514
+ ASSERT(['left','center','right'].includes(textAlign), 'align must be left, center, or right');
3515
+ ASSERT(isString(font), 'font must be a string');
3516
+
3415
3517
  context.fillStyle = color.toString();
3416
3518
  context.strokeStyle = lineColor.toString();
3417
3519
  context.lineWidth = lineWidth;
@@ -3613,6 +3715,7 @@ let engineFontImage;
3613
3715
  * - 96 characters (from space to tilde) are stored in an image
3614
3716
  * - Uses a default 8x8 font if none is supplied
3615
3717
  * - You can also use fonts from the main tile sheet
3718
+ * @memberof Draw
3616
3719
  * @example
3617
3720
  * // use built in font
3618
3721
  * const font = new FontImage;
@@ -3669,7 +3772,7 @@ class FontImage
3669
3772
  * @param {boolean} [center]
3670
3773
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
3671
3774
  */
3672
- drawTextScreen(text, pos, scale=4, center, context=overlayContext)
3775
+ drawTextScreen(text, pos, scale=4, center=true, context=overlayContext)
3673
3776
  {
3674
3777
  context.save();
3675
3778
  const size = this.tileSize;
@@ -4363,6 +4466,7 @@ function audioInit()
4363
4466
  * Sound Object - Stores a sound for later use and can be played positionally
4364
4467
  *
4365
4468
  * <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
4469
+ * @memberof Audio
4366
4470
  * @example
4367
4471
  * // create a sound
4368
4472
  * const sound_example = new Sound([.5,.5]);
@@ -4457,12 +4561,12 @@ class Sound
4457
4561
 
4458
4562
  /** Play the sound as a musical note with a semitone offset
4459
4563
  * This can be used to play music with chromatic scales
4460
- * @param {number} semitoneOffset - How many semitones to offset pitch
4564
+ * @param {number} [semitoneOffset=0] - How many semitones to offset pitch
4461
4565
  * @param {Vector2} [pos] - World space position to play the sound if any
4462
4566
  * @param {number} [volume=1] - How much to scale volume by
4463
4567
  * @return {SoundInstance} - The audio source node
4464
4568
  */
4465
- playNote(semitoneOffset, pos, volume)
4569
+ playNote(semitoneOffset=0, pos, volume)
4466
4570
  {
4467
4571
  const pitch = getNoteFrequency(semitoneOffset, 1);
4468
4572
  return this.play(pos, volume, pitch, 0);
@@ -4485,6 +4589,8 @@ class Sound
4485
4589
  /**
4486
4590
  * Sound Wave Object - Stores a wave sound for later use and can be played positionally
4487
4591
  * - this can be used to play wave, mp3, and ogg files
4592
+ * @extends Sound
4593
+ * @memberof Audio
4488
4594
  * @example
4489
4595
  * // create a sound
4490
4596
  * const sound_example = new SoundWave('sound.mp3');
@@ -4494,22 +4600,29 @@ class Sound
4494
4600
  */
4495
4601
  class SoundWave extends Sound
4496
4602
  {
4603
+ /**
4604
+ * @callback SoundLoadCallback - Function called when sound is loaded
4605
+ * @param {SoundWave} sound
4606
+ * @memberof Audio
4607
+ */
4608
+
4497
4609
  /** Create a sound object and cache the wave file for later use
4498
4610
  * @param {string} filename - Filename of audio file to load
4499
4611
  * @param {number} [randomness] - How much to randomize frequency each time sound plays
4500
4612
  * @param {number} [range=soundDefaultRange] - World space max range of sound
4501
4613
  * @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
4502
- * @param {Function} [onloadCallback] - callback function to call when sound is loaded
4614
+ * @param {SoundLoadCallback} [onloadCallback] - callback function to call when sound is loaded
4503
4615
  */
4504
4616
  constructor(filename, randomness=0, range, taper, onloadCallback)
4505
4617
  {
4506
4618
  super(undefined, range, taper);
4507
4619
  if (!soundEnable || headlessMode) return;
4620
+ ASSERT(!filename || isString(filename), 'filename must be a string');
4508
4621
 
4509
- /** @property {Function} - callback function to call when sound is loaded */
4622
+ /** @property {SoundLoadCallback} - callback function to call when sound is loaded */
4510
4623
  this.onloadCallback = onloadCallback;
4511
4624
  this.randomness = randomness;
4512
- this.loadSound(filename);
4625
+ filename && this.loadSound(filename);
4513
4626
  }
4514
4627
 
4515
4628
  /** Loads a sound from a URL and decodes it into sample data. Must be used with await!
@@ -4518,6 +4631,8 @@ class SoundWave extends Sound
4518
4631
  async loadSound(filename)
4519
4632
  {
4520
4633
  const response = await fetch(filename);
4634
+ if (!response.ok)
4635
+ throw new Error(`Failed to load sound from ${filename}: ${response.status} ${response.statusText}`);
4521
4636
  const arrayBuffer = await response.arrayBuffer();
4522
4637
  const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
4523
4638
 
@@ -4553,7 +4668,7 @@ class SoundWave extends Sound
4553
4668
  this.sampleChannels = sampleChannels;
4554
4669
  this.loadedPercent = 1;
4555
4670
  if (this.onloadCallback)
4556
- this.onloadCallback();
4671
+ this.onloadCallback(this);
4557
4672
  }
4558
4673
  }
4559
4674
 
@@ -4562,6 +4677,7 @@ class SoundWave extends Sound
4562
4677
  /**
4563
4678
  * Sound Instance - Wraps an AudioBufferSourceNode for individual sound control
4564
4679
  * Represents a single playing instance of a sound with pause/resume capabilities
4680
+ * @memberof Audio
4565
4681
  * @example
4566
4682
  * // Play a sound and get an instance for control
4567
4683
  * const jumpSound = new Sound([.5,.5,220]);
@@ -4761,6 +4877,12 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
4761
4877
 
4762
4878
  ///////////////////////////////////////////////////////////////////////////////
4763
4879
 
4880
+ /**
4881
+ * @callback AudioEndedCallback - Function called when a sound ends
4882
+ * @param {AudioBufferSourceNode} source
4883
+ * @memberof Audio
4884
+ */
4885
+
4764
4886
  /** Play cached audio samples with given settings
4765
4887
  * @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
4766
4888
  * @param {number} [volume] - How much to scale volume by
@@ -4770,7 +4892,7 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
4770
4892
  * @param {number} [sampleRate=44100] - Sample rate for the sound
4771
4893
  * @param {GainNode} [gainNode] - Optional gain node for volume control while playing
4772
4894
  * @param {number} [offset] - Offset in seconds to start playback from
4773
- * @param {Function} [onended] - Callback for when the sound ends
4895
+ * @param {AudioEndedCallback} [onended] - Callback for when the sound ends
4774
4896
  * @return {AudioBufferSourceNode} - The audio node of the sound played
4775
4897
  * @memberof Audio */
4776
4898
  function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=audioDefaultSampleRate, gainNode, offset=0, onended)
@@ -4802,16 +4924,14 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
4802
4924
  if (onended)
4803
4925
  source.addEventListener('ended', ()=> onended(source));
4804
4926
 
4927
+ const startOffset = offset * rate;
4805
4928
  if (!audioIsRunning())
4806
4929
  {
4807
- // fix stalled audio, this sound won't be able to play
4808
- audioContext.resume();
4809
- return;
4930
+ // fix stalled audio and start
4931
+ audioContext.resume().then(()=>source.start(0, startOffset));
4810
4932
  }
4811
-
4812
- // play and return sound
4813
- const startOffset = offset * rate;
4814
- source.start(0, startOffset);
4933
+ else
4934
+ source.start(0, startOffset);
4815
4935
  return source;
4816
4936
  }
4817
4937
 
@@ -4976,7 +5096,7 @@ function zzfxG
4976
5096
  * - Unlimited numbers of layers, allocates canvases as needed
4977
5097
  * - Tile layers can be drawn to using their context with canvas2d
4978
5098
  * - Tile layers can also have collision with EngineObjects
4979
- * @namespace TileCollision
5099
+ * @namespace TileLayers
4980
5100
  */
4981
5101
 
4982
5102
  ///////////////////////////////////////////////////////////////////////////////
@@ -4984,13 +5104,13 @@ function zzfxG
4984
5104
 
4985
5105
  /** Keep track of all tile layers with collision
4986
5106
  * @type {Array<TileCollisionLayer>}
4987
- * @memberof TileCollision */
5107
+ * @memberof TileLayers */
4988
5108
  const tileCollisionLayers = [];
4989
5109
 
4990
5110
  /** Get tile collision data for a given cell in the grid
4991
5111
  * @param {Vector2} pos
4992
5112
  * @return {number}
4993
- * @memberof TileCollision */
5113
+ * @memberof TileLayers */
4994
5114
  function tileCollisionGetData(pos)
4995
5115
  {
4996
5116
  // check all tile collision layers
@@ -5006,7 +5126,7 @@ function tileCollisionGetData(pos)
5006
5126
  * @param {EngineObject} [object] - An object or undefined for generic test
5007
5127
  * @param {boolean} [solidOnly] - Only check solid layers if true
5008
5128
  * @return {TileCollisionLayer}
5009
- * @memberof TileCollision */
5129
+ * @memberof TileLayers */
5010
5130
  function tileCollisionTest(pos, size=vec2(), object, solidOnly=true)
5011
5131
  {
5012
5132
  for (const layer of tileCollisionLayers)
@@ -5024,7 +5144,7 @@ function tileCollisionTest(pos, size=vec2(), object, solidOnly=true)
5024
5144
  * @param {EngineObject} [object] - An object or undefined for generic test
5025
5145
  * @param {boolean} [solidOnly=true] - Only check solid layers if true
5026
5146
  * @return {Vector2}
5027
- * @memberof TileCollision */
5147
+ * @memberof TileLayers */
5028
5148
  function tileCollisionRaycast(posStart, posEnd, object, solidOnly=true)
5029
5149
  {
5030
5150
  for (const layer of tileCollisionLayers)
@@ -5047,8 +5167,8 @@ function tileCollisionRaycast(posStart, posEnd, object, solidOnly=true)
5047
5167
  * @param {number} [collisionLayer] - Layer to use for collision if any
5048
5168
  * @param {boolean} [draw] - Should the layer be drawn automatically
5049
5169
  * @return {Array<TileCollisionLayer>}
5050
- * @memberof TileCollision */
5051
- function tileCollisionLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisionLayer, draw=true)
5170
+ * @memberof TileLayers */
5171
+ function tileLayersLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisionLayer, draw=true)
5052
5172
  {
5053
5173
  if (!tileMapData)
5054
5174
  {
@@ -5079,9 +5199,9 @@ function tileCollisionLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisio
5079
5199
  tileLayers[layerIndex] = tileLayer;
5080
5200
 
5081
5201
  // apply layer color
5082
- const layerColor = dataLayer.color || WHITE;
5083
- if (dataLayer.tintcolor)
5084
- layerColor.setHex(dataLayer.tintcolor);
5202
+ const layerColor = dataLayer.tintcolor ?
5203
+ new Color().setHex(dataLayer.tintcolor) :
5204
+ dataLayer.color || WHITE;
5085
5205
  ASSERT(isColor(layerColor), 'layer color is not a color');
5086
5206
 
5087
5207
  for (let x=levelSize.x; x--;)
@@ -5108,6 +5228,7 @@ function tileCollisionLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisio
5108
5228
  ///////////////////////////////////////////////////////////////////////////////
5109
5229
  /**
5110
5230
  * Tile layer data object stores info about how to draw a tile
5231
+ * @memberof TileLayers
5111
5232
  * @example
5112
5233
  * // create tile layer data with tile index 0 and random orientation and color
5113
5234
  * const tileIndex = 0;
@@ -5145,6 +5266,7 @@ class TileLayerData
5145
5266
  * - Contains an offscreen canvas that can be rendered to
5146
5267
  * - WebGL rendering is optional, call useWebGL to enable
5147
5268
  * @extends EngineObject
5269
+ * @memberof TileLayers
5148
5270
  * @example
5149
5271
  * const canvasLayer = new CanvasLayer(vec2(), vec2(200,100));
5150
5272
  */
@@ -5159,6 +5281,7 @@ class CanvasLayer extends EngineObject
5159
5281
  */
5160
5282
  constructor(position, size, angle=0, renderOrder=0, canvasSize=vec2(512))
5161
5283
  {
5284
+ ASSERT(isVector2(canvasSize), 'canvasSize must be a Vector2');
5162
5285
  super(position, size, undefined, angle, WHITE, renderOrder);
5163
5286
 
5164
5287
  /** @property {HTMLCanvasElement} - The canvas used by this layer */
@@ -5206,12 +5329,18 @@ class CanvasLayer extends EngineObject
5206
5329
  drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
5207
5330
  }
5208
5331
 
5332
+ /**
5333
+ * @callback Canvas2DDrawCallback - Function that draws to a canvas 2D context
5334
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
5335
+ * @memberof TileLayers
5336
+ */
5337
+
5209
5338
  /** Draw onto the layer canvas in world space (bypass WebGL)
5210
5339
  * @param {Vector2} pos
5211
5340
  * @param {Vector2} size
5212
5341
  * @param {number} angle
5213
5342
  * @param {boolean} mirror
5214
- * @param {Function} drawFunction */
5343
+ * @param {Canvas2DDrawCallback} drawFunction */
5215
5344
  drawCanvas2D(pos, size, angle, mirror, drawFunction)
5216
5345
  {
5217
5346
  const context = this.context;
@@ -5248,7 +5377,7 @@ class CanvasLayer extends EngineObject
5248
5377
  else
5249
5378
  {
5250
5379
  // untextured
5251
- context.fillStyle = color;
5380
+ context.fillStyle = color.toString();
5252
5381
  context.fillRect(-.5, -.5, 1, 1);
5253
5382
  }
5254
5383
  });
@@ -5285,7 +5414,9 @@ class CanvasLayer extends EngineObject
5285
5414
  * - To allow dynamic modifications, layers are rendered using canvas 2d
5286
5415
  * - Some devices like mobile phones are limited to 4k texture resolution
5287
5416
  * - For with 16x16 tiles this limits layers to 256x256 on mobile devices
5417
+ * - Tile layers are centered on their corner, so normal levels are at (0,0)
5288
5418
  * @extends CanvasLayer
5419
+ * @memberof TileLayers
5289
5420
  * @example
5290
5421
  * const tileLayer = new TileLayer(vec2(), vec2(200,100));
5291
5422
  */
@@ -5295,15 +5426,14 @@ class TileLayer extends CanvasLayer
5295
5426
  * @param {Vector2} position - World space position
5296
5427
  * @param {Vector2} size - World space size
5297
5428
  * @param {TileInfo} [tileInfo] - Default tile info for layer (used for size and texture)
5298
- * @param {Vector2} [scale=(1,1)] - How much to scale this layer when rendered
5299
5429
  * @param {number} [renderOrder] - Objects are sorted by renderOrder
5300
5430
  * @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
5301
5431
  */
5302
- constructor(position, size, tileInfo=tile(), scale=vec2(1), renderOrder=0, useWebGL=glEnable)
5432
+ constructor(position, size, tileInfo=tile(), renderOrder=0, useWebGL=glEnable)
5303
5433
  {
5304
5434
  super(position, size, 0, renderOrder, size);
5435
+
5305
5436
  this.tileInfo = tileInfo;
5306
-
5307
5437
  const canvasSize = size.multiply(tileInfo.size);
5308
5438
  /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
5309
5439
  this.canvas = new OffscreenCanvas(canvasSize.x, canvasSize.y);
@@ -5340,6 +5470,8 @@ class TileLayer extends CanvasLayer
5340
5470
  * @param {boolean} [redraw] - Force the tile to redraw if true */
5341
5471
  setData(layerPos, data, redraw=false)
5342
5472
  {
5473
+ ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
5474
+ ASSERT(data instanceof TileLayerData, 'data must be a TileLayerData');
5343
5475
  if (layerPos.arrayCheck(this.size))
5344
5476
  {
5345
5477
  this.data[(layerPos.y|0)*this.size.x+layerPos.x|0] = data;
@@ -5351,7 +5483,10 @@ class TileLayer extends CanvasLayer
5351
5483
  * @param {Vector2} layerPos - Local position in array
5352
5484
  * @return {TileLayerData} */
5353
5485
  getData(layerPos)
5354
- { return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0]; }
5486
+ {
5487
+ ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
5488
+ return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0];
5489
+ }
5355
5490
 
5356
5491
  // Render the tile layer, called automatically by the engine
5357
5492
  render()
@@ -5360,9 +5495,10 @@ class TileLayer extends CanvasLayer
5360
5495
 
5361
5496
  // draw the tile layer as a single tile
5362
5497
  const tileInfo = new TileInfo().setFullImage(this.canvas, this.glTexture);
5363
- const pos = this.pos.add(this.size.scale(.5));
5498
+ const size = this.drawSize || this.size;
5499
+ const pos = this.pos.add(size.scale(.5));
5364
5500
  const useWebGL = glEnable && this.glTexture !== undefined;
5365
- drawTile(pos, this.size, tileInfo, WHITE, 0, false, CLEAR_BLACK, useWebGL);
5501
+ drawTile(pos, size, tileInfo, WHITE, 0, false, CLEAR_BLACK, useWebGL);
5366
5502
  }
5367
5503
 
5368
5504
  /** Draw all the tile data to an offscreen canvas
@@ -5454,6 +5590,7 @@ class TileLayer extends CanvasLayer
5454
5590
  * - there can be multiple tile collision layers
5455
5591
  * - tile collision layers should not overlap each other
5456
5592
  * @extends TileLayer
5593
+ * @memberof TileLayers
5457
5594
  */
5458
5595
  class TileCollisionLayer extends TileLayer
5459
5596
  {
@@ -5466,8 +5603,7 @@ class TileCollisionLayer extends TileLayer
5466
5603
  */
5467
5604
  constructor(position, size, tileInfo=tile(), renderOrder=0, useWebGL=glEnable)
5468
5605
  {
5469
- const scale = vec2(1); // collision layers are not scaled
5470
- super(position, size.floor(), tileInfo, scale, renderOrder, useWebGL);
5606
+ super(position, size.floor(), tileInfo, renderOrder, useWebGL);
5471
5607
 
5472
5608
  /** @property {Array<number>} - The tile collision grid */
5473
5609
  this.collisionData = [];
@@ -5497,6 +5633,7 @@ class TileCollisionLayer extends TileLayer
5497
5633
  * @param {Vector2} size - width and height of tile collision 2d grid */
5498
5634
  initCollision(size)
5499
5635
  {
5636
+ ASSERT(isVector2(size), 'size must be a Vector2');
5500
5637
  this.size = size.floor();
5501
5638
  this.collisionData = [];
5502
5639
  this.collisionData.length = size.area();
@@ -5508,6 +5645,7 @@ class TileCollisionLayer extends TileLayer
5508
5645
  * @param {number} [data] */
5509
5646
  setCollisionData(gridPos, data=1)
5510
5647
  {
5648
+ ASSERT(isVector2(gridPos), 'gridPos must be a Vector2');
5511
5649
  const i = (gridPos.y|0)*this.size.x + gridPos.x|0;
5512
5650
  gridPos.arrayCheck(this.size) && (this.collisionData[i] = data);
5513
5651
  }
@@ -5517,6 +5655,7 @@ class TileCollisionLayer extends TileLayer
5517
5655
  * @return {number} */
5518
5656
  getCollisionData(gridPos)
5519
5657
  {
5658
+ ASSERT(isVector2(gridPos), 'gridPos must be a Vector2');
5520
5659
  const i = (gridPos.y|0)*this.size.x + gridPos.x|0;
5521
5660
  return gridPos.arrayCheck(this.size) ? this.collisionData[i] : 0;
5522
5661
  }
@@ -5528,6 +5667,9 @@ class TileCollisionLayer extends TileLayer
5528
5667
  * @return {boolean} */
5529
5668
  collisionTest(pos, size=new Vector2, object)
5530
5669
  {
5670
+ ASSERT(isVector2(pos) && isVector2(size), 'pos and size must be Vector2s');
5671
+ ASSERT(!object || object instanceof EngineObject, 'object must be an EngineObject');
5672
+
5531
5673
  // transform to local layer space
5532
5674
  const posX = pos.x - this.pos.x;
5533
5675
  const posY = pos.y - this.pos.y;
@@ -5558,6 +5700,9 @@ class TileCollisionLayer extends TileLayer
5558
5700
  * @return {Vector2} */
5559
5701
  collisionRaycast(posStart, posEnd, object)
5560
5702
  {
5703
+ ASSERT(isVector2(posStart) && isVector2(posEnd), 'positions must be Vector2s');
5704
+ ASSERT(!object || object instanceof EngineObject, 'object must be an EngineObject');
5705
+
5561
5706
  // transform to local layer space
5562
5707
  const posStartX = posStart.x - this.pos.x;
5563
5708
  const posStartY = posStart.y - this.pos.y;
@@ -5607,9 +5752,16 @@ class TileCollisionLayer extends TileLayer
5607
5752
  * LittleJS Particle System
5608
5753
  */
5609
5754
 
5755
+ /**
5756
+ * @callback ParticleCallbackFunction - Function that processes a particle
5757
+ * @param {Particle} particle
5758
+ * @memberof Engine
5759
+ */
5760
+
5610
5761
  /**
5611
5762
  * Particle Emitter - Spawns particles with the given settings
5612
5763
  * @extends EngineObject
5764
+ * @memberof Engine
5613
5765
  * @example
5614
5766
  * // create a particle emitter
5615
5767
  * let pos = vec2(2,3);
@@ -5634,10 +5786,10 @@ class ParticleEmitter extends EngineObject
5634
5786
  * @param {number} [emitRate] - How many particles per second to spawn, does not emit if 0
5635
5787
  * @param {number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
5636
5788
  * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
5637
- * @param {Color} [colorStartA=(1,1,1,1)] - Color at start of life 1, randomized between start colors
5638
- * @param {Color} [colorStartB=(1,1,1,1)] - Color at start of life 2, randomized between start colors
5639
- * @param {Color} [colorEndA=(1,1,1,0)] - Color at end of life 1, randomized between end colors
5640
- * @param {Color} [colorEndB=(1,1,1,0)] - Color at end of life 2, randomized between end colors
5789
+ * @param {Color} [colorStartA=WHITE] - Color at start of life 1, randomized between start colors
5790
+ * @param {Color} [colorStartB=WHITE] - Color at start of life 2, randomized between start colors
5791
+ * @param {Color} [colorEndA=CLEAR_WHITE] - Color at end of life 1, randomized between end colors
5792
+ * @param {Color} [colorEndB=CLEAR_WHITE] - Color at end of life 2, randomized between end colors
5641
5793
  * @param {number} [particleTime] - How long particles live
5642
5794
  * @param {number} [sizeStart] - How big are particles at start
5643
5795
  * @param {number} [sizeEnd] - How big are particles at end
@@ -5664,10 +5816,10 @@ class ParticleEmitter extends EngineObject
5664
5816
  emitRate = 100,
5665
5817
  emitConeAngle = PI,
5666
5818
  tileInfo,
5667
- colorStartA = new Color,
5668
- colorStartB = new Color,
5669
- colorEndA = new Color(1,1,1,0),
5670
- colorEndB = new Color(1,1,1,0),
5819
+ colorStartA = WHITE,
5820
+ colorStartB = WHITE,
5821
+ colorEndA = CLEAR_WHITE,
5822
+ colorEndB = CLEAR_WHITE,
5671
5823
  particleTime = .5,
5672
5824
  sizeStart = .1,
5673
5825
  sizeEnd = 1,
@@ -5690,7 +5842,8 @@ class ParticleEmitter extends EngineObject
5690
5842
 
5691
5843
  // emitter settings
5692
5844
  /** @property {number|Vector2} - World space size of the emitter (float for circle diameter, vec2 for rect) */
5693
- this.emitSize = emitSize
5845
+ this.emitSize = emitSize instanceof Vector2 ?
5846
+ emitSize.copy() : emitSize;
5694
5847
  /** @property {number} - How long to stay alive (0 is forever) */
5695
5848
  this.emitTime = emitTime;
5696
5849
  /** @property {number} - How many particles per second to spawn, does not emit if 0 */
@@ -5700,13 +5853,13 @@ class ParticleEmitter extends EngineObject
5700
5853
 
5701
5854
  // color settings
5702
5855
  /** @property {Color} - Color at start of life 1, randomized between start colors */
5703
- this.colorStartA = colorStartA;
5856
+ this.colorStartA = colorStartA.copy();
5704
5857
  /** @property {Color} - Color at start of life 2, randomized between start colors */
5705
- this.colorStartB = colorStartB;
5858
+ this.colorStartB = colorStartB.copy();
5706
5859
  /** @property {Color} - Color at end of life 1, randomized between end colors */
5707
- this.colorEndA = colorEndA;
5860
+ this.colorEndA = colorEndA.copy();
5708
5861
  /** @property {Color} - Color at end of life 2, randomized between end colors */
5709
- this.colorEndB = colorEndB;
5862
+ this.colorEndB = colorEndB.copy();
5710
5863
  /** @property {boolean} - Should color be randomized linearly or across each component */
5711
5864
  this.randomColorLinear = randomColorLinear;
5712
5865
 
@@ -5741,9 +5894,9 @@ class ParticleEmitter extends EngineObject
5741
5894
  this.localSpace = localSpace;
5742
5895
  /** @property {number} - If non zero the particle is drawn as a trail, stretched in the direction of velocity */
5743
5896
  this.trailScale = 0;
5744
- /** @property {Function} - Callback when particle is destroyed */
5897
+ /** @property {ParticleCallbackFunction} - Callback when particle is destroyed */
5745
5898
  this.particleDestroyCallback = undefined;
5746
- /** @property {Function} - Callback when particle is created */
5899
+ /** @property {ParticleCallbackFunction} - Callback when particle is created */
5747
5900
  this.particleCreateCallback = undefined;
5748
5901
  /** @property {number} - Track particle emit time */
5749
5902
  this.emitTimeBuffer = 0;
@@ -5844,6 +5997,7 @@ class ParticleEmitter extends EngineObject
5844
5997
  /**
5845
5998
  * Particle Object - Created automatically by Particle Emitters
5846
5999
  * @extends EngineObject
6000
+ * @memberof Engine
5847
6001
  */
5848
6002
  class Particle extends EngineObject
5849
6003
  {
@@ -5862,7 +6016,7 @@ class Particle extends EngineObject
5862
6016
  * @param {boolean} additive - Does it use additive blend mode
5863
6017
  * @param {number} trailScale - If a trail, how long to make it
5864
6018
  * @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
5865
- * @param {Function} [destroyCallback] - Callback when particle dies
6019
+ * @param {ParticleCallbackFunction} [destroyCallback] - Callback when particle dies
5866
6020
  */
5867
6021
  constructor(position, tileInfo, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, fadeRate, additive, trailScale, localSpaceEmitter, destroyCallback
5868
6022
  )
@@ -5871,14 +6025,14 @@ class Particle extends EngineObject
5871
6025
 
5872
6026
  /** @property {Color} - Color at start of life */
5873
6027
  this.colorStart = colorStart;
5874
- /** @property {Color} - Calculated change in color */
5875
- this.colorEndDelta = colorEnd.subtract(colorStart);
6028
+ /** @property {Color} - Color at end of life */
6029
+ this.colorEnd = colorEnd;
5876
6030
  /** @property {number} - How long to live for */
5877
6031
  this.lifeTime = lifeTime;
5878
6032
  /** @property {number} - Size at start of life */
5879
6033
  this.sizeStart = sizeStart;
5880
- /** @property {number} - Calculated change in size */
5881
- this.sizeEndDelta = sizeEnd - sizeStart;
6034
+ /** @property {number} - Size at end of life */
6035
+ this.sizeEnd = sizeEnd;
5882
6036
  /** @property {number} - How quick to fade in/out */
5883
6037
  this.fadeRate = fadeRate;
5884
6038
  /** @property {boolean} - Is it additive */
@@ -5887,7 +6041,7 @@ class Particle extends EngineObject
5887
6041
  this.trailScale = trailScale;
5888
6042
  /** @property {ParticleEmitter} - Parent emitter if local space */
5889
6043
  this.localSpaceEmitter = localSpaceEmitter;
5890
- /** @property {Function} - Called when particle dies */
6044
+ /** @property {ParticleCallbackFunction} - Called when particle dies */
5891
6045
  this.destroyCallback = destroyCallback;
5892
6046
  // particles do not clamp speed by default
5893
6047
  this.clampSpeed = false;
@@ -5914,54 +6068,58 @@ class Particle extends EngineObject
5914
6068
  /** Render the particle, automatically called each frame, sorted by renderOrder */
5915
6069
  render()
5916
6070
  {
5917
- // modulate size and color
5918
- const p = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1;
5919
- const radius = this.sizeStart + p * this.sizeEndDelta;
5920
- const size = vec2(radius);
6071
+ // lerp color and size
6072
+ const p1 = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1, p2 = 1-p1;
6073
+ const radius = p2 * this.sizeStart + p1 * this.sizeEnd;
6074
+ this.size.x = this.size.y = radius;
6075
+ this.color.r = p2 * this.colorStart.r + p1 * this.colorEnd.r;
6076
+ this.color.g = p2 * this.colorStart.g + p1 * this.colorEnd.g;
6077
+ this.color.b = p2 * this.colorStart.b + p1 * this.colorEnd.b;
6078
+ this.color.a = p2 * this.colorStart.a + p1 * this.colorEnd.a;
6079
+
6080
+ // fade alpha
5921
6081
  const fadeRate = this.fadeRate/2;
5922
- const color = new Color(
5923
- this.colorStart.r + p * this.colorEndDelta.r,
5924
- this.colorStart.g + p * this.colorEndDelta.g,
5925
- this.colorStart.b + p * this.colorEndDelta.b,
5926
- (this.colorStart.a + p * this.colorEndDelta.a) *
5927
- (p < fadeRate ? p/fadeRate : p > 1-fadeRate ? (1-p)/fadeRate : 1)); // fade alpha
6082
+ this.color.a *= p1 < fadeRate ? p1/fadeRate :
6083
+ p1 > 1-fadeRate ? (1-p1)/fadeRate : 1;
5928
6084
 
5929
6085
  // draw the particle
5930
6086
  this.additive && setBlendMode(true);
5931
6087
 
6088
+ // update the position and angle for drawing
5932
6089
  let pos = this.pos, angle = this.angle;
5933
6090
  if (this.localSpaceEmitter)
5934
6091
  {
5935
6092
  // in local space of emitter
5936
- pos = this.localSpaceEmitter.pos.add(pos.rotate(-this.localSpaceEmitter.angle));
6093
+ const a = this.localSpaceEmitter.angle;
6094
+ const c = Math.cos(a), s = Math.sin(a);
6095
+ pos = this.localSpaceEmitter.pos.add(
6096
+ new Vector2(pos.x*c - pos.y*s, pos.x*s + pos.y*c));
5937
6097
  angle += this.localSpaceEmitter.angle;
5938
6098
  }
5939
6099
  if (this.trailScale)
5940
6100
  {
5941
6101
  // trail style particles
5942
- let velocity = this.velocity;
5943
- if (this.localSpaceEmitter)
5944
- velocity = velocity.rotate(-this.localSpaceEmitter.angle);
5945
- const speed = velocity.length();
6102
+ const direction = this.localSpaceEmitter ?
6103
+ this.velocity.rotate(-this.localSpaceEmitter.angle) :
6104
+ this.velocity;
6105
+ const speed = direction.length();
5946
6106
  if (speed)
5947
6107
  {
5948
- const direction = velocity.scale(1/speed);
6108
+ // stretch in direction of motion
5949
6109
  const trailLength = speed * this.trailScale;
5950
- size.y = max(size.x, trailLength);
5951
- angle = direction.angle();
5952
- drawTile(pos.add(direction.multiply(vec2(0,-trailLength/2))), size, this.tileInfo, color, angle, this.mirror);
6110
+ this.size.y = max(this.size.x, trailLength);
6111
+ angle = Math.atan2(direction.x, direction.y);
6112
+ drawTile(pos, this.size, this.tileInfo, this.color, angle, this.mirror);
5953
6113
  }
5954
6114
  }
5955
6115
  else
5956
- drawTile(pos, size, this.tileInfo, color, angle, this.mirror);
6116
+ drawTile(pos, this.size, this.tileInfo, this.color, angle, this.mirror);
5957
6117
  this.additive && setBlendMode();
5958
- debugParticles && debugRect(pos, size, '#f005', 0, angle);
6118
+ debugParticles && debugRect(pos, this.size, '#f005', 0, angle);
5959
6119
 
5960
- if (p === 1)
6120
+ if (p1 === 1)
5961
6121
  {
5962
- // destroy particle when it's time runs out
5963
- this.color = color;
5964
- this.size = size;
6122
+ // destroy particle when its time runs out
5965
6123
  this.destroyCallback && this.destroyCallback(this);
5966
6124
  this.destroyed = 1;
5967
6125
  }
@@ -6026,8 +6184,14 @@ function medalsInit(saveName)
6026
6184
  }
6027
6185
  }
6028
6186
 
6187
+ /**
6188
+ * @callback MedalCallbackFunction - Function that processes a medal
6189
+ * @param {Medal} medal
6190
+ * @memberof Medals
6191
+ */
6192
+
6029
6193
  /** Calls a function for each medal
6030
- * @param {Function} callback
6194
+ * @param {MedalCallbackFunction} callback
6031
6195
  * @memberof Medals */
6032
6196
  function medalsForEach(callback)
6033
6197
  { Object.values(medals).forEach(medal=>callback(medal)); }
@@ -6036,6 +6200,7 @@ function medalsForEach(callback)
6036
6200
 
6037
6201
  /**
6038
6202
  * Medal - Tracks an unlockable medal
6203
+ * @memberof Medals
6039
6204
  * @example
6040
6205
  * // create a medal
6041
6206
  * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
@@ -6104,11 +6269,12 @@ class Medal
6104
6269
  const height = medalDisplaySize.y;
6105
6270
  const x = overlayCanvas.width - width;
6106
6271
  const y = -height*hidePercent;
6272
+ const backgroundColor = hsl(0,0,.9);
6107
6273
 
6108
6274
  // draw containing rect and clip to that region
6109
6275
  context.save();
6110
6276
  context.beginPath();
6111
- context.fillStyle = new Color(.9,.9,.9).toString();
6277
+ context.fillStyle = backgroundColor.toString();
6112
6278
  context.strokeStyle = BLACK.toString();
6113
6279
  context.lineWidth = 3;
6114
6280
  context.rect(x, y, width, height);
@@ -6910,24 +7076,25 @@ function glPolyStrip(points)
6910
7076
  return strip;
6911
7077
  }
6912
7078
  /**
6913
- * LittleJS Newgrounds API
7079
+ * LittleJS Newgrounds Plugin
6914
7080
  * - NewgroundsMedal extends Medal with Newgrounds API functionality
6915
- * - Call new NewgroundsPlugin() to setup Newgrounds
7081
+ * - Call new NewgroundsPlugin(app_id) to setup Newgrounds
6916
7082
  * - Uses CryptoJS for encryption if optional cipher is provided
7083
+ * - provides functions to interact with medals scoreboards
6917
7084
  * - Keeps connection alive and logs views
6918
- * - Functions to interact with scoreboards
6919
- * - Functions to unlock medals
7085
+ * @namespace Newgrounds
6920
7086
  */
6921
7087
 
6922
7088
  /** Global Newgrounds object
6923
7089
  * @type {NewgroundsPlugin}
6924
- * @memberof Medal */
7090
+ * @memberof Newgrounds */
6925
7091
  let newgrounds;
6926
7092
 
6927
7093
  ///////////////////////////////////////////////////////////////////////////////
6928
7094
  /**
6929
7095
  * Newgrounds medal auto unlocks in newgrounds API
6930
7096
  * @extends Medal
7097
+ * @memberof Newgrounds
6931
7098
  */
6932
7099
  class NewgroundsMedal extends Medal
6933
7100
  {
@@ -6952,6 +7119,7 @@ class NewgroundsMedal extends Medal
6952
7119
  ///////////////////////////////////////////////////////////////////////////////
6953
7120
  /**
6954
7121
  * Newgrounds API object
7122
+ * @memberof Newgrounds
6955
7123
  */
6956
7124
  class NewgroundsPlugin
6957
7125
  {
@@ -7087,19 +7255,22 @@ class NewgroundsPlugin
7087
7255
  /**
7088
7256
  * LittleJS Post Processing Plugin
7089
7257
  * - Supports shadertoy style post processing shaders
7090
- * - call new new PostProcessPlugin() to setup post processing
7258
+ * - call new PostProcessPlugin() to setup post processing
7091
7259
  * - can be enabled to pass other canvases through a final shader
7260
+ * @namespace PostProcess
7092
7261
  */
7093
7262
 
7094
7263
  ///////////////////////////////////////////////////////////////////////////////
7095
7264
 
7096
7265
  /** Global Post Process plugin object
7097
- * @type {PostProcessPlugin} */
7266
+ * @type {PostProcessPlugin}
7267
+ * @memberof PostProcess */
7098
7268
  let postProcess;
7099
7269
 
7100
7270
  /////////////////////////////////////////////////////////////////////////
7101
7271
  /**
7102
7272
  * UI System Global Object
7273
+ * @memberof PostProcess
7103
7274
  */
7104
7275
  class PostProcessPlugin
7105
7276
  {
@@ -7207,12 +7378,15 @@ class PostProcessPlugin
7207
7378
  }
7208
7379
  /**
7209
7380
  * LittleJS ZzFXM Plugin
7381
+ * @namespace ZzFXM
7210
7382
  */
7211
7383
 
7212
7384
  /**
7213
7385
  * Music Object - Stores a zzfx music track for later use
7214
7386
  *
7215
7387
  * <a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a>
7388
+ * @extends Sound
7389
+ * @memberof ZzFXM
7216
7390
  * @example
7217
7391
  * // create some music
7218
7392
  * const music_example = new Music(
@@ -7271,7 +7445,8 @@ class ZzFXMusic extends Sound
7271
7445
  * @param {Array} patterns - Array of pattern data
7272
7446
  * @param {Array} sequence - Array of pattern indexes
7273
7447
  * @param {number} [BPM] - Playback speed of the song in BPM
7274
- * @return {Array} - Left and right channel sample data */
7448
+ * @return {Array} - Left and right channel sample data
7449
+ * @memberof ZzFXM */
7275
7450
  function zzfxM(instruments, patterns, sequence, BPM = 125)
7276
7451
  {
7277
7452
  let i, j, k;
@@ -7374,17 +7549,20 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
7374
7549
  * - Buttons
7375
7550
  * - Checkboxes
7376
7551
  * - Images
7552
+ * @namespace UISystem
7377
7553
  */
7378
7554
 
7379
7555
  ///////////////////////////////////////////////////////////////////////////////
7380
7556
 
7381
7557
  /** Global UI system plugin object
7382
- * @type {UISystemPlugin} */
7558
+ * @type {UISystemPlugin}
7559
+ * @memberof UISystem */
7383
7560
  let uiSystem;
7384
7561
 
7385
7562
  ///////////////////////////////////////////////////////////////////////////////
7386
7563
  /**
7387
7564
  * UI System Global Object
7565
+ * @memberof UISystem
7388
7566
  */
7389
7567
  class UISystemPlugin
7390
7568
  {
@@ -7429,10 +7607,12 @@ class UISystemPlugin
7429
7607
  this.uiObjects = [];
7430
7608
  /** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - Context to render UI elements to */
7431
7609
  this.uiContext = context;
7432
- /** @property {UIObject} - Top most object user is over */
7433
- this.hoverObject = undefined;
7434
7610
  /** @property {UIObject} - Object user is currently interacting with */
7435
7611
  this.activeObject = undefined;
7612
+ /** @property {UIObject} - Top most object user is over */
7613
+ this.hoverObject = undefined;
7614
+ /** @property {UIObject} - Hover object at start of update */
7615
+ this.lastHoverObject = undefined;
7436
7616
 
7437
7617
  engineAddPlugin(uiUpdate, uiRender);
7438
7618
 
@@ -7463,6 +7643,7 @@ class UISystemPlugin
7463
7643
  updateInvisibleObject(o);
7464
7644
  }
7465
7645
  // reset hover object at start of update
7646
+ uiSystem.lastHoverObject = uiSystem.hoverObject;
7466
7647
  uiSystem.hoverObject = undefined;
7467
7648
  for (let i = uiSystem.uiObjects.length; i--;)
7468
7649
  {
@@ -7495,6 +7676,13 @@ class UISystemPlugin
7495
7676
  * @param {number} [cornerRadius=uiSystem.defaultCornerRadius] */
7496
7677
  drawRect(pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, cornerRadius=uiSystem.defaultCornerRadius)
7497
7678
  {
7679
+ ASSERT(isVector2(pos), 'pos must be a vec2');
7680
+ ASSERT(isVector2(size), 'size must be a vec2');
7681
+ ASSERT(isColor(color), 'color must be a color');
7682
+ ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
7683
+ ASSERT(isColor(lineColor), 'lineColor must be a color');
7684
+ ASSERT(isNumber(cornerRadius), 'cornerRadius must be a number');
7685
+
7498
7686
  const context = uiSystem.uiContext;
7499
7687
  context.fillStyle = color.toString();
7500
7688
  context.beginPath();
@@ -7518,6 +7706,11 @@ class UISystemPlugin
7518
7706
  * @param {Color} [lineColor=uiSystem.defaultLineColor] */
7519
7707
  drawLine(posA, posB, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor)
7520
7708
  {
7709
+ ASSERT(isVector2(posA), 'posA must be a vec2');
7710
+ ASSERT(isVector2(posB), 'posB must be a vec2');
7711
+ ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
7712
+ ASSERT(isColor(lineColor), 'lineColor must be a color');
7713
+
7521
7714
  const context = uiSystem.uiContext;
7522
7715
  context.strokeStyle = lineColor.toString();
7523
7716
  context.lineWidth = lineWidth;
@@ -7553,12 +7746,37 @@ class UISystemPlugin
7553
7746
  {
7554
7747
  drawTextScreen(text, pos, size.y, color, lineWidth, lineColor, align, font, applyMaxWidth ? size.x : undefined, uiSystem.uiContext);
7555
7748
  }
7749
+
7750
+ /**
7751
+ * @callback DragAndDropCallback - Callback for drag and drop events
7752
+ * @param {DragEvent} event - The drag event
7753
+ * @memberof UISystem
7754
+ */
7755
+
7756
+ /** Setup drag and drop event handlers
7757
+ * Automatically prevents defaults and calls the given functions
7758
+ * @param {DragAndDropCallback} [onDrop] - when a file is dropped
7759
+ * @param {DragAndDropCallback} [onDragEnter] - when a file is dragged onto the window
7760
+ * @param {DragAndDropCallback} [onDragLeave] - when a file is dragged off the window
7761
+ * @param {DragAndDropCallback} [onDragOver] - continously when dragging over */
7762
+ setupDragAndDrop(onDrop, onDragEnter, onDragLeave, onDragOver)
7763
+ {
7764
+ function setCallback(callback, listenerType)
7765
+ {
7766
+ function listener(e) { e.preventDefault(); callback && callback(e); }
7767
+ document.addEventListener(listenerType, listener);
7768
+ }
7769
+ setCallback(onDrop, 'drop');
7770
+ setCallback(onDragEnter, 'dragenter');
7771
+ setCallback(onDragLeave, 'dragleave');
7772
+ setCallback(onDragOver, 'dragover');
7773
+ }
7556
7774
  }
7557
7775
 
7558
7776
  ///////////////////////////////////////////////////////////////////////////////
7559
7777
  /**
7560
7778
  * UI Object - Base level object for all UI elements
7561
- */
7779
+ * @memberof UISystem */
7562
7780
  class UIObject
7563
7781
  {
7564
7782
  /** Create a UIObject
@@ -7567,6 +7785,9 @@ class UIObject
7567
7785
  */
7568
7786
  constructor(pos=vec2(), size=vec2())
7569
7787
  {
7788
+ ASSERT(isVector2(pos), 'ui object pos must be a vec2');
7789
+ ASSERT(isVector2(size), 'ui object size must be a vec2');
7790
+
7570
7791
  /** @property {Vector2} - Local position of the object */
7571
7792
  this.localPos = pos.copy();
7572
7793
  /** @property {Vector2} - Screen space position of the object */
@@ -7574,21 +7795,21 @@ class UIObject
7574
7795
  /** @property {Vector2} - Screen space size of the object */
7575
7796
  this.size = size.copy();
7576
7797
  /** @property {Color} - Color of the object */
7577
- this.color = uiSystem.defaultColor;
7798
+ this.color = uiSystem.defaultColor.copy();
7578
7799
  /** @property {Color} - Color of the object when active, uses color if undefined */
7579
7800
  this.activeColor = undefined;
7580
7801
  /** @property {string} - Text for this ui object */
7581
7802
  this.text = undefined;
7582
7803
  /** @property {Color} - Color when disabled */
7583
- this.disabledColor = uiSystem.defaultDisabledColor;
7804
+ this.disabledColor = uiSystem.defaultDisabledColor.copy();
7584
7805
  /** @property {boolean} - Is this object disabled? */
7585
7806
  this.disabled = false;
7586
7807
  /** @property {Color} - Color for text */
7587
- this.textColor = uiSystem.defaultTextColor;
7808
+ this.textColor = uiSystem.defaultTextColor.copy()
7588
7809
  /** @property {Color} - Color used when hovering over the object */
7589
- this.hoverColor = uiSystem.defaultHoverColor;
7810
+ this.hoverColor = uiSystem.defaultHoverColor.copy()
7590
7811
  /** @property {Color} - Color for line drawing */
7591
- this.lineColor = uiSystem.defaultLineColor;
7812
+ this.lineColor = uiSystem.defaultLineColor.copy()
7592
7813
  /** @property {number} - Width for line drawing */
7593
7814
  this.lineWidth = uiSystem.defaultLineWidth;
7594
7815
  /** @property {number} - Corner radius for rounded rects */
@@ -7645,7 +7866,7 @@ class UIObject
7645
7866
  /** Update the object, called automatically by plugin once each frame */
7646
7867
  update()
7647
7868
  {
7648
- const wasHover = this.isHoverObject();
7869
+ const wasHover = uiSystem.lastHoverObject === this;
7649
7870
  const isActive = this.isActiveObject();
7650
7871
  const mouseDown = mouseIsDown(0);
7651
7872
  const mousePress = this.dragActivate ? mouseDown : mouseWasPressed(0);
@@ -7658,15 +7879,14 @@ class UIObject
7658
7879
  }
7659
7880
  if (this.isHoverObject())
7660
7881
  {
7661
- if (mousePress)
7662
- inputClearKey(0,0,0,1,0); // clear mouse was pressed state
7663
7882
  if (!this.disabled)
7664
7883
  {
7665
7884
  if (mousePress)
7666
7885
  {
7667
7886
  if (this.interactive)
7668
7887
  {
7669
- this.onPress();
7888
+ if (!this.dragActivate || (!wasHover || mouseWasPressed(0)))
7889
+ this.onPress();
7670
7890
  if (this.soundPress)
7671
7891
  this.soundPress.play();
7672
7892
  if (uiSystem.activeObject && !isActive)
@@ -7674,13 +7894,15 @@ class UIObject
7674
7894
  uiSystem.activeObject = this;
7675
7895
  }
7676
7896
  }
7677
- if (!mouseDown && uiSystem.activeObject === this && this.interactive)
7897
+ if (!mouseDown && this.isActiveObject() && this.interactive)
7678
7898
  {
7679
7899
  this.onClick();
7680
7900
  if (this.soundClick)
7681
7901
  this.soundClick.play();
7682
7902
  }
7683
7903
  }
7904
+ // clear mouse was pressed state even when disabled
7905
+ mousePress && inputClearKey(0,0,0,1,0);
7684
7906
  }
7685
7907
  if (isActive)
7686
7908
  if (!mouseDown || (this.dragActivate && !this.isHoverObject()))
@@ -7691,6 +7913,7 @@ class UIObject
7691
7913
  uiSystem.activeObject = undefined;
7692
7914
  }
7693
7915
 
7916
+ // call enter/leave events
7694
7917
  if (this.isHoverObject() !== wasHover)
7695
7918
  this.isHoverObject() ? this.onEnter() : this.onLeave();
7696
7919
  }
@@ -7701,7 +7924,7 @@ class UIObject
7701
7924
  if (!this.size.x || !this.size.y) return;
7702
7925
 
7703
7926
  const lineColor = this.interactive && this.isActiveObject() && !this.disabled ? this.color : this.lineColor;
7704
- const color = this.interactive ? this.disabled ? this.disabledColor : this.isActiveObject() ? this.activeColor || this.color : this.isHoverObject() ? this.hoverColor : this.color : this.color;
7927
+ const color = this.disabled ? this.disabledColor : this.interactive ? this.isActiveObject() ? this.activeColor || this.color : this.isHoverObject() ? this.hoverColor : this.color : this.color;
7705
7928
  uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius);
7706
7929
  }
7707
7930
 
@@ -7752,6 +7975,7 @@ class UIObject
7752
7975
  /**
7753
7976
  * UIText - A UI object that displays text
7754
7977
  * @extends UIObject
7978
+ * @memberof UISystem
7755
7979
  */
7756
7980
  class UIText extends UIObject
7757
7981
  {
@@ -7766,6 +7990,10 @@ class UIText extends UIObject
7766
7990
  {
7767
7991
  super(pos, size);
7768
7992
 
7993
+ ASSERT(isString(text), 'ui text must be a string');
7994
+ ASSERT(['left','center','right'].includes(align), 'ui text align must be left, center, or right');
7995
+ ASSERT(isString(font), 'ui text font must be a string');
7996
+
7769
7997
  // set properties
7770
7998
  this.text = text;
7771
7999
  this.align = align;
@@ -7785,6 +8013,7 @@ class UIText extends UIObject
7785
8013
  /**
7786
8014
  * UITile - A UI object that displays a tile image
7787
8015
  * @extends UIObject
8016
+ * @memberof UISystem
7788
8017
  */
7789
8018
  class UITile extends UIObject
7790
8019
  {
@@ -7799,15 +8028,19 @@ class UITile extends UIObject
7799
8028
  constructor(pos, size, tileInfo, color=WHITE, angle=0, mirror=false)
7800
8029
  {
7801
8030
  super(pos, size);
8031
+
8032
+ ASSERT(tileInfo instanceof TileInfo, 'ui tile tileInfo must be a TileInfo');
8033
+ ASSERT(isColor(color), 'ui tile color must be a color');
8034
+ ASSERT(isNumber(angle), 'ui tile angle must be a number');
8035
+
7802
8036
  /** @property {TileInfo} - Tile image to use */
7803
8037
  this.tileInfo = tileInfo;
7804
8038
  /** @property {number} - Angle to rotate in radians */
7805
8039
  this.angle = angle;
7806
8040
  /** @property {boolean} - Should it be mirrored? */
7807
8041
  this.mirror = mirror;
7808
-
7809
8042
  // set properties
7810
- this.color = color;
8043
+ this.color = color.copy();
7811
8044
  }
7812
8045
  render()
7813
8046
  {
@@ -7819,6 +8052,7 @@ class UITile extends UIObject
7819
8052
  /**
7820
8053
  * UIButton - A UI object that acts as a button
7821
8054
  * @extends UIObject
8055
+ * @memberof UISystem
7822
8056
  */
7823
8057
  class UIButton extends UIObject
7824
8058
  {
@@ -7832,9 +8066,12 @@ class UIButton extends UIObject
7832
8066
  {
7833
8067
  super(pos, size);
7834
8068
 
8069
+ ASSERT(isString(text), 'ui button must be a string');
8070
+ ASSERT(isColor(color), 'ui button color must be a color');
8071
+
7835
8072
  // set properties
7836
8073
  this.text = text;
7837
- this.color = color;
8074
+ this.color = color.copy()
7838
8075
  this.interactive = true;
7839
8076
  }
7840
8077
  render()
@@ -7852,6 +8089,7 @@ class UIButton extends UIObject
7852
8089
  /**
7853
8090
  * UICheckbox - A UI object that acts as a checkbox
7854
8091
  * @extends UIObject
8092
+ * @memberof UISystem
7855
8093
  */
7856
8094
  class UICheckbox extends UIObject
7857
8095
  {
@@ -7865,12 +8103,15 @@ class UICheckbox extends UIObject
7865
8103
  constructor(pos, size, checked=false, text='', color=uiSystem.defaultButtonColor)
7866
8104
  {
7867
8105
  super(pos, size);
8106
+
8107
+ ASSERT(isString(text), 'ui checkbox must be a string');
8108
+ ASSERT(isColor(color), 'ui checkbox color must be a color');
8109
+
7868
8110
  /** @property {boolean} - Current percentage value of this scrollbar 0-1 */
7869
8111
  this.checked = checked;
7870
-
7871
8112
  // set properties
7872
8113
  this.text = text;
7873
- this.color = color;
8114
+ this.color = color.copy();
7874
8115
  this.interactive = true;
7875
8116
  }
7876
8117
  onClick()
@@ -7902,6 +8143,7 @@ class UICheckbox extends UIObject
7902
8143
  /**
7903
8144
  * UIScrollbar - A UI object that acts as a scrollbar
7904
8145
  * @extends UIObject
8146
+ * @memberof UISystem
7905
8147
  */
7906
8148
  class UIScrollbar extends UIObject
7907
8149
  {
@@ -7917,14 +8159,19 @@ class UIScrollbar extends UIObject
7917
8159
  {
7918
8160
  super(pos, size);
7919
8161
 
8162
+ ASSERT(isNumber(value), 'ui scrollbar value must be a number');
8163
+ ASSERT(isString(text), 'ui scrollbar must be a string');
8164
+ ASSERT(isColor(color), 'ui scrollbar color must be a color');
8165
+ ASSERT(isColor(handleColor), 'ui scrollbar handleColor must be a color');
8166
+
7920
8167
  /** @property {number} - Current percentage value of this scrollbar 0-1 */
7921
8168
  this.value = value;
7922
8169
  /** @property {Color} - Color for the handle part of the scrollbar */
7923
- this.handleColor = handleColor;
8170
+ this.handleColor = handleColor.copy();
7924
8171
 
7925
8172
  // set properties
7926
8173
  this.text = text;
7927
- this.color = color;
8174
+ this.color = color.copy();
7928
8175
  this.interactive = true;
7929
8176
  }
7930
8177
  update()
@@ -7932,29 +8179,43 @@ class UIScrollbar extends UIObject
7932
8179
  super.update();
7933
8180
  if (this.isActiveObject() && this.interactive)
7934
8181
  {
8182
+ // handle horizontal or vertical scrollbar
8183
+ const isHorizontal = this.size.x > this.size.y;
8184
+ const handleSize = isHorizontal ? this.size.y : this.size.x;
8185
+ const barSize = isHorizontal ? this.size.x : this.size.y;
8186
+ const centerPos = isHorizontal ? this.pos.x : this.pos.y;
8187
+
7935
8188
  // check if value changed
7936
- const handleSize = vec2(this.size.y);
7937
- const handleWidth = this.size.x - handleSize.x;
7938
- const p1 = this.pos.x - handleWidth/2;
7939
- const p2 = this.pos.x + handleWidth/2;
8189
+ const handleWidth = barSize - handleSize;
8190
+ const p1 = centerPos - handleWidth/2;
8191
+ const p2 = centerPos + handleWidth/2;
7940
8192
  const oldValue = this.value;
7941
- this.value = percent(mousePosScreen.x, p1, p2);
8193
+ this.value = isHorizontal ?
8194
+ percent(mousePosScreen.x, p1, p2) :
8195
+ percent(mousePosScreen.y, p2, p1);
7942
8196
  this.value === oldValue || this.onChange();
7943
8197
  }
7944
8198
  }
7945
8199
  render()
7946
8200
  {
7947
8201
  super.render();
7948
-
8202
+
8203
+ // handle horizontal or vertical scrollbar
8204
+ const isHorizontal = this.size.x > this.size.y;
8205
+ const handleSize = isHorizontal ? this.size.y : this.size.x;
8206
+ const barSize = isHorizontal ? this.size.x : this.size.y;
8207
+ const centerPos = isHorizontal ? this.pos.x : this.pos.y;
8208
+
7949
8209
  // draw the scrollbar handle
7950
- const handleSize = vec2(this.size.y);
7951
- const handleWidth = this.size.x - handleSize.x;
7952
- const p1 = this.pos.x - handleWidth/2;
7953
- const p2 = this.pos.x + handleWidth/2;
7954
- const handlePos = vec2(lerp(p1, p2, this.value), this.pos.y);
8210
+ const handleWidth = barSize - handleSize;
8211
+ const p1 = centerPos - handleWidth/2;
8212
+ const p2 = centerPos + handleWidth/2;
8213
+ const handlePos = isHorizontal ?
8214
+ vec2(lerp(p1, p2, this.value), this.pos.y) :
8215
+ vec2(this.pos.x, lerp(p2, p1, this.value))
7955
8216
  const handleColor = this.disabled ? this.disabledColor :
7956
8217
  this.interactive && this.isActiveObject() ? this.color : this.handleColor;
7957
- uiSystem.drawRect(handlePos, handleSize, handleColor, this.lineWidth, this.lineColor, this.cornerRadius);
8218
+ uiSystem.drawRect(handlePos, vec2(handleSize), handleColor, this.lineWidth, this.lineColor, this.cornerRadius);
7958
8219
 
7959
8220
  // draw the text scaled to fit on the scrollbar
7960
8221
  const textSize = this.getTextSize();
@@ -8001,6 +8262,7 @@ function box2dSetDebug(enable) { box2dDebug = enable; }
8001
8262
  * - Each object has a Box2D body which can have multiple fixtures and joints
8002
8263
  * - Provides interface for Box2D body and fixture functions
8003
8264
  * @extends EngineObject
8265
+ * @memberof Box2D
8004
8266
  */
8005
8267
  class Box2dObject extends EngineObject
8006
8268
  {
@@ -8012,7 +8274,7 @@ class Box2dObject extends EngineObject
8012
8274
  * @param {Color} [color]
8013
8275
  * @param {number} [bodyType]
8014
8276
  * @param {number} [renderOrder] */
8015
- constructor(pos=vec2(), size, tileInfo, angle=0, color, bodyType=box2d.bodyTypeDynamic, renderOrder=0)
8277
+ constructor(pos, size, tileInfo, angle=0, color, bodyType=box2d.bodyTypeDynamic, renderOrder=0)
8016
8278
  {
8017
8279
  super(pos, size, tileInfo, angle, color, renderOrder);
8018
8280
 
@@ -8026,7 +8288,7 @@ class Box2dObject extends EngineObject
8026
8288
  this.lineColor = BLACK;
8027
8289
  }
8028
8290
 
8029
- /** Destroy this object and it's physics body */
8291
+ /** Destroy this object and its physics body */
8030
8292
  destroy()
8031
8293
  {
8032
8294
  // destroy physics body, fixtures, and joints
@@ -8510,6 +8772,7 @@ class Box2dRaycastResult
8510
8772
  * Box2D Joint
8511
8773
  * - Base class for Box2D joints
8512
8774
  * - A joint is used to connect objects together
8775
+ * @memberof Box2D
8513
8776
  */
8514
8777
  class Box2dJoint
8515
8778
  {
@@ -8565,6 +8828,7 @@ class Box2dJoint
8565
8828
  * - This a soft constraint with a max force
8566
8829
  * - This allows the constraint to stretch and without applying huge forces
8567
8830
  * @extends Box2dJoint
8831
+ * @memberof Box2D
8568
8832
  */
8569
8833
  class Box2dTargetJoint extends Box2dJoint
8570
8834
  {
@@ -8614,6 +8878,7 @@ class Box2dTargetJoint extends Box2dJoint
8614
8878
  * - Constrains two points on two objects to remain at a fixed distance
8615
8879
  * - You can view this as a massless, rigid rod
8616
8880
  * @extends Box2dJoint
8881
+ * @memberof Box2D
8617
8882
  */
8618
8883
  class Box2dDistanceJoint extends Box2dJoint
8619
8884
  {
@@ -8677,6 +8942,7 @@ class Box2dDistanceJoint extends Box2dJoint
8677
8942
  * Box2D Pin Joint
8678
8943
  * - Pins two objects together at a point
8679
8944
  * @extends Box2dDistanceJoint
8945
+ * @memberof Box2D
8680
8946
  */
8681
8947
  class Box2dPinJoint extends Box2dDistanceJoint
8682
8948
  {
@@ -8696,6 +8962,7 @@ class Box2dPinJoint extends Box2dDistanceJoint
8696
8962
  * Box2D Rope Joint
8697
8963
  * - Enforces a maximum distance between two points on two objects
8698
8964
  * @extends Box2dJoint
8965
+ * @memberof Box2D
8699
8966
  */
8700
8967
  class Box2dRopeJoint extends Box2dJoint
8701
8968
  {
@@ -8748,6 +9015,7 @@ class Box2dRopeJoint extends Box2dJoint
8748
9015
  * - You can use a motor to drive the relative rotation about the shared point
8749
9016
  * - A maximum motor torque is provided so that infinite forces are not generated
8750
9017
  * @extends Box2dJoint
9018
+ * @memberof Box2D
8751
9019
  */
8752
9020
  class Box2dRevoluteJoint extends Box2dJoint
8753
9021
  {
@@ -8849,6 +9117,7 @@ class Box2dRevoluteJoint extends Box2dJoint
8849
9117
  * - Either joint can be a revolute or prismatic joint
8850
9118
  * - You specify a gear ratio to bind the motions together
8851
9119
  * @extends Box2dJoint
9120
+ * @memberof Box2D
8852
9121
  */
8853
9122
  class Box2dGearJoint extends Box2dJoint
8854
9123
  {
@@ -8897,6 +9166,7 @@ class Box2dGearJoint extends Box2dJoint
8897
9166
  * - You can use a joint limit to restrict the range of motion
8898
9167
  * - You can use a joint motor to drive the motion or to model joint friction
8899
9168
  * @extends Box2dJoint
9169
+ * @memberof Box2D
8900
9170
  */
8901
9171
  class Box2dPrismaticJoint extends Box2dJoint
8902
9172
  {
@@ -9006,6 +9276,7 @@ class Box2dPrismaticJoint extends Box2dJoint
9006
9276
  * - You can use a joint motor to drive the motion or to model joint friction
9007
9277
  * - This joint is designed for vehicle suspensions
9008
9278
  * @extends Box2dJoint
9279
+ * @memberof Box2D
9009
9280
  */
9010
9281
  class Box2dWheelJoint extends Box2dJoint
9011
9282
  {
@@ -9101,6 +9372,7 @@ class Box2dWheelJoint extends Box2dJoint
9101
9372
  * Box2D Weld Joint
9102
9373
  * - Glues two objects together
9103
9374
  * @extends Box2dJoint
9375
+ * @memberof Box2D
9104
9376
  */
9105
9377
  class Box2dWeldJoint extends Box2dJoint
9106
9378
  {
@@ -9159,6 +9431,7 @@ class Box2dWeldJoint extends Box2dJoint
9159
9431
  * - Used to apply top-down friction
9160
9432
  * - Provides 2D translational friction and angular friction
9161
9433
  * @extends Box2dJoint
9434
+ * @memberof Box2D
9162
9435
  */
9163
9436
  class Box2dFrictionJoint extends Box2dJoint
9164
9437
  {
@@ -9213,6 +9486,7 @@ class Box2dFrictionJoint extends Box2dJoint
9213
9486
  * - The pulley supports a ratio such that: length1 + ratio * length2 <= constant
9214
9487
  * - The force transmitted is scaled by the ratio
9215
9488
  * @extends Box2dJoint
9489
+ * @memberof Box2D
9216
9490
  */
9217
9491
  class Box2dPulleyJoint extends Box2dJoint
9218
9492
  {
@@ -9280,6 +9554,7 @@ class Box2dPulleyJoint extends Box2dJoint
9280
9554
  * - Controls the relative motion between two objects
9281
9555
  * - Typical usage is to control the movement of a object with respect to the ground
9282
9556
  * @extends Box2dJoint
9557
+ * @memberof Box2D
9283
9558
  */
9284
9559
  class Box2dMotorJoint extends Box2dJoint
9285
9560
  {
@@ -9343,6 +9618,7 @@ class Box2dMotorJoint extends Box2dJoint
9343
9618
  /**
9344
9619
  * Box2D Global Object
9345
9620
  * - Wraps Box2d world and provides global functions
9621
+ * @memberof Box2D
9346
9622
  */
9347
9623
  class Box2dPlugin
9348
9624
  {