littlejsengine 1.14.10 → 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 (76) hide show
  1. package/dist/littlejs.d.ts +264 -120
  2. package/dist/littlejs.esm.js +603 -288
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +600 -287
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +521 -232
  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 +59 -51
  15. package/examples/module/game.js +3 -3
  16. package/examples/platformer/gameCharacter.js +6 -2
  17. package/examples/platformer/gameEffects.js +4 -4
  18. package/examples/platformer/gameLevel.js +1 -1
  19. package/examples/platformer/gameObjects.js +11 -9
  20. package/examples/puzzle/game.js +1 -1
  21. package/examples/shorts/animation.js +1 -1
  22. package/examples/shorts/base.html +1 -1
  23. package/examples/shorts/blending.js +8 -14
  24. package/examples/shorts/box2d.js +7 -3
  25. package/examples/shorts/box2dCar.js +2 -1
  26. package/examples/shorts/empty.js +30 -0
  27. package/examples/shorts/helloWorld.js +1 -1
  28. package/examples/shorts/hillGlideGame.js +10 -4
  29. package/examples/shorts/landerGame.js +30 -3
  30. package/examples/shorts/medals.js +4 -4
  31. package/examples/shorts/music.js +29 -55
  32. package/examples/shorts/musicPlayer.js +134 -0
  33. package/examples/shorts/nineSlice.js +34 -15
  34. package/examples/shorts/parallax.js +4 -3
  35. package/examples/shorts/particles.js +16 -16
  36. package/examples/shorts/piano.js +15 -21
  37. package/examples/shorts/pongGame.js +6 -4
  38. package/examples/shorts/raycasting.js +13 -5
  39. package/examples/shorts/sequencer.js +122 -0
  40. package/examples/shorts/shapes.js +7 -4
  41. package/examples/shorts/slidingPuzzle.js +16 -6
  42. package/examples/shorts/sound.js +18 -9
  43. package/examples/shorts/spaceGame.js +12 -8
  44. package/examples/shorts/spriteAtlas.js +7 -7
  45. package/examples/shorts/starfield.js +1 -1
  46. package/examples/shorts/systemFont.js +2 -2
  47. package/examples/shorts/texture.js +2 -2
  48. package/examples/shorts/tileLayer.js +10 -10
  49. package/examples/shorts/tiltedView.js +14 -3
  50. package/examples/shorts/timers.js +10 -0
  51. package/examples/shorts/topDown.js +4 -1
  52. package/examples/shorts/uiSystem.js +8 -5
  53. package/examples/starter/game.js +3 -3
  54. package/examples/starter/index.html +2 -2
  55. package/examples/typescript/game.js +3 -3
  56. package/examples/typescript/game.ts +3 -3
  57. package/examples/uiSystem/game.js +6 -6
  58. package/package.json +4 -2
  59. package/plugins/box2d.js +18 -3
  60. package/plugins/newgrounds.js +11 -9
  61. package/plugins/postProcess.js +5 -2
  62. package/plugins/uiSystem.js +120 -34
  63. package/plugins/zzfxm.js +5 -1
  64. package/reference.md +1 -3
  65. package/src/engine.js +52 -21
  66. package/src/engineAudio.js +31 -14
  67. package/src/engineDebug.js +80 -55
  68. package/src/engineDraw.js +60 -31
  69. package/src/engineExport.js +3 -1
  70. package/src/engineMedals.js +10 -2
  71. package/src/engineObject.js +14 -10
  72. package/src/engineParticles.js +59 -46
  73. package/src/engineRelease.js +1 -0
  74. package/src/engineSettings.js +7 -7
  75. package/src/engineTileLayer.js +49 -18
  76. package/src/engineUtilities.js +79 -34
@@ -47,6 +47,7 @@ function audioInit()
47
47
  * Sound Object - Stores a sound for later use and can be played positionally
48
48
  *
49
49
  * <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
50
+ * @memberof Audio
50
51
  * @example
51
52
  * // create a sound
52
53
  * const sound_example = new Sound([.5,.5]);
@@ -141,12 +142,12 @@ class Sound
141
142
 
142
143
  /** Play the sound as a musical note with a semitone offset
143
144
  * This can be used to play music with chromatic scales
144
- * @param {number} semitoneOffset - How many semitones to offset pitch
145
+ * @param {number} [semitoneOffset=0] - How many semitones to offset pitch
145
146
  * @param {Vector2} [pos] - World space position to play the sound if any
146
147
  * @param {number} [volume=1] - How much to scale volume by
147
148
  * @return {SoundInstance} - The audio source node
148
149
  */
149
- playNote(semitoneOffset, pos, volume)
150
+ playNote(semitoneOffset=0, pos, volume)
150
151
  {
151
152
  const pitch = getNoteFrequency(semitoneOffset, 1);
152
153
  return this.play(pos, volume, pitch, 0);
@@ -169,6 +170,8 @@ class Sound
169
170
  /**
170
171
  * Sound Wave Object - Stores a wave sound for later use and can be played positionally
171
172
  * - this can be used to play wave, mp3, and ogg files
173
+ * @extends Sound
174
+ * @memberof Audio
172
175
  * @example
173
176
  * // create a sound
174
177
  * const sound_example = new SoundWave('sound.mp3');
@@ -178,22 +181,29 @@ class Sound
178
181
  */
179
182
  class SoundWave extends Sound
180
183
  {
184
+ /**
185
+ * @callback SoundLoadCallback - Function called when sound is loaded
186
+ * @param {SoundWave} sound
187
+ * @memberof Audio
188
+ */
189
+
181
190
  /** Create a sound object and cache the wave file for later use
182
191
  * @param {string} filename - Filename of audio file to load
183
192
  * @param {number} [randomness] - How much to randomize frequency each time sound plays
184
193
  * @param {number} [range=soundDefaultRange] - World space max range of sound
185
194
  * @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
186
- * @param {Function} [onloadCallback] - callback function to call when sound is loaded
195
+ * @param {SoundLoadCallback} [onloadCallback] - callback function to call when sound is loaded
187
196
  */
188
197
  constructor(filename, randomness=0, range, taper, onloadCallback)
189
198
  {
190
199
  super(undefined, range, taper);
191
200
  if (!soundEnable || headlessMode) return;
201
+ ASSERT(!filename || isString(filename), 'filename must be a string');
192
202
 
193
- /** @property {Function} - callback function to call when sound is loaded */
203
+ /** @property {SoundLoadCallback} - callback function to call when sound is loaded */
194
204
  this.onloadCallback = onloadCallback;
195
205
  this.randomness = randomness;
196
- this.loadSound(filename);
206
+ filename && this.loadSound(filename);
197
207
  }
198
208
 
199
209
  /** Loads a sound from a URL and decodes it into sample data. Must be used with await!
@@ -202,6 +212,8 @@ class SoundWave extends Sound
202
212
  async loadSound(filename)
203
213
  {
204
214
  const response = await fetch(filename);
215
+ if (!response.ok)
216
+ throw new Error(`Failed to load sound from ${filename}: ${response.status} ${response.statusText}`);
205
217
  const arrayBuffer = await response.arrayBuffer();
206
218
  const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
207
219
 
@@ -237,7 +249,7 @@ class SoundWave extends Sound
237
249
  this.sampleChannels = sampleChannels;
238
250
  this.loadedPercent = 1;
239
251
  if (this.onloadCallback)
240
- this.onloadCallback();
252
+ this.onloadCallback(this);
241
253
  }
242
254
  }
243
255
 
@@ -246,6 +258,7 @@ class SoundWave extends Sound
246
258
  /**
247
259
  * Sound Instance - Wraps an AudioBufferSourceNode for individual sound control
248
260
  * Represents a single playing instance of a sound with pause/resume capabilities
261
+ * @memberof Audio
249
262
  * @example
250
263
  * // Play a sound and get an instance for control
251
264
  * const jumpSound = new Sound([.5,.5,220]);
@@ -445,6 +458,12 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
445
458
 
446
459
  ///////////////////////////////////////////////////////////////////////////////
447
460
 
461
+ /**
462
+ * @callback AudioEndedCallback - Function called when a sound ends
463
+ * @param {AudioBufferSourceNode} source
464
+ * @memberof Audio
465
+ */
466
+
448
467
  /** Play cached audio samples with given settings
449
468
  * @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
450
469
  * @param {number} [volume] - How much to scale volume by
@@ -454,7 +473,7 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
454
473
  * @param {number} [sampleRate=44100] - Sample rate for the sound
455
474
  * @param {GainNode} [gainNode] - Optional gain node for volume control while playing
456
475
  * @param {number} [offset] - Offset in seconds to start playback from
457
- * @param {Function} [onended] - Callback for when the sound ends
476
+ * @param {AudioEndedCallback} [onended] - Callback for when the sound ends
458
477
  * @return {AudioBufferSourceNode} - The audio node of the sound played
459
478
  * @memberof Audio */
460
479
  function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=audioDefaultSampleRate, gainNode, offset=0, onended)
@@ -486,16 +505,14 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
486
505
  if (onended)
487
506
  source.addEventListener('ended', ()=> onended(source));
488
507
 
508
+ const startOffset = offset * rate;
489
509
  if (!audioIsRunning())
490
510
  {
491
- // fix stalled audio, this sound won't be able to play
492
- audioContext.resume();
493
- return;
511
+ // fix stalled audio and start
512
+ audioContext.resume().then(()=>source.start(0, startOffset));
494
513
  }
495
-
496
- // play and return sound
497
- const startOffset = offset * rate;
498
- source.start(0, startOffset);
514
+ else
515
+ source.start(0, startOffset);
499
516
  return source;
500
517
  }
501
518
 
@@ -16,12 +16,6 @@
16
16
  * @memberof Debug */
17
17
  const debug = true;
18
18
 
19
- /** True if asserts are enabled
20
- * @type {boolean}
21
- * @default
22
- * @memberof Debug */
23
- const enableAsserts = true;
24
-
25
19
  /** Size to render debug points by default
26
20
  * @type {number}
27
21
  * @default
@@ -52,77 +46,92 @@ let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParti
52
46
  ///////////////////////////////////////////////////////////////////////////////
53
47
  // Debug helper functions
54
48
 
55
- /** Asserts if the expression is false, does not do anything in release builds
49
+ /** Asserts if the expression is false, does nothing in release builds
50
+ * Halts execution if the assert fails and throws an error
56
51
  * @param {boolean} assert
57
52
  * @param {...Object} [output] - error message output
58
53
  * @memberof Debug */
59
54
  function ASSERT(assert, ...output)
60
55
  {
61
- if (enableAsserts)
62
- console.assert(assert, ...output);
56
+ if (assert) return;
57
+ console.assert(assert, ...output)
58
+ throw new Error('Assert failed!'); // halt execution
63
59
  }
64
60
 
61
+ /** Log to console if debug is enabled, does nothing in release builds
62
+ * @param {...Object} [output] - message output
63
+ * @memberof Debug */
64
+ function LOG(...output) { console.log(...output); }
65
+
65
66
  /** Draw a debug rectangle in world space
66
67
  * @param {Vector2} pos
67
68
  * @param {Vector2} [size=Vector2()]
68
- * @param {string} [color]
69
- * @param {number} [time]
70
- * @param {number} [angle]
69
+ * @param {Color|string} [color]
70
+ * @param {number} [time]
71
+ * @param {number} [angle]
71
72
  * @param {boolean} [fill]
72
73
  * @memberof Debug */
73
- function debugRect(pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)
74
+ function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false)
74
75
  {
75
76
  if (typeof size === 'number')
76
77
  size = vec2(size); // allow passing in floats
77
- ASSERT(typeof color === 'string', 'pass in css color strings');
78
- debugPrimitives.push({pos, size, color, time:new Timer(time), angle, fill});
78
+ if (isColor(color))
79
+ color = color.toString();
80
+ pos = pos.copy();
81
+ size = size.copy();
82
+ const timer = new Timer(time);
83
+ debugPrimitives.push({pos:pos.copy(), size:size.copy(), color, timer, angle, fill});
79
84
  }
80
85
 
81
86
  /** Draw a debug poly in world space
82
87
  * @param {Vector2} pos
83
88
  * @param {Array<Vector2>} points
84
- * @param {string} [color]
85
- * @param {number} [time]
86
- * @param {number} [angle]
89
+ * @param {Color|string} [color]
90
+ * @param {number} [time]
91
+ * @param {number} [angle]
87
92
  * @param {boolean} [fill]
88
93
  * @memberof Debug */
89
- function debugPoly(pos, points, color='#fff', time=0, angle=0, fill=false)
94
+ function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false)
90
95
  {
91
- ASSERT(typeof color === 'string', 'pass in css color strings');
92
- debugPrimitives.push({pos, points, color, time:new Timer(time), angle, fill});
96
+ if (isColor(color))
97
+ color = color.toString();
98
+ pos = pos.copy();
99
+ points = points.map(p=>p.copy());
100
+ const timer = new Timer(time);
101
+ debugPrimitives.push({pos, points, color, timer, angle, fill});
93
102
  }
94
103
 
95
104
  /** Draw a debug circle in world space
96
105
  * @param {Vector2} pos
97
- * @param {number} [size] - diameter
98
- * @param {string} [color]
99
- * @param {number} [time]
106
+ * @param {number} [size] - diameter
107
+ * @param {Color|string} [color]
108
+ * @param {number} [time]
100
109
  * @param {boolean} [fill]
101
110
  * @memberof Debug */
102
- function debugCircle(pos, size=0, color='#fff', time=0, fill=false)
111
+ function debugCircle(pos, size=0, color=WHITE, time=0, fill=false)
103
112
  {
104
- ASSERT(typeof color === 'string', 'pass in css color strings');
105
- debugPrimitives.push({pos, size, color, time:new Timer(time), angle:0, fill});
113
+ if (isColor(color))
114
+ color = color.toString();
115
+ pos = pos.copy();
116
+ const timer = new Timer(time);
117
+ debugPrimitives.push({pos, size, color, timer, angle:0, fill});
106
118
  }
107
119
 
108
120
  /** Draw a debug point in world space
109
121
  * @param {Vector2} pos
110
- * @param {string} [color]
111
- * @param {number} [time]
112
- * @param {number} [angle]
122
+ * @param {Color|string} [color]
123
+ * @param {number} [time]
124
+ * @param {number} [angle]
113
125
  * @memberof Debug */
114
126
  function debugPoint(pos, color, time, angle)
115
- {
116
- ASSERT(typeof color === 'string', 'pass in css color strings');
117
- debugRect(pos, undefined, color, time, angle);
118
- }
127
+ { debugRect(pos, undefined, color, time, angle); }
119
128
 
120
129
  /** Draw a debug line in world space
121
130
  * @param {Vector2} posA
122
131
  * @param {Vector2} posB
123
- * @param {string} [color]
124
- * @param {number} [width]
125
- * @param {number} [time]
132
+ * @param {Color|string} [color]
133
+ * @param {number} [width]
134
+ * @param {number} [time]
126
135
  * @memberof Debug */
127
136
  function debugLine(posA, posB, color, width=.1, time)
128
137
  {
@@ -136,7 +145,7 @@ function debugLine(posA, posB, color, width=.1, time)
136
145
  * @param {Vector2} sizeA
137
146
  * @param {Vector2} posB
138
147
  * @param {Vector2} sizeB
139
- * @param {string} [color]
148
+ * @param {Color|string} [color]
140
149
  * @memberof Debug */
141
150
  function debugOverlap(posA, sizeA, posB, sizeB, color)
142
151
  {
@@ -152,18 +161,21 @@ function debugOverlap(posA, sizeA, posB, sizeB, color)
152
161
  }
153
162
 
154
163
  /** Draw a debug axis aligned bounding box in world space
155
- * @param {string} text
164
+ * @param {string} text
156
165
  * @param {Vector2} pos
157
- * @param {number} [size]
158
- * @param {string} [color]
159
- * @param {number} [time]
160
- * @param {number} [angle]
161
- * @param {string} [font]
166
+ * @param {number} [size]
167
+ * @param {Color|string} [color]
168
+ * @param {number} [time]
169
+ * @param {number} [angle]
170
+ * @param {string} [font]
162
171
  * @memberof Debug */
163
- function debugText(text, pos, size=1, color='#fff', time=0, angle=0, font='monospace')
172
+ function debugText(text, pos, size=1, color=WHITE, time=0, angle=0, font='monospace')
164
173
  {
165
- ASSERT(typeof color === 'string', 'pass in css color strings');
166
- debugPrimitives.push({text, pos, size, color, time:new Timer(time), angle, font});
174
+ if (isColor(color))
175
+ color = color.toString();
176
+ pos = pos.copy();
177
+ const timer = new Timer(time);
178
+ debugPrimitives.push({text, pos, size, color, timer, angle, font});
167
179
  }
168
180
 
169
181
  /** Clear all debug primitives in the list
@@ -214,17 +226,30 @@ function debugSaveDataURL(dataURL, filename)
214
226
  downloadLink.click();
215
227
  }
216
228
 
217
- /** Show error as full page of red text
229
+ /** Breaks on all asserts/errors, hides the canvas, and shows message in plain text
230
+ * This is a good function to call at the start of your game to catch all errors
231
+ * In release builds this function has no effect
218
232
  * @memberof Debug */
219
233
  function debugShowErrors()
220
234
  {
221
235
  const showError = (message)=>
222
236
  {
223
237
  // replace entire page with error message
224
- document.body.style.display = '';
225
- document.body.style.backgroundColor = '#111';
226
- document.body.innerHTML = `<pre style=color:#f00;font-size:50px;white-space:pre-wrap>` + message;
238
+ document.body.style = 'background-color:#111;margin:8px';
239
+ document.body.innerHTML = `<pre style=color:#f00;font-size:28px;white-space:pre-wrap>` + message;
227
240
  }
241
+
242
+ const originalAssert = console.assert;
243
+ console.assert = (assertion, ...output)=>
244
+ {
245
+ originalAssert(assertion, ...output);
246
+ if (!assertion)
247
+ {
248
+ const message = output.join(' ');
249
+ const stack = new Error().stack;
250
+ throw 'Assertion failed!\n' + message + '\n' + stack;
251
+ }
252
+ };
228
253
  onunhandledrejection = (event)=>
229
254
  showError(event.reason.stack || event.reason);
230
255
  onerror = (message, source, lineno, colno)=>
@@ -392,7 +417,7 @@ function debugRender()
392
417
  p.fill && overlayContext.fill();
393
418
  overlayContext.stroke();
394
419
  }
395
- else if (p.size === 0 || p.size.x === 0 && p.size.y === 0)
420
+ else if (p.size === 0 || (p.size.x === 0 && p.size.y === 0))
396
421
  {
397
422
  // point
398
423
  overlayContext.fillRect(-pointSize/2, -1, pointSize, 3);
@@ -419,7 +444,7 @@ function debugRender()
419
444
  });
420
445
 
421
446
  // remove expired primitives
422
- debugPrimitives = debugPrimitives.filter(r=>r.time<0);
447
+ debugPrimitives = debugPrimitives.filter(r=>r.timer<0);
423
448
  }
424
449
 
425
450
  if (debugObject)
@@ -554,7 +579,7 @@ function debugVideoCaptureStart()
554
579
  }
555
580
 
556
581
  // start recording
557
- console.log('Video capture started.');
582
+ LOG('Video capture started.');
558
583
  debugVideoCapture.start();
559
584
  debugVideoCaptureTimer = new Timer(0);
560
585
 
@@ -581,7 +606,7 @@ function debugVideoCaptureStop()
581
606
  return; // not recording
582
607
 
583
608
  // stop recording
584
- console.log(`Video capture ended. ${debugVideoCaptureTimer.get().toFixed(2)} seconds recorded.`);
609
+ LOG(`Video capture ended. ${debugVideoCaptureTimer.get().toFixed(2)} seconds recorded.`);
585
610
  debugVideoCapture.stop();
586
611
  debugVideoCapture = 0;
587
612
  debugVideoCaptureIcon.style.display = 'none';
package/src/engineDraw.js CHANGED
@@ -83,7 +83,7 @@ let drawCount;
83
83
  * Create a tile info object using a grid based system
84
84
  * - This can take vecs or floats for easier use and conversion
85
85
  * - If an index is passed in, the tile size and index will determine the position
86
- * @param {Vector2|number} [pos=0] - Index of tile in sheet
86
+ * @param {Vector2|number} [pos=0] - Position of the tile in pixels, or tile index
87
87
  * @param {Vector2|number} [size=tileSizeDefault] - Size of tile in pixels
88
88
  * @param {number} [textureIndex] - Texture index to use
89
89
  * @param {number} [padding] - How many pixels padding around tiles
@@ -128,6 +128,7 @@ function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
128
128
 
129
129
  /**
130
130
  * Tile Info - Stores info about how to draw a tile
131
+ * @memberof Draw
131
132
  */
132
133
  class TileInfo
133
134
  {
@@ -188,7 +189,10 @@ class TileInfo
188
189
  }
189
190
  }
190
191
 
191
- /** Texture Info - Stores info about each texture */
192
+ /**
193
+ * Tile Info - Stores info about each texture
194
+ * @memberof Draw
195
+ */
192
196
  class TextureInfo
193
197
  {
194
198
  /**
@@ -234,10 +238,11 @@ class TextureInfo
234
238
  function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
235
239
  angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
236
240
  {
237
- ASSERT(isVector2(pos), 'drawTile pos should be a vec2');
238
- ASSERT(isVector2(size), 'drawTile size should be a vec2');
239
- ASSERT(isColor(color) && (!additiveColor || isColor(additiveColor)), 'drawTile color is invalid');
240
- ASSERT(isNumber(angle), 'drawTile angle should be a number');
241
+ ASSERT(isVector2(pos), 'pos must be a vec2');
242
+ ASSERT(isVector2(size), 'size must be a vec2');
243
+ ASSERT(isColor(color), 'color is invalid');
244
+ ASSERT(isNumber(angle), 'angle must be a number');
245
+ ASSERT(!additiveColor || isColor(additiveColor), 'additiveColor must be a color');
241
246
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
242
247
 
243
248
  const textureInfo = tileInfo && tileInfo.textureInfo;
@@ -332,10 +337,10 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
332
337
  * @memberof Draw */
333
338
  function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0, useWebGL=glEnable, screenSpace=false, context)
334
339
  {
335
- ASSERT(isVector2(pos), 'drawRectGradient pos should be a vec2');
336
- ASSERT(isVector2(size), 'drawRectGradient size should be a vec2');
337
- ASSERT(isColor(colorTop) && isColor(colorBottom), 'drawRectGradient color is invalid');
338
- ASSERT(isNumber(angle), 'drawRectGradient angle should be a number');
340
+ ASSERT(isVector2(pos), 'pos must be a vec2');
341
+ ASSERT(isVector2(size), 'size must be a vec2');
342
+ ASSERT(isColor(colorTop) && isColor(colorBottom), 'color is invalid');
343
+ ASSERT(isNumber(angle), 'angle must be a number');
339
344
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
340
345
  if (useWebGL)
341
346
  {
@@ -393,11 +398,11 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
393
398
  * @memberof Draw */
394
399
  function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace, context)
395
400
  {
396
- ASSERT(Array.isArray(points), 'drawLineList points should be an array');
397
- ASSERT(isNumber(width), 'drawLineList width should be a number');
398
- ASSERT(isColor(color), 'drawLineList color is invalid');
399
- ASSERT(isVector2(pos), 'drawLineList pos should be a vec2');
400
- ASSERT(isNumber(angle), 'drawLineList angle should be a number');
401
+ ASSERT(Array.isArray(points), 'points must be an array');
402
+ ASSERT(isNumber(width), 'width must be a number');
403
+ ASSERT(isColor(color), 'color is invalid');
404
+ ASSERT(isVector2(pos), 'pos must be a vec2');
405
+ ASSERT(isNumber(angle), 'angle must be a number');
401
406
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
402
407
  if (useWebGL)
403
408
  {
@@ -468,8 +473,8 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
468
473
  * @memberof Draw */
469
474
  function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, lineColor=BLACK, angle=0, useWebGL=glEnable, screenSpace=false, context)
470
475
  {
471
- ASSERT(isVector2(size), 'drawRegularPoly size should be a vec2');
472
- ASSERT(isNumber(sides), 'drawRegularPoly sides should be a number');
476
+ ASSERT(isVector2(size), 'size must be a vec2');
477
+ ASSERT(isNumber(sides), 'sides must be a number');
473
478
 
474
479
  // build regular polygon points
475
480
  const points = [];
@@ -495,12 +500,13 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
495
500
  * @memberof Draw */
496
501
  function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace=false, context=undefined)
497
502
  {
498
- ASSERT(isVector2(pos), 'drawPoly pos should be a vec2');
499
- ASSERT(Array.isArray(points), 'drawPoly points should be an array');
500
- ASSERT(isColor(color) && isColor(lineColor), 'drawPoly color is invalid');
501
- ASSERT(isNumber(lineWidth), 'drawPoly lineWidth should be a number');
502
- ASSERT(isNumber(angle), 'drawPoly angle should be a number');
503
+ ASSERT(isVector2(pos), 'pos must be a vec2');
504
+ ASSERT(Array.isArray(points), 'points must be an array');
505
+ ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
506
+ ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
507
+ ASSERT(isNumber(angle), 'angle must be a number');
503
508
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
509
+
504
510
  if (useWebGL)
505
511
  {
506
512
  let scale = 1;
@@ -547,13 +553,14 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
547
553
  * @memberof Draw */
548
554
  function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
549
555
  {
550
- ASSERT(isVector2(pos), 'drawEllipse pos should be a vec2');
551
- ASSERT(isVector2(size), 'drawEllipse size should be a vec2');
552
- ASSERT(isColor(color) && isColor(lineColor), 'drawEllipse color is invalid');
553
- ASSERT(isNumber(angle), 'drawEllipse angle should be a number');
554
- ASSERT(isNumber(lineWidth), 'drawEllipse lineWidth should be a number');
555
- ASSERT(lineWidth >= 0 && lineWidth < size.x && lineWidth < size.y, 'drawEllipse invalid lineWidth');
556
+ ASSERT(isVector2(pos), 'pos must be a vec2');
557
+ ASSERT(isVector2(size), 'size must be a vec2');
558
+ ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
559
+ ASSERT(isNumber(angle), 'angle must be a number');
560
+ ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
561
+ ASSERT(lineWidth >= 0 && lineWidth < size.x && lineWidth < size.y, 'invalid lineWidth');
556
562
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
563
+
557
564
  if (useWebGL)
558
565
  {
559
566
  // draw as a regular polygon
@@ -590,21 +597,32 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
590
597
  * @memberof Draw */
591
598
  function drawCircle(pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
592
599
  {
593
- ASSERT(isNumber(size), 'drawCircle size should be a number');
600
+ ASSERT(isNumber(size), 'size must be a number');
594
601
  drawEllipse(pos, vec2(size), color, 0, lineWidth, lineColor, useWebGL, screenSpace, context);
595
602
  }
596
603
 
604
+ /**
605
+ * @callback Canvas2DDrawFunction - A function that draws to a 2D canvas context
606
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
607
+ * @memberof Draw
608
+ */
609
+
597
610
  /** Draw directly to a 2d canvas context in world space
598
611
  * @param {Vector2} pos
599
612
  * @param {Vector2} size
600
613
  * @param {number} angle
601
614
  * @param {boolean} [mirror]
602
- * @param {Function} [drawFunction]
615
+ * @param {Canvas2DDrawFunction} [drawFunction]
603
616
  * @param {boolean} [screenSpace=false]
604
617
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
605
618
  * @memberof Draw */
606
619
  function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpace=false, context=drawContext)
607
620
  {
621
+ ASSERT(isVector2(pos), 'pos must be a vec2');
622
+ ASSERT(isVector2(size), 'size must be a vec2');
623
+ ASSERT(isNumber(angle), 'angle must be a number');
624
+ ASSERT(typeof drawFunction === 'function', 'drawFunction must be a function');
625
+
608
626
  if (!screenSpace)
609
627
  {
610
628
  // transform from world space to screen space
@@ -672,6 +690,16 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
672
690
  * @memberof Draw */
673
691
  function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, maxWidth, context=overlayContext)
674
692
  {
693
+ ASSERT(isString(text), 'text must be a string');
694
+ ASSERT(isVector2(pos), 'pos must be a vec2');
695
+ ASSERT(isNumber(size), 'size must be a number');
696
+ ASSERT(isColor(color), 'color must be a color');
697
+ ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
698
+ ASSERT(isColor(lineColor), 'lineColor must be a color');
699
+ ASSERT(isColor(lineColor), 'lineColor must be a color');
700
+ ASSERT(['left','center','right'].includes(textAlign), 'align must be left, center, or right');
701
+ ASSERT(isString(font), 'font must be a string');
702
+
675
703
  context.fillStyle = color.toString();
676
704
  context.strokeStyle = lineColor.toString();
677
705
  context.lineWidth = lineWidth;
@@ -873,6 +901,7 @@ let engineFontImage;
873
901
  * - 96 characters (from space to tilde) are stored in an image
874
902
  * - Uses a default 8x8 font if none is supplied
875
903
  * - You can also use fonts from the main tile sheet
904
+ * @memberof Draw
876
905
  * @example
877
906
  * // use built in font
878
907
  * const font = new FontImage;
@@ -929,7 +958,7 @@ class FontImage
929
958
  * @param {boolean} [center]
930
959
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
931
960
  */
932
- drawTextScreen(text, pos, scale=4, center, context=overlayContext)
961
+ drawTextScreen(text, pos, scale=4, center=true, context=overlayContext)
933
962
  {
934
963
  context.save();
935
964
  const size = this.tileSize;
@@ -31,6 +31,7 @@ export
31
31
 
32
32
  // Debug
33
33
  ASSERT,
34
+ LOG,
34
35
  debugRect,
35
36
  debugPoly,
36
37
  debugCircle,
@@ -177,6 +178,7 @@ export
177
178
  isColor,
178
179
  isVector2,
179
180
  isNumber,
181
+ isString,
180
182
 
181
183
  // Default Colors
182
184
  WHITE,
@@ -312,7 +314,7 @@ export
312
314
  tileCollisionGetData,
313
315
  tileCollisionTest,
314
316
  tileCollisionRaycast,
315
- tileCollisionLoad,
317
+ tileLayersLoad,
316
318
  TileLayerData,
317
319
  CanvasLayer,
318
320
  TileLayer,
@@ -59,8 +59,14 @@ function medalsInit(saveName)
59
59
  }
60
60
  }
61
61
 
62
+ /**
63
+ * @callback MedalCallbackFunction - Function that processes a medal
64
+ * @param {Medal} medal
65
+ * @memberof Medals
66
+ */
67
+
62
68
  /** Calls a function for each medal
63
- * @param {Function} callback
69
+ * @param {MedalCallbackFunction} callback
64
70
  * @memberof Medals */
65
71
  function medalsForEach(callback)
66
72
  { Object.values(medals).forEach(medal=>callback(medal)); }
@@ -69,6 +75,7 @@ function medalsForEach(callback)
69
75
 
70
76
  /**
71
77
  * Medal - Tracks an unlockable medal
78
+ * @memberof Medals
72
79
  * @example
73
80
  * // create a medal
74
81
  * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
@@ -137,11 +144,12 @@ class Medal
137
144
  const height = medalDisplaySize.y;
138
145
  const x = overlayCanvas.width - width;
139
146
  const y = -height*hidePercent;
147
+ const backgroundColor = hsl(0,0,.9);
140
148
 
141
149
  // draw containing rect and clip to that region
142
150
  context.save();
143
151
  context.beginPath();
144
- context.fillStyle = new Color(.9,.9,.9).toString();
152
+ context.fillStyle = backgroundColor.toString();
145
153
  context.strokeStyle = BLACK.toString();
146
154
  context.lineWidth = 3;
147
155
  context.rect(x, y, width, height);