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
package/src/engine.js CHANGED
@@ -30,7 +30,7 @@ const engineName = 'LittleJS';
30
30
  * @type {string}
31
31
  * @default
32
32
  * @memberof Engine */
33
- const engineVersion = '1.14.11';
33
+ const engineVersion = '1.14.16';
34
34
 
35
35
  /** Frames per second to update
36
36
  * @type {number}
@@ -93,9 +93,14 @@ let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
93
93
 
94
94
  const pluginUpdateList = [], pluginRenderList = [];
95
95
 
96
+ /**
97
+ * @callback PluginCallback - Update or render function for a plugin
98
+ * @memberof Engine
99
+ */
100
+
96
101
  /** Add a new update function for a plugin
97
- * @param {Function} [updateFunction]
98
- * @param {Function} [renderFunction]
102
+ * @param {PluginCallback} [updateFunction]
103
+ * @param {PluginCallback} [renderFunction]
99
104
  * @memberof Engine */
100
105
  function engineAddPlugin(updateFunction, renderFunction)
101
106
  {
@@ -108,12 +113,22 @@ function engineAddPlugin(updateFunction, renderFunction)
108
113
  ///////////////////////////////////////////////////////////////////////////////
109
114
  // Main Engine Functions
110
115
 
116
+ /**
117
+ * @callback GameInitCallback - Called after the engine starts, can be async
118
+ * @returns {void|Promise<void>}
119
+ * @memberof Engine
120
+ */
121
+ /**
122
+ * @callback GameCallback - Update or render function for the game
123
+ * @memberof Engine
124
+ */
125
+
111
126
  /** Startup LittleJS engine with your callback functions
112
- * @param {Function|function():Promise} gameInit - Called once after the engine starts up, can be async for loading
113
- * @param {Function} gameUpdate - Called every frame before objects are updated (60fps), use for game logic
114
- * @param {Function} gameUpdatePost - Called after physics and objects are updated, even when paused, use for UI updates
115
- * @param {Function} gameRender - Called before objects are rendered, use for drawing backgrounds/world elements
116
- * @param {Function} gameRenderPost - Called after objects are rendered, use for drawing UI/overlays
127
+ * @param {GameInitCallback} gameInit - Called once after the engine starts up, can be async for loading
128
+ * @param {GameCallback} gameUpdate - Called every frame before objects are updated (60fps), use for game logic
129
+ * @param {GameCallback} gameUpdatePost - Called after physics and objects are updated, even when paused, use for UI updates
130
+ * @param {GameCallback} gameRender - Called before objects are rendered, use for drawing backgrounds/world elements
131
+ * @param {GameCallback} gameRenderPost - Called after objects are rendered, use for drawing UI/overlays
117
132
  * @param {Array<string>} [imageSources=[]] - List of image file paths to preload (e.g., ['player.png', 'tiles.png'])
118
133
  * @param {HTMLElement} [rootElement] - Root DOM element to attach canvas to, defaults to document.body
119
134
  * @example
@@ -365,6 +380,8 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
365
380
  const promises = imageSources.map((src, textureIndex)=>
366
381
  new Promise(resolve =>
367
382
  {
383
+ ASSERT(isString(src), 'imageSources must be an array of strings');
384
+
368
385
  const image = new Image;
369
386
  image.onerror = image.onload = ()=>
370
387
  {
@@ -396,7 +413,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
396
413
  promises.push(new Promise(resolve =>
397
414
  {
398
415
  let t = 0;
399
- LOG(`${engineName} Engine v${engineVersion}`);
416
+ console.log(`${engineName} Engine v${engineVersion}`);
400
417
  updateSplash();
401
418
  function updateSplash()
402
419
  {
@@ -480,10 +497,16 @@ function engineObjectsCollect(pos, size, objects=engineObjects)
480
497
  return collectedObjects;
481
498
  }
482
499
 
500
+ /**
501
+ * @callback ObjectCallbackFunction - Function that processes an object
502
+ * @param {EngineObject} uiObjects
503
+ * @memberof Engine
504
+ */
505
+
483
506
  /** Triggers a callback for each object within a given area
484
- * @param {Vector2} [pos] - Center of test area, or undefined for all objects
485
- * @param {Vector2|number} [size] - Radius of circle if float, rectangle size if Vector2
486
- * @param {Function} [callbackFunction] - Calls this function on every object that passes the test
507
+ * @param {Vector2} [pos] - Center of test area, or undefined for all objects
508
+ * @param {Vector2|number} [size] - Radius of circle if float, rectangle size if Vector2
509
+ * @param {ObjectCallbackFunction} [callbackFunction] - Calls this function on every object that passes the test
487
510
  * @param {Array<EngineObject>} [objects=engineObjects] - List of objects to check
488
511
  * @memberof Engine */
489
512
  function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
@@ -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
 
@@ -47,10 +47,16 @@ let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParti
47
47
  // Debug helper functions
48
48
 
49
49
  /** Asserts if the expression is false, does nothing in release builds
50
+ * Halts execution if the assert fails and throws an error
50
51
  * @param {boolean} assert
51
52
  * @param {...Object} [output] - error message output
52
53
  * @memberof Debug */
53
- function ASSERT(assert, ...output) { console.assert(assert, ...output); }
54
+ function ASSERT(assert, ...output)
55
+ {
56
+ if (assert) return;
57
+ console.assert(assert, ...output)
58
+ throw new Error('Assert failed!'); // halt execution
59
+ }
54
60
 
55
61
  /** Log to console if debug is enabled, does nothing in release builds
56
62
  * @param {...Object} [output] - message output
@@ -60,64 +66,72 @@ function LOG(...output) { console.log(...output); }
60
66
  /** Draw a debug rectangle in world space
61
67
  * @param {Vector2} pos
62
68
  * @param {Vector2} [size=Vector2()]
63
- * @param {string} [color]
64
- * @param {number} [time]
65
- * @param {number} [angle]
69
+ * @param {Color|string} [color]
70
+ * @param {number} [time]
71
+ * @param {number} [angle]
66
72
  * @param {boolean} [fill]
67
73
  * @memberof Debug */
68
- 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)
69
75
  {
70
76
  if (typeof size === 'number')
71
77
  size = vec2(size); // allow passing in floats
72
- ASSERT(typeof color === 'string', 'pass in css color strings');
73
- 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});
74
84
  }
75
85
 
76
86
  /** Draw a debug poly in world space
77
87
  * @param {Vector2} pos
78
88
  * @param {Array<Vector2>} points
79
- * @param {string} [color]
80
- * @param {number} [time]
81
- * @param {number} [angle]
89
+ * @param {Color|string} [color]
90
+ * @param {number} [time]
91
+ * @param {number} [angle]
82
92
  * @param {boolean} [fill]
83
93
  * @memberof Debug */
84
- 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)
85
95
  {
86
- ASSERT(typeof color === 'string', 'pass in css color strings');
87
- 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});
88
102
  }
89
103
 
90
104
  /** Draw a debug circle in world space
91
105
  * @param {Vector2} pos
92
- * @param {number} [size] - diameter
93
- * @param {string} [color]
94
- * @param {number} [time]
106
+ * @param {number} [size] - diameter
107
+ * @param {Color|string} [color]
108
+ * @param {number} [time]
95
109
  * @param {boolean} [fill]
96
110
  * @memberof Debug */
97
- function debugCircle(pos, size=0, color='#fff', time=0, fill=false)
111
+ function debugCircle(pos, size=0, color=WHITE, time=0, fill=false)
98
112
  {
99
- ASSERT(typeof color === 'string', 'pass in css color strings');
100
- 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});
101
118
  }
102
119
 
103
120
  /** Draw a debug point in world space
104
121
  * @param {Vector2} pos
105
- * @param {string} [color]
106
- * @param {number} [time]
107
- * @param {number} [angle]
122
+ * @param {Color|string} [color]
123
+ * @param {number} [time]
124
+ * @param {number} [angle]
108
125
  * @memberof Debug */
109
126
  function debugPoint(pos, color, time, angle)
110
- {
111
- ASSERT(typeof color === 'string', 'pass in css color strings');
112
- debugRect(pos, undefined, color, time, angle);
113
- }
127
+ { debugRect(pos, undefined, color, time, angle); }
114
128
 
115
129
  /** Draw a debug line in world space
116
130
  * @param {Vector2} posA
117
131
  * @param {Vector2} posB
118
- * @param {string} [color]
119
- * @param {number} [width]
120
- * @param {number} [time]
132
+ * @param {Color|string} [color]
133
+ * @param {number} [width]
134
+ * @param {number} [time]
121
135
  * @memberof Debug */
122
136
  function debugLine(posA, posB, color, width=.1, time)
123
137
  {
@@ -131,7 +145,7 @@ function debugLine(posA, posB, color, width=.1, time)
131
145
  * @param {Vector2} sizeA
132
146
  * @param {Vector2} posB
133
147
  * @param {Vector2} sizeB
134
- * @param {string} [color]
148
+ * @param {Color|string} [color]
135
149
  * @memberof Debug */
136
150
  function debugOverlap(posA, sizeA, posB, sizeB, color)
137
151
  {
@@ -147,18 +161,21 @@ function debugOverlap(posA, sizeA, posB, sizeB, color)
147
161
  }
148
162
 
149
163
  /** Draw a debug axis aligned bounding box in world space
150
- * @param {string} text
164
+ * @param {string} text
151
165
  * @param {Vector2} pos
152
- * @param {number} [size]
153
- * @param {string} [color]
154
- * @param {number} [time]
155
- * @param {number} [angle]
156
- * @param {string} [font]
166
+ * @param {number} [size]
167
+ * @param {Color|string} [color]
168
+ * @param {number} [time]
169
+ * @param {number} [angle]
170
+ * @param {string} [font]
157
171
  * @memberof Debug */
158
- 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')
159
173
  {
160
- ASSERT(typeof color === 'string', 'pass in css color strings');
161
- 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});
162
179
  }
163
180
 
164
181
  /** Clear all debug primitives in the list
@@ -209,17 +226,30 @@ function debugSaveDataURL(dataURL, filename)
209
226
  downloadLink.click();
210
227
  }
211
228
 
212
- /** 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
213
232
  * @memberof Debug */
214
233
  function debugShowErrors()
215
234
  {
216
235
  const showError = (message)=>
217
236
  {
218
237
  // replace entire page with error message
219
- document.body.style.display = '';
220
- document.body.style.backgroundColor = '#111';
221
- 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;
222
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
+ };
223
253
  onunhandledrejection = (event)=>
224
254
  showError(event.reason.stack || event.reason);
225
255
  onerror = (message, source, lineno, colno)=>
@@ -387,7 +417,7 @@ function debugRender()
387
417
  p.fill && overlayContext.fill();
388
418
  overlayContext.stroke();
389
419
  }
390
- 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))
391
421
  {
392
422
  // point
393
423
  overlayContext.fillRect(-pointSize/2, -1, pointSize, 3);
@@ -414,7 +444,7 @@ function debugRender()
414
444
  });
415
445
 
416
446
  // remove expired primitives
417
- debugPrimitives = debugPrimitives.filter(r=>r.time<0);
447
+ debugPrimitives = debugPrimitives.filter(r=>r.timer<0);
418
448
  }
419
449
 
420
450
  if (debugObject)
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;
@@ -178,6 +178,7 @@ export
178
178
  isColor,
179
179
  isVector2,
180
180
  isNumber,
181
+ isString,
181
182
 
182
183
  // Default Colors
183
184
  WHITE,
@@ -313,7 +314,7 @@ export
313
314
  tileCollisionGetData,
314
315
  tileCollisionTest,
315
316
  tileCollisionRaycast,
316
- tileCollisionLoad,
317
+ tileLayersLoad,
317
318
  TileLayerData,
318
319
  CanvasLayer,
319
320
  TileLayer,