littlejsengine 1.1.2 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +55 -0
  2. package/build.bat +2 -1
  3. package/engine/engine.all.js +1320 -930
  4. package/engine/engine.all.min.js +1 -1
  5. package/engine/engine.all.release.js +965 -601
  6. package/engine/engine.js +71 -60
  7. package/engine/engineAudio.js +80 -22
  8. package/engine/engineBuild.bat +2 -2
  9. package/engine/engineDebug.js +65 -36
  10. package/engine/engineDraw.js +71 -56
  11. package/engine/engineInput.js +178 -28
  12. package/engine/engineMedals.js +66 -58
  13. package/engine/engineObject.js +86 -71
  14. package/engine/engineParticles.js +101 -63
  15. package/engine/engineRelease.js +4 -1
  16. package/engine/engineSettings.js +62 -53
  17. package/engine/engineTileLayer.js +99 -83
  18. package/engine/engineUtilities.js +95 -59
  19. package/engine/engineWebGL.js +53 -48
  20. package/examples/breakout/game.js +1 -1
  21. package/examples/breakout/gameObjects.js +2 -2
  22. package/examples/breakout/index.html +7 -5
  23. package/examples/platformer/game.js +7 -4
  24. package/examples/platformer/gameEffects.js +8 -11
  25. package/examples/platformer/gameLevel.js +15 -8
  26. package/examples/platformer/gameObjects.js +2 -2
  27. package/examples/platformer/gamePlayer.js +6 -6
  28. package/examples/platformer/index.html +10 -8
  29. package/examples/puzzle/game.js +3 -3
  30. package/examples/puzzle/index.html +6 -4
  31. package/examples/stress/index.html +7 -5
  32. package/game.js +10 -10
  33. package/index.html +17 -15
  34. package/package.json +4 -7
  35. package/tiles.png +0 -0
  36. package/LittleJS.zip +0 -0
  37. package/docs/Audio.html +0 -2264
  38. package/docs/Color.html +0 -2506
  39. package/docs/Debug.html +0 -2929
  40. package/docs/Draw.html +0 -3854
  41. package/docs/EngineObject.html +0 -2506
  42. package/docs/Input.html +0 -2478
  43. package/docs/Medal.html +0 -992
  44. package/docs/Medals.html +0 -624
  45. package/docs/Music.html +0 -569
  46. package/docs/Newgrounds.html +0 -1384
  47. package/docs/Particle.html +0 -567
  48. package/docs/ParticleEmitter.html +0 -1537
  49. package/docs/Random.html +0 -1770
  50. package/docs/Settings.html +0 -2885
  51. package/docs/Sound.html +0 -998
  52. package/docs/TileLayer.html +0 -3211
  53. package/docs/TileLayerData.html +0 -569
  54. package/docs/Timer.html +0 -1168
  55. package/docs/Utilities.html +0 -3266
  56. package/docs/Vector2.html +0 -3956
  57. package/docs/WebGL.html +0 -1842
  58. package/docs/engine.js.html +0 -428
  59. package/docs/engineAudio.js.html +0 -567
  60. package/docs/engineDebug.js.html +0 -721
  61. package/docs/engineDraw.js.html +0 -409
  62. package/docs/engineInput.js.html +0 -397
  63. package/docs/engineMedals.js.html +0 -445
  64. package/docs/engineObject.js.html +0 -492
  65. package/docs/engineParticles.js.html +0 -399
  66. package/docs/engineSettings.js.html +0 -358
  67. package/docs/engineTileLayer.js.html +0 -482
  68. package/docs/engineUtilities.js.html +0 -600
  69. package/docs/engineWebGL.js.html +0 -507
  70. package/docs/fonts/MavenPro-Regular.ttf +0 -0
  71. package/docs/fonts/Montserrat-Regular.ttf +0 -0
  72. package/docs/fonts/Muli-Black.ttf +0 -0
  73. package/docs/fonts/OFL-hind.txt +0 -93
  74. package/docs/fonts/OFL-montserrat.txt +0 -93
  75. package/docs/global.html +0 -1635
  76. package/docs/index.html +0 -243
  77. package/docs/scripts/fix-code-block.js +0 -53
  78. package/docs/scripts/fix-navbar.js +0 -25
  79. package/docs/scripts/linenumber.js +0 -25
  80. package/docs/scripts/misc.js +0 -217
  81. package/docs/scripts/resize.js +0 -85
  82. package/docs/scripts/search.js +0 -83
  83. package/docs/scripts/third-party/Apache-License-2.0.txt +0 -202
  84. package/docs/scripts/third-party/fuse.js +0 -9
  85. package/docs/scripts/third-party/lang-css.js +0 -2
  86. package/docs/scripts/third-party/prettify.js +0 -28
  87. package/docs/static/favicon.png +0 -0
  88. package/docs/styles/clean-jsdoc-theme-base.css +0 -393
  89. package/docs/styles/clean-jsdoc-theme-dark.css +0 -324
  90. package/docs/styles/clean-jsdoc-theme-light.css +0 -319
  91. package/docs/styles/reset.css +0 -346
  92. package/docs/styles/third-party/ionicons.min.css +0 -11
  93. package/docs/styles/third-party/prettify-jsdoc.css +0 -111
  94. package/docs/styles/third-party/prettify-tomorrow.css +0 -5
package/engine/engine.js CHANGED
@@ -22,21 +22,21 @@
22
22
  const engineName = 'LittleJS';
23
23
 
24
24
  /** Version of engine */
25
- const engineVersion = '1.1.2';
25
+ const engineVersion = '1.1.3';
26
26
 
27
27
  /** Frames per second to update objects
28
28
  * @default */
29
- const FPS = 60;
29
+ const frameRate = 60;
30
30
 
31
31
  /** How many seconds each frame lasts, engine uses a fixed time step
32
32
  * @default 1/60 */
33
- const timeDelta = 1/FPS;
33
+ const timeDelta = 1/frameRate;
34
34
 
35
35
  /** Array containing all engine objects */
36
36
  let engineObjects = [];
37
37
 
38
- /** Array containing only objects that are set to collide with other objects (for optimization) */
39
- let engineCollideObjects = [];
38
+ /** Array containing only objects that are set to collide with other objects this frame (for optimization) */
39
+ let engineObjectsCollide = [];
40
40
 
41
41
  /** Current update frame, used to calculate time */
42
42
  let frame = 0;
@@ -51,36 +51,40 @@ let timeReal = 0;
51
51
  let paused = 0;
52
52
 
53
53
  // Engine internal variables not exposed to documentation
54
- let frameTimeLastMS = 0, frameTimeBufferMS = 0, debugFPS = 0,
55
- shrinkTilesX, shrinkTilesY, drawCount, tileImageSize, tileImageSizeInverse;
54
+ let frameTimeLastMS = 0, frameTimeBufferMS = 0, tileImageSize, tileImageFixBleed;
55
+
56
+ // Engine stat tracking, if showWatermark is true
57
+ let averageFPS, drawCount;
58
+
59
+ // css text used for elements created by engine
60
+ const styleBody = 'margin:0;overflow:hidden;background:#000' +
61
+ ';touch-action:none;user-select:none;-webkit-user-select:none;-moz-user-select:none';
62
+ const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
56
63
 
57
64
  ///////////////////////////////////////////////////////////////////////////////
58
65
 
59
66
  /** Start up LittleJS engine with your callback functions
60
- * @param {Function} gameInit - Called once after the engine starts up, setup the game
61
- * @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state
62
- * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
63
- * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
64
- * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
65
- * @param {String} tileImageSource - Tile image to use, everything starts when the image is finished loading
67
+ * @param {Function} gameInit - Called once after the engine starts up, setup the game
68
+ * @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state
69
+ * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
70
+ * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
71
+ * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
72
+ * @param {String} [tileImageSource] - Tile image to use, everything starts when the image is finished loading
66
73
  */
67
74
  function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, tileImageSource)
68
75
  {
69
- // init engine when tiles load
70
- tileImage.onload = ()=>
76
+ // init engine when tiles load or fail to load
77
+ tileImage.onerror = tileImage.onload = ()=>
71
78
  {
72
79
  // save tile image info
73
- tileImageSizeInverse = vec2(1).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
80
+ tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
74
81
  debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
75
- shrinkTilesX = tileBleedShrinkFix/tileImageSize.x;
76
- shrinkTilesY = tileBleedShrinkFix/tileImageSize.y;
77
82
 
78
83
  // setup html
84
+ document.body.style = styleBody;
79
85
  document.body.appendChild(mainCanvas = document.createElement('canvas'));
80
- document.body.style = 'margin:0;overflow:hidden;background:#000';
81
- mainCanvas.style = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)' +
82
- (pixelated ? ';image-rendering:crisp-edges;image-rendering:pixelated' : ''); // pixelated rendering
83
86
  mainContext = mainCanvas.getContext('2d');
87
+ mainCanvas.style = styleCanvas;
84
88
 
85
89
  // init stuff and start engine
86
90
  debugInit();
@@ -88,10 +92,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
88
92
 
89
93
  // create overlay canvas for hud to appear above gl canvas
90
94
  document.body.appendChild(overlayCanvas = document.createElement('canvas'));
91
- overlayCanvas.style = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
92
95
  overlayContext = overlayCanvas.getContext('2d');
93
-
96
+ overlayCanvas.style = styleCanvas;
97
+
94
98
  gameInit();
99
+ touchGamepadCreate();
95
100
  engineUpdate();
96
101
  };
97
102
 
@@ -104,7 +109,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
104
109
  let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
105
110
  frameTimeLastMS = frameTimeMS;
106
111
  if (debug || showWatermark)
107
- debugFPS = lerp(.05, 1e3/(frameTimeDeltaMS||1), debugFPS);
112
+ averageFPS = lerp(.05, averageFPS || 0, 1e3/(frameTimeDeltaMS||1));
108
113
  if (debug)
109
114
  frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
110
115
  timeReal += frameTimeDeltaMS / 1e3;
@@ -130,7 +135,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
130
135
  }
131
136
 
132
137
  // update multiple frames if necessary in case of slow framerate
133
- for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / FPS)
138
+ for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / frameRate)
134
139
  {
135
140
  // update game and objects
136
141
  inputUpdate();
@@ -147,45 +152,39 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
147
152
  frameTimeBufferMS += deltaSmooth;
148
153
  }
149
154
 
150
- if (fixedWidth)
155
+ if (canvasFixedSize.x)
151
156
  {
152
157
  // clear set fixed size
153
- mainCanvas.width = fixedWidth;
154
- mainCanvas.height = fixedHeight;
158
+ mainCanvas.width = canvasFixedSize.x;
159
+ mainCanvas.height = canvasFixedSize.y;
155
160
 
156
- if (fixedFitToWindow)
161
+ // fit to window by adding space on top or bottom if necessary
162
+ const aspect = innerWidth / innerHeight;
163
+ const fixedAspect = mainCanvas.width / mainCanvas.height;
164
+ mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
165
+ mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
166
+ if (glCanvas)
157
167
  {
158
- // fit to window by adding space on top or bottom if necessary
159
- const aspect = innerWidth / innerHeight;
160
- const fixedAspect = fixedWidth / fixedHeight;
161
- mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
162
- mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
163
- if (glCanvas)
164
- {
165
- glCanvas.style.width = mainCanvas.style.width;
166
- glCanvas.style.height = mainCanvas.style.height;
167
- }
168
+ glCanvas.style.width = mainCanvas.style.width;
169
+ glCanvas.style.height = mainCanvas.style.height;
168
170
  }
169
171
  }
170
172
  else
171
173
  {
172
174
  // clear and set size to same as window
173
- mainCanvas.width = min(innerWidth, maxWidth);
174
- mainCanvas.height = min(innerHeight, maxHeight);
175
+ mainCanvas.width = min(innerWidth, canvasMaxSize.x);
176
+ mainCanvas.height = min(innerHeight, canvasMaxSize.y);
175
177
  }
176
178
 
177
- // save canvas size and clear overlay canvas
178
- mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
179
- mainContext.imageSmoothingEnabled = !pixelated; // disable smoothing for pixel art
180
-
181
179
  // render sort then render while removing destroyed objects
182
- glPreRender(mainCanvas.width, mainCanvas.height);
180
+ enginePreRender();
183
181
  gameRender();
184
182
  engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
185
183
  for (const o of engineObjects)
186
184
  o.destroyed || o.render();
187
185
  gameRenderPost();
188
186
  medalsRender();
187
+ touchGamepadRender();
189
188
  debugRender();
190
189
  glCopyToContext(mainContext);
191
190
 
@@ -197,7 +196,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
197
196
  overlayContext.font = '1em monospace';
198
197
  overlayContext.fillStyle = '#000';
199
198
  const text = engineName + ' ' + 'v' + engineVersion + ' / '
200
- + drawCount + ' / ' + engineObjects.length + ' / ' + debugFPS.toFixed(1);
199
+ + drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
200
+ + ' ' + (glEnable ? 'GL' : '2D') ;
201
201
  overlayContext.fillText(text, mainCanvas.width-3, 3);
202
202
  overlayContext.fillStyle = '#fff';
203
203
  overlayContext.fillText(text, mainCanvas.width-2, 2);
@@ -209,12 +209,27 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
209
209
  tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
210
210
  }
211
211
 
212
+ // called by engine to setup render system
213
+ function enginePreRender()
214
+ {
215
+ // save canvas size and clear canvases
216
+ mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
217
+
218
+ // disable smoothing for pixel art
219
+ mainContext.imageSmoothingEnabled = !cavasPixelated;
220
+
221
+ // setup gl rendering if enabled
222
+ glPreRender(mainCanvas.width, mainCanvas.height, cameraPos.x, cameraPos.y, cameraScale);
223
+ }
212
224
 
213
225
  ///////////////////////////////////////////////////////////////////////////////
214
226
 
215
227
  /** Calls update on each engine object (recursively if child), removes destroyed objects, and updated time */
216
228
  function engineObjectsUpdate()
217
229
  {
230
+ // get list of solid objects for physics optimzation
231
+ engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
232
+
218
233
  // recursive object update
219
234
  const updateObject = (o)=>
220
235
  {
@@ -230,42 +245,38 @@ function engineObjectsUpdate()
230
245
 
231
246
  // remove destroyed objects
232
247
  engineObjects = engineObjects.filter(o=>!o.destroyed);
233
- engineCollideObjects = engineCollideObjects.filter(o=>!o.destroyed);
234
248
 
235
249
  // increment frame and update time
236
- time = ++frame / FPS;
250
+ time = ++frame / frameRate;
237
251
  }
238
252
 
239
- /** Detroy and remove all objects that are not persistent or descendants of a persistent object */
253
+ /** Destroy and remove all objects */
240
254
  function engineObjectsDestroy()
241
255
  {
242
256
  for (const o of engineObjects)
243
- o.persistent || o.parent || o.destroy();
257
+ o.parent || o.destroy();
244
258
  engineObjects = engineObjects.filter(o=>!o.destroyed);
245
259
  }
246
260
 
247
261
  /** Triggers a callback for each object within a given area
248
- * @param {Vector2} [pos] - Center of test area
249
- * @param {Number} [size] - Radius of circle if float, rectangle size if Vector2
250
- * @param {Function} [callbackFunction] - Calls this function on every object that passes the test
262
+ * @param {Vector2} [pos] - Center of test area
263
+ * @param {Number} [size] - Radius of circle if float, rectangle size if Vector2
264
+ * @param {Function} [callbackFunction] - Calls this function on every object that passes the test
251
265
  * @param {Array} [objects=engineObjects] - List of objects to check */
252
266
  function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
253
267
  {
254
- if (!pos)
268
+ if (!pos) // all objects
255
269
  {
256
- // all objects
257
270
  for (const o of objects)
258
271
  callbackFunction(o);
259
272
  }
260
- else if (size.x != undefined)
273
+ else if (size.x != undefined) // bounding box test
261
274
  {
262
- // aabb test
263
275
  for (const o of objects)
264
276
  isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
265
277
  }
266
- else
278
+ else // circle test
267
279
  {
268
- // circle test
269
280
  const sizeSquared = size*size;
270
281
  for (const o of objects)
271
282
  pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
@@ -1,28 +1,41 @@
1
1
  /**
2
- * LittleJS Audio System
3
- * <br> - ZzFX Sound Effects and ZzFXM Music
4
- * <br> - Caches sounds and music for fast playback
5
- * <br> - Can attenuate and apply stereo panning to sounds
6
- * <br> - Ability to play mp3, ogg, and wave files
7
- * <br> - Speech synthesis wrapper functions
8
- * @namespace Audio
2
+ * LittleJS Audio System
3
+ * <br> - <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX Sound Effects</a>
4
+ * <br> - <a href=https://keithclark.github.io/ZzFXM/>ZzFXM Music</a>
5
+ * <br> - Caches sounds and music for fast playback
6
+ * <br> - Can attenuate and apply stereo panning to sounds
7
+ * <br> - Ability to play mp3, ogg, and wave files
8
+ * <br> - Speech synthesis wrapper functions
9
9
  */
10
10
 
11
11
  'use strict';
12
12
 
13
- /** Sound Object - Stores a zzfx sound for later use and can be played positionally */
13
+ /**
14
+ * Sound Object - Stores a zzfx sound for later use and can be played positionally
15
+ * <br>
16
+ * <br><b><a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a></b>
17
+ * @example
18
+ * // create a sound
19
+ * const sound_example = new Sound([.5,.5]);
20
+ *
21
+ * // play the sound
22
+ * sound_example.play();
23
+ */
14
24
  class Sound
15
25
  {
16
26
  /** Create a sound object and cache the zzfx samples for later use
17
27
  * @param {Array} zzfxSound - Array of zzfx parameters, ex. [.5,.5]
18
- * @param {Number} [range=defaultSoundRange] - World space max range of sound, will not play if camera is farther away
19
- * @param {Number} [taper=defaultSoundTaper] - At what percentage of range should it start tapering off
28
+ * @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
29
+ * @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
20
30
  */
21
- constructor(zzfxSound, range=defaultSoundRange, taper=defaultSoundTaper)
31
+ constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
22
32
  {
23
33
  if (!soundEnable) return;
24
34
 
35
+ /** @property {Number} - World space max range of sound, will not play if camera is farther away */
25
36
  this.range = range;
37
+
38
+ /** @property {Number} - At what percentage of range should it start tapering off */
26
39
  this.taper = taper;
27
40
 
28
41
  // get randomness from sound parameters
@@ -56,7 +69,7 @@ class Sound
56
69
  return; // out of range
57
70
 
58
71
  // attenuate volume by distance
59
- volume *= percent(lengthSquared**.5, range*this.taper, range);
72
+ volume *= percent(lengthSquared**.5, range, range*this.taper);
60
73
  }
61
74
 
62
75
  // get pan from screen space coords
@@ -82,7 +95,36 @@ class Sound
82
95
  }
83
96
  }
84
97
 
85
- /** Music Object - Stores a zzfx music track for later use */
98
+ /**
99
+ * Music Object - Stores a zzfx music track for later use
100
+ * <br>
101
+ * <br><b><a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a></b>
102
+ * @example
103
+ * // create some music
104
+ * const music_example = new Music(
105
+ * [
106
+ * [ // instruments
107
+ * [,0,400] // simple note
108
+ * ],
109
+ * [ // patterns
110
+ * [ // pattern 1
111
+ * [ // channel 0
112
+ * 0, -1, // instrument 0, left speaker
113
+ * 1, 0, 9, 1 // channel notes
114
+ * ],
115
+ * [ // channel 1
116
+ * 0, 1, // instrument 1, right speaker
117
+ * 0, 12, 17, -1 // channel notes
118
+ * ]
119
+ * ],
120
+ * ],
121
+ * [0, 0, 0, 0], // sequence, play pattern 0 four times
122
+ * 90 // BPM
123
+ * ]);
124
+ *
125
+ * // play the music
126
+ * music_example.play();
127
+ */
86
128
  class Music
87
129
  {
88
130
  /** Create a music object and cache the zzfx music samples for later use
@@ -119,7 +161,7 @@ function playAudioFile(url, volume=1, loop=1)
119
161
  if (!soundEnable) return;
120
162
 
121
163
  const audio = new Audio(url);
122
- audio.volume = audioVolume * volume;
164
+ audio.volume = soundVolume * volume;
123
165
  audio.loop = loop;
124
166
  audio.play();
125
167
  return audio;
@@ -144,7 +186,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
144
186
  // build utterance and speak
145
187
  const utterance = new SpeechSynthesisUtterance(text);
146
188
  utterance.lang = language;
147
- utterance.volume = 2*volume*audioVolume;
189
+ utterance.volume = 2*volume*soundVolume;
148
190
  utterance.rate = rate;
149
191
  utterance.pitch = pitch;
150
192
  speechSynthesis.speak(utterance);
@@ -153,7 +195,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
153
195
 
154
196
  /** Stop all queued speech
155
197
  * @memberof Audio */
156
- const stopSpeech = ()=> speechSynthesis && speechSynthesis.cancel();
198
+ const speakStop = ()=> speechSynthesis && speechSynthesis.cancel();
157
199
 
158
200
  /** Get frequency of a note on a musical scale
159
201
  * @param {Number} semitoneOffset - How many semitones away from the root note
@@ -184,6 +226,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
184
226
  if (!audioContext)
185
227
  audioContext = new (window.AudioContext||webkitAudioContext);
186
228
 
229
+ // fix stalled audio
230
+ audioContext.resume();
231
+
232
+ // prevent sounds from building up if they can't be played
233
+ if (audioContext.state != 'running')
234
+ return;
235
+
187
236
  // create buffer and source
188
237
  const buffer = audioContext.createBuffer(sampleChannels.length, sampleChannels[0].length, zzfxR),
189
238
  source = audioContext.createBufferSource();
@@ -194,11 +243,18 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
194
243
  source.playbackRate.value = rate;
195
244
  source.loop = loop;
196
245
 
197
- // create pan and gain nodes
198
- source
199
- .connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, 1, -1)}))
200
- .connect(new GainNode(audioContext, {'gain':audioVolume*volume}))
201
- .connect(audioContext.destination);
246
+ // create and connect gain node (createGain is more widley spported then GainNode construtor)
247
+ const gainNode = audioContext.createGain();
248
+ gainNode.gain.value = soundVolume*volume;
249
+ gainNode.connect(audioContext.destination);
250
+
251
+ // connect source to gain
252
+ (
253
+ window.StereoPannerNode ? // create pan node if possible
254
+ source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)}))
255
+ : source
256
+ )
257
+ .connect(gainNode);
202
258
 
203
259
  // play and return sound
204
260
  source.start();
@@ -209,6 +265,8 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
209
265
  // ZzFXMicro - Zuper Zmall Zound Zynth - v1.1.8 by Frank Force
210
266
 
211
267
  /** Generate and play a ZzFX sound
268
+ * <br>
269
+ * <br><b><a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a></b>
212
270
  * @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
213
271
  * @return {Array} - Array of audio samples
214
272
  * @memberof Audio */
@@ -265,7 +323,7 @@ function zzfxG
265
323
  1 - tremolo + tremolo*Math.sin(PI2*i/repeatTime) // tremolo
266
324
  : 1) *
267
325
  sign(s)*(abs(s)**shapeCurve) * // curve 0=square, 2=pointy
268
- volume * audioVolume * ( // envelope
326
+ volume * soundVolume * ( // envelope
269
327
  i < attack ? i/attack : // attack
270
328
  i < attack + decay ? // decay
271
329
  1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
@@ -15,8 +15,6 @@ type engineUtilities.js >> %OUTPUT_FILENAME%
15
15
  echo.>> %OUTPUT_FILENAME%
16
16
  type engineSettings.js >> %OUTPUT_FILENAME%
17
17
  echo.>> %OUTPUT_FILENAME%
18
- type engine.js >> %OUTPUT_FILENAME%
19
- echo.>> %OUTPUT_FILENAME%
20
18
  type engineObject.js >> %OUTPUT_FILENAME%
21
19
  echo.>> %OUTPUT_FILENAME%
22
20
  type engineDraw.js >> %OUTPUT_FILENAME%
@@ -33,6 +31,8 @@ type engineMedals.js >> %OUTPUT_FILENAME%
33
31
  echo.>> %OUTPUT_FILENAME%
34
32
  type engineWebGL.js >> %OUTPUT_FILENAME%
35
33
  echo.>> %OUTPUT_FILENAME%
34
+ type engine.js >> %OUTPUT_FILENAME%
35
+ echo.>> %OUTPUT_FILENAME%
36
36
 
37
37
  rem --- BUILD ENGINE RELEASE ---
38
38
 
@@ -1,9 +1,16 @@
1
+ /*
2
+ LittleJS - Debug Build
3
+ MIT License - Copyright 2021 Frank Force
4
+ */
5
+
1
6
  /**
2
- * LittleJS Medal System
3
- * <br> - Debug overlay with mouse pick
4
- * <br> - Debug primitive rendering
5
- * <br> - Save screenshots to disk
6
- * @namespace Debug
7
+ * LittleJS Debug System
8
+ * <br> - Press ~ to show debug overlay with mouse pick
9
+ * <br> - Number keys toggle debug functions
10
+ * <br> - +/- apply time scale
11
+ * <br> - Debug primitive rendering
12
+ * <br> - Save a 2d canvas as an image
13
+ * @namespace Debug
7
14
  */
8
15
 
9
16
  'use strict';
@@ -34,7 +41,7 @@ let showWatermark = 1;
34
41
  let godMode = 0;
35
42
 
36
43
  // Engine internal variables not exposed to documentation
37
- let debugPrimitives = [], debugOverlay = 0, debugPhysics = 0, debugRaycast = 0,
44
+ let debugPrimitives = [], debugOverlay = 0, debugPhysics = 0, debugRaycast = 0,
38
45
  debugParticles = 0, debugGamepads = 0, debugMedals = 0, debugTakeScreenshot, downloadLink;
39
46
 
40
47
  ///////////////////////////////////////////////////////////////////////////////
@@ -156,22 +163,25 @@ const debugUpdate = ()=>
156
163
 
157
164
  if (keyWasPressed(192)) // ~
158
165
  debugOverlay = !debugOverlay;
159
- if (keyWasPressed(49)) // 1
160
- debugPhysics = !debugPhysics, debugParticles = 0;
161
- if (keyWasPressed(50)) // 2
162
- debugParticles = !debugParticles, debugPhysics = 0;
163
- if (keyWasPressed(51)) // 3
164
- godMode = !godMode;
165
- if (keyWasPressed(53)) // 5
166
- debugTakeScreenshot = 1;
167
- //if (keyWasPressed(54)) // 6
168
- // debugToggleParticleEditor();
169
- if (keyWasPressed(55)) // 7
170
- debugGamepads = !debugGamepads;
171
- //if (keyWasPressed(56)) // 8
172
- //if (keyWasPressed(57)) // 9
173
- if (keyWasPressed(48)) // 0
174
- showWatermark = !showWatermark;
166
+ if (debugOverlay)
167
+ {
168
+ if (keyWasPressed(49)) // 1
169
+ debugPhysics = !debugPhysics, debugParticles = 0;
170
+ if (keyWasPressed(50)) // 2
171
+ debugParticles = !debugParticles, debugPhysics = 0;
172
+ if (keyWasPressed(51)) // 3
173
+ godMode = !godMode;
174
+ if (keyWasPressed(53)) // 5
175
+ debugTakeScreenshot = 1;
176
+ //if (keyWasPressed(54)) // 6
177
+ // debugToggleParticleEditor();
178
+ if (keyWasPressed(55)) // 7
179
+ debugGamepads = !debugGamepads;
180
+ //if (keyWasPressed(56)) // 8
181
+ //if (keyWasPressed(57)) // 9
182
+ if (keyWasPressed(48)) // 0
183
+ showWatermark = !showWatermark;
184
+ }
175
185
  }
176
186
 
177
187
  const debugRender = ()=>
@@ -224,12 +234,24 @@ const debugRender = ()=>
224
234
 
225
235
  if (debugOverlay)
226
236
  {
237
+ // mouse pick
238
+ let bestDistance = Infinity, bestObject;
227
239
  for (const o of engineObjects)
228
240
  {
229
- if (o.canvas)
241
+ if (o.canvas || o.destroyed)
230
242
  continue; // skip tile layers
231
243
 
232
244
  const size = o.size.copy();
245
+ if (!size.x || !size.y)
246
+ continue;
247
+
248
+ const distance = mousePos.distanceSquared(o.pos);
249
+ if (distance < bestDistance)
250
+ {
251
+ bestDistance = distance;
252
+ bestObject = o
253
+ }
254
+
233
255
  size.x = max(size.x, .2);
234
256
  size.y = max(size.y, .2);
235
257
 
@@ -244,18 +266,6 @@ const debugRender = ()=>
244
266
  drawRect(o.pos, size.scale(.8), o.parent ? new Color(1,1,1,.5) : new Color(0,0,0,.8));
245
267
  o.parent && drawLine(o.pos, o.parent.pos, .1, new Color(0,0,1,.5));
246
268
  }
247
-
248
- // mouse pick
249
- let bestDistance = Infinity, bestObject;
250
- for (const o of engineObjects)
251
- {
252
- const distance = mousePos.distanceSquared(o.pos);
253
- if (distance < bestDistance)
254
- {
255
- bestDistance = distance;
256
- bestObject = o
257
- }
258
- }
259
269
 
260
270
  if (bestObject)
261
271
  {
@@ -275,7 +285,8 @@ const debugRender = ()=>
275
285
  pos = pos.add(height), ...args);
276
286
  drawText('vel = ' + printVec2(bestObject.velocity), pos = pos.add(height), ...args);
277
287
  drawText('size = ' + printVec2(bestObject.size), pos = pos.add(height), ...args);
278
- drawText('collision = ' + getTileCollisionData(mousePos), pos = mousePos.subtract(height), ...args);
288
+ drawText('type = ' + ( bestObject.constructor.name), pos = mousePos.subtract(height), ...args);
289
+ drawText('collision = ' + getTileCollisionData(mousePos), pos = mousePos.subtract(height.scale(2)), ...args);
279
290
  mainContext = saveContext;
280
291
  }
281
292
 
@@ -361,6 +372,24 @@ const debugRender = ()=>
361
372
  //overlayContext.fillText('6: Particle Editor', x, y += h);
362
373
  overlayContext.fillStyle = debugGamepads ? '#f00' : '#fff';
363
374
  overlayContext.fillText('7: Debug Gamepads', x, y += h);
375
+
376
+ let keysPressed = '';
377
+ for(const i in inputData[0])
378
+ {
379
+ if (i && keyIsDown(i, 0))
380
+ keysPressed += i + ' ' ;
381
+ }
382
+ keysPressed && overlayContext.fillText('Keys Down: ' + keysPressed, x, y += h);
383
+
384
+ let buttonsPressed = '';
385
+ if (inputData[1])
386
+ for(const i in inputData[1])
387
+ {
388
+ if (i && keyIsDown(i, 1))
389
+ buttonsPressed += i + ' ' ;
390
+ }
391
+ buttonsPressed && overlayContext.fillText('Gamepad: ' + buttonsPressed, x, y += h);
392
+
364
393
  }
365
394
  else
366
395
  {