littlejsengine 1.14.11 → 1.14.19

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 (80) hide show
  1. package/dist/littlejs.d.ts +303 -137
  2. package/dist/littlejs.esm.js +936 -463
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +934 -462
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +859 -417
  7. package/examples/box2d/game.js +3 -2
  8. package/examples/box2d/gameObjects.js +2 -2
  9. package/examples/box2d/tiles.png +0 -0
  10. package/examples/breakout/game.js +5 -5
  11. package/examples/breakout/gameObjects.js +2 -2
  12. package/examples/breakoutTutorial/README.md +32 -32
  13. package/examples/breakoutTutorial/game.js +1 -1
  14. package/examples/electron/game.js +4 -4
  15. package/examples/electron/index.html +2 -2
  16. package/examples/electron/package.json +1 -8
  17. package/examples/index.html +67 -56
  18. package/examples/module/game.js +4 -4
  19. package/examples/platformer/gameEffects.js +5 -5
  20. package/examples/platformer/gameLevel.js +1 -1
  21. package/examples/platformer/gameObjects.js +4 -4
  22. package/examples/puzzle/game.js +1 -1
  23. package/examples/shorts/animation.js +1 -1
  24. package/examples/shorts/base.html +1 -1
  25. package/examples/shorts/blending.js +8 -14
  26. package/examples/shorts/box2d.js +7 -3
  27. package/examples/shorts/box2dCar.js +2 -1
  28. package/examples/shorts/empty.js +30 -0
  29. package/examples/shorts/helloWorld.js +1 -1
  30. package/examples/shorts/hillGlideGame.js +10 -4
  31. package/examples/shorts/landerGame.js +11 -8
  32. package/examples/shorts/medals.js +4 -4
  33. package/examples/shorts/music.js +29 -56
  34. package/examples/shorts/musicPlayer.js +135 -0
  35. package/examples/shorts/nineSlice.js +34 -15
  36. package/examples/shorts/parallax.js +5 -4
  37. package/examples/shorts/particles.js +15 -15
  38. package/examples/shorts/piano.js +15 -21
  39. package/examples/shorts/pongGame.js +6 -4
  40. package/examples/shorts/raycasting.js +9 -4
  41. package/examples/shorts/sequencer.js +122 -0
  42. package/examples/shorts/shader.js +29 -0
  43. package/examples/shorts/shapes.js +7 -4
  44. package/examples/shorts/slidingPuzzle.js +4 -2
  45. package/examples/shorts/sound.js +18 -9
  46. package/examples/shorts/spaceGame.js +12 -8
  47. package/examples/shorts/spriteAtlas.js +7 -7
  48. package/examples/shorts/starfield.js +1 -1
  49. package/examples/shorts/systemFont.js +2 -2
  50. package/examples/shorts/texture.js +2 -2
  51. package/examples/shorts/tileLayer.js +10 -10
  52. package/examples/shorts/tiltedView.js +24 -5
  53. package/examples/shorts/timers.js +10 -0
  54. package/examples/shorts/topDown.js +4 -1
  55. package/examples/shorts/uiSystem.js +8 -5
  56. package/examples/starter/game.js +4 -4
  57. package/examples/starter/index.html +2 -2
  58. package/examples/style.css +1 -0
  59. package/examples/typescript/game.js +4 -4
  60. package/examples/typescript/game.ts +4 -4
  61. package/examples/uiSystem/game.js +4 -3
  62. package/package.json +4 -2
  63. package/plugins/box2d.js +17 -2
  64. package/plugins/newgrounds.js +7 -5
  65. package/plugins/postProcess.js +76 -53
  66. package/plugins/uiSystem.js +192 -53
  67. package/plugins/zzfxm.js +5 -1
  68. package/reference.md +1 -1
  69. package/src/engine.js +60 -23
  70. package/src/engineAudio.js +44 -17
  71. package/src/engineDebug.js +75 -45
  72. package/src/engineDraw.js +94 -59
  73. package/src/engineExport.js +2 -1
  74. package/src/engineMedals.js +10 -2
  75. package/src/engineObject.js +14 -10
  76. package/src/engineParticles.js +60 -47
  77. package/src/engineSettings.js +7 -7
  78. package/src/engineTileLayer.js +73 -56
  79. package/src/engineUtilities.js +67 -20
  80. package/src/engineWebGL.js +134 -63
@@ -306,7 +306,7 @@ let medalsPreventUnlock = false;
306
306
  /** Set position of camera in world space
307
307
  * @param {Vector2} pos
308
308
  * @memberof Settings */
309
- function setCameraPos(pos) { cameraPos = pos; }
309
+ function setCameraPos(pos) { cameraPos = pos.copy(); }
310
310
 
311
311
  /** Set angle of camera in world space
312
312
  * @param {number} angle
@@ -329,17 +329,17 @@ function setCanvasColorTiles(colorTiles) { canvasColorTiles = colorTiles; }
329
329
  /** Set color to clear the canvas to before render
330
330
  * @param {Color} color
331
331
  * @memberof Settings */
332
- function setCanvasClearColor(color) { canvasClearColor = color; }
332
+ function setCanvasClearColor(color) { canvasClearColor = color.copy(); }
333
333
 
334
334
  /** Set max size of the canvas
335
335
  * @param {Vector2} size
336
336
  * @memberof Settings */
337
- function setCanvasMaxSize(size) { canvasMaxSize = size; }
337
+ function setCanvasMaxSize(size) { canvasMaxSize = size.copy(); }
338
338
 
339
339
  /** Set fixed size of the canvas
340
340
  * @param {Vector2} size
341
341
  * @memberof Settings */
342
- function setCanvasFixedSize(size) { canvasFixedSize = size; }
342
+ function setCanvasFixedSize(size) { canvasFixedSize = size.copy(); }
343
343
 
344
344
  /** Use nearest scaling algorithm for canvas for more pixelated look
345
345
  * - If enabled sets css image-rendering:pixelated
@@ -404,7 +404,7 @@ function setGLCircleSides(sides) { glCircleSides = sides; }
404
404
  /** Set default size of tiles in pixels
405
405
  * @param {Vector2} size
406
406
  * @memberof Settings */
407
- function setTileSizeDefault(size) { tileSizeDefault = size; }
407
+ function setTileSizeDefault(size) { tileSizeDefault = size.copy(); }
408
408
 
409
409
  /** Set to prevent tile bleeding from neighbors in pixels
410
410
  * @param {number} scale
@@ -449,7 +449,7 @@ function setObjectMaxSpeed(speed) { objectMaxSpeed = speed; }
449
449
  /** Set how much gravity to apply to objects
450
450
  * @param {Vector2} newGravity
451
451
  * @memberof Settings */
452
- function setGravity(newGravity) { gravity = newGravity; }
452
+ function setGravity(newGravity) { gravity = newGravity.copy(); }
453
453
 
454
454
  /** Set to scales emit rate of particles
455
455
  * @param {number} scale
@@ -539,7 +539,7 @@ function setMedalDisplaySlideTime(time) { medalDisplaySlideTime = time; }
539
539
  /** Set size of medal display
540
540
  * @param {Vector2} size
541
541
  * @memberof Settings */
542
- function setMedalDisplaySize(size) { medalDisplaySize = size; }
542
+ function setMedalDisplaySize(size) { medalDisplaySize = size.copy(); }
543
543
 
544
544
  /** Set to stop medals from being unlockable
545
545
  * @param {boolean} preventUnlock
@@ -4,7 +4,7 @@
4
4
  * - Unlimited numbers of layers, allocates canvases as needed
5
5
  * - Tile layers can be drawn to using their context with canvas2d
6
6
  * - Tile layers can also have collision with EngineObjects
7
- * @namespace TileCollision
7
+ * @namespace TileLayers
8
8
  */
9
9
 
10
10
  'use strict';
@@ -14,13 +14,13 @@
14
14
 
15
15
  /** Keep track of all tile layers with collision
16
16
  * @type {Array<TileCollisionLayer>}
17
- * @memberof TileCollision */
17
+ * @memberof TileLayers */
18
18
  const tileCollisionLayers = [];
19
19
 
20
20
  /** Get tile collision data for a given cell in the grid
21
21
  * @param {Vector2} pos
22
22
  * @return {number}
23
- * @memberof TileCollision */
23
+ * @memberof TileLayers */
24
24
  function tileCollisionGetData(pos)
25
25
  {
26
26
  // check all tile collision layers
@@ -36,7 +36,7 @@ function tileCollisionGetData(pos)
36
36
  * @param {EngineObject} [object] - An object or undefined for generic test
37
37
  * @param {boolean} [solidOnly] - Only check solid layers if true
38
38
  * @return {TileCollisionLayer}
39
- * @memberof TileCollision */
39
+ * @memberof TileLayers */
40
40
  function tileCollisionTest(pos, size=vec2(), object, solidOnly=true)
41
41
  {
42
42
  for (const layer of tileCollisionLayers)
@@ -54,7 +54,7 @@ function tileCollisionTest(pos, size=vec2(), object, solidOnly=true)
54
54
  * @param {EngineObject} [object] - An object or undefined for generic test
55
55
  * @param {boolean} [solidOnly=true] - Only check solid layers if true
56
56
  * @return {Vector2}
57
- * @memberof TileCollision */
57
+ * @memberof TileLayers */
58
58
  function tileCollisionRaycast(posStart, posEnd, object, solidOnly=true)
59
59
  {
60
60
  for (const layer of tileCollisionLayers)
@@ -77,8 +77,8 @@ function tileCollisionRaycast(posStart, posEnd, object, solidOnly=true)
77
77
  * @param {number} [collisionLayer] - Layer to use for collision if any
78
78
  * @param {boolean} [draw] - Should the layer be drawn automatically
79
79
  * @return {Array<TileCollisionLayer>}
80
- * @memberof TileCollision */
81
- function tileCollisionLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisionLayer, draw=true)
80
+ * @memberof TileLayers */
81
+ function tileLayersLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisionLayer, draw=true)
82
82
  {
83
83
  if (!tileMapData)
84
84
  {
@@ -109,9 +109,9 @@ function tileCollisionLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisio
109
109
  tileLayers[layerIndex] = tileLayer;
110
110
 
111
111
  // apply layer color
112
- const layerColor = dataLayer.color || WHITE;
113
- if (dataLayer.tintcolor)
114
- layerColor.setHex(dataLayer.tintcolor);
112
+ const layerColor = dataLayer.tintcolor ?
113
+ new Color().setHex(dataLayer.tintcolor) :
114
+ dataLayer.color || WHITE;
115
115
  ASSERT(isColor(layerColor), 'layer color is not a color');
116
116
 
117
117
  for (let x=levelSize.x; x--;)
@@ -138,6 +138,7 @@ function tileCollisionLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisio
138
138
  ///////////////////////////////////////////////////////////////////////////////
139
139
  /**
140
140
  * Tile layer data object stores info about how to draw a tile
141
+ * @memberof TileLayers
141
142
  * @example
142
143
  * // create tile layer data with tile index 0 and random orientation and color
143
144
  * const tileIndex = 0;
@@ -175,6 +176,7 @@ class TileLayerData
175
176
  * - Contains an offscreen canvas that can be rendered to
176
177
  * - WebGL rendering is optional, call useWebGL to enable
177
178
  * @extends EngineObject
179
+ * @memberof TileLayers
178
180
  * @example
179
181
  * const canvasLayer = new CanvasLayer(vec2(), vec2(200,100));
180
182
  */
@@ -189,15 +191,19 @@ class CanvasLayer extends EngineObject
189
191
  */
190
192
  constructor(position, size, angle=0, renderOrder=0, canvasSize=vec2(512))
191
193
  {
194
+ ASSERT(isVector2(canvasSize), 'canvasSize must be a Vector2');
192
195
  super(position, size, undefined, angle, WHITE, renderOrder);
193
196
 
194
197
  /** @property {HTMLCanvasElement} - The canvas used by this layer */
195
198
  this.canvas = headlessMode ? undefined : new OffscreenCanvas(canvasSize.x, canvasSize.y);
196
199
  /** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this layer */
197
- this.context = headlessMode ? undefined : this.canvas.getContext('2d');
198
- /** @property {WebGLTexture} - Texture if using WebGL for this layer, call useWebGL to enable */
199
- this.glTexture = undefined;
200
- this.gravityScale = 0; // disable gravity by default for canvas layers
200
+ this.context = this.canvas?.getContext('2d');
201
+ /** @property {TextureInfo} - Texture info to use for this object rendering */
202
+ const useWebGL = false; // do not use webgl by default
203
+ this.textureInfo = new TextureInfo(this.canvas, useWebGL);
204
+
205
+ // disable physics by default
206
+ this.mass = this.gravityScale = this.friction = this.restitution = 0;
201
207
  }
202
208
 
203
209
  /** Destroy this canvas layer */
@@ -206,9 +212,7 @@ class CanvasLayer extends EngineObject
206
212
  if (this.destroyed)
207
213
  return;
208
214
 
209
- // free up the WebGL texture
210
- if (this.glTexture)
211
- glDeleteTexture(this.glTexture);
215
+ this.textureInfo.destroyWebGLTexture();
212
216
  super.destroy();
213
217
  }
214
218
 
@@ -231,19 +235,27 @@ class CanvasLayer extends EngineObject
231
235
  draw(pos, size, angle=0, color=WHITE, mirror=false, additiveColor, screenSpace=false, context)
232
236
  {
233
237
  // draw the canvas layer as a single tile that uses the whole texture
234
- const useWebGL = glEnable && this.glTexture !== undefined;
235
- const tileInfo = new TileInfo().setFullImage(this.canvas, this.glTexture);
238
+ const useWebGL = glEnable && this.textureInfo.hasWebGL();
239
+ const tileInfo = new TileInfo().setFullImage(this.textureInfo);
236
240
  drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
237
241
  }
238
242
 
243
+ /**
244
+ * @callback Canvas2DDrawCallback - Function that draws to a canvas 2D context
245
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
246
+ * @memberof TileLayers
247
+ */
248
+
239
249
  /** Draw onto the layer canvas in world space (bypass WebGL)
240
250
  * @param {Vector2} pos
241
251
  * @param {Vector2} size
242
252
  * @param {number} angle
243
253
  * @param {boolean} mirror
244
- * @param {Function} drawFunction */
254
+ * @param {Canvas2DDrawCallback} drawFunction */
245
255
  drawCanvas2D(pos, size, angle, mirror, drawFunction)
246
256
  {
257
+ if (!this.context) return;
258
+
247
259
  const context = this.context;
248
260
  context.save();
249
261
  pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
@@ -278,7 +290,7 @@ class CanvasLayer extends EngineObject
278
290
  else
279
291
  {
280
292
  // untextured
281
- context.fillStyle = color;
293
+ context.fillStyle = color.toString();
282
294
  context.fillRect(-.5, -.5, 1, 1);
283
295
  }
284
296
  });
@@ -296,15 +308,10 @@ class CanvasLayer extends EngineObject
296
308
  * @param {boolean} [enable] - enable WebGL rendering and update the texture */
297
309
  useWebGL(enable=true)
298
310
  {
299
- if (glEnable && enable)
300
- {
301
- if (this.glTexture)
302
- glSetTextureData(this.glTexture, this.canvas);
303
- else
304
- this.glTexture = glCreateTexture(this.canvas);
305
- }
311
+ if (enable)
312
+ this.textureInfo.createWebGLTexture();
306
313
  else
307
- this.glTexture = undefined;
314
+ this.textureInfo.destroyWebGLTexture();
308
315
  }
309
316
  }
310
317
 
@@ -315,7 +322,9 @@ class CanvasLayer extends EngineObject
315
322
  * - To allow dynamic modifications, layers are rendered using canvas 2d
316
323
  * - Some devices like mobile phones are limited to 4k texture resolution
317
324
  * - For with 16x16 tiles this limits layers to 256x256 on mobile devices
325
+ * - Tile layers are centered on their corner, so normal levels are at (0,0)
318
326
  * @extends CanvasLayer
327
+ * @memberof TileLayers
319
328
  * @example
320
329
  * const tileLayer = new TileLayer(vec2(), vec2(200,100));
321
330
  */
@@ -325,26 +334,15 @@ class TileLayer extends CanvasLayer
325
334
  * @param {Vector2} position - World space position
326
335
  * @param {Vector2} size - World space size
327
336
  * @param {TileInfo} [tileInfo] - Default tile info for layer (used for size and texture)
328
- * @param {Vector2} [scale=(1,1)] - How much to scale this layer when rendered
329
337
  * @param {number} [renderOrder] - Objects are sorted by renderOrder
330
- * @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
331
338
  */
332
- constructor(position, size, tileInfo=tile(), scale=vec2(1), renderOrder=0, useWebGL=glEnable)
339
+ constructor(position, size, tileInfo=tile(), renderOrder=0)
333
340
  {
334
- super(position, size, 0, renderOrder, size);
335
- this.tileInfo = tileInfo;
336
-
337
341
  const canvasSize = size.multiply(tileInfo.size);
338
- /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
339
- this.canvas = new OffscreenCanvas(canvasSize.x, canvasSize.y);
340
- /** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
341
- this.context = this.canvas.getContext('2d');
342
- /** @property {WebGLTexture} - Texture if using WebGL for this layer */
343
- this.glTexture = useWebGL ? glCreateTexture(this.canvas) : undefined;
344
- // set no friction by default, applied friction is max of both objects
345
- this.friction = 0;
346
- // set no restitution by default, applied restitution is max of both objects
347
- this.restitution = 0;
342
+ super(position, size, 0, renderOrder, canvasSize);
343
+
344
+ // set tile info
345
+ this.tileInfo = tileInfo;
348
346
 
349
347
  // init tile data
350
348
  this.data = [];
@@ -370,6 +368,8 @@ class TileLayer extends CanvasLayer
370
368
  * @param {boolean} [redraw] - Force the tile to redraw if true */
371
369
  setData(layerPos, data, redraw=false)
372
370
  {
371
+ ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
372
+ ASSERT(data instanceof TileLayerData, 'data must be a TileLayerData');
373
373
  if (layerPos.arrayCheck(this.size))
374
374
  {
375
375
  this.data[(layerPos.y|0)*this.size.x+layerPos.x|0] = data;
@@ -381,7 +381,10 @@ class TileLayer extends CanvasLayer
381
381
  * @param {Vector2} layerPos - Local position in array
382
382
  * @return {TileLayerData} */
383
383
  getData(layerPos)
384
- { return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0]; }
384
+ {
385
+ ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
386
+ return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0];
387
+ }
385
388
 
386
389
  // Render the tile layer, called automatically by the engine
387
390
  render()
@@ -389,10 +392,11 @@ class TileLayer extends CanvasLayer
389
392
  ASSERT(drawContext !== this.context, 'must call redrawEnd() after drawing tiles!');
390
393
 
391
394
  // draw the tile layer as a single tile
392
- const tileInfo = new TileInfo().setFullImage(this.canvas, this.glTexture);
393
- const pos = this.pos.add(this.size.scale(.5));
394
- const useWebGL = glEnable && this.glTexture !== undefined;
395
- drawTile(pos, this.size, tileInfo, WHITE, 0, false, CLEAR_BLACK, useWebGL);
395
+ const tileInfo = new TileInfo().setFullImage(this.textureInfo);
396
+ const size = this.drawSize || this.size;
397
+ const pos = this.pos.add(size.scale(.5));
398
+ const useWebGL = glEnable && this.textureInfo.hasWebGL();
399
+ drawTile(pos, size, tileInfo, WHITE, 0, false, CLEAR_BLACK, useWebGL);
396
400
  }
397
401
 
398
402
  /** Draw all the tile data to an offscreen canvas
@@ -404,8 +408,7 @@ class TileLayer extends CanvasLayer
404
408
  for (let y = this.size.y; y--;)
405
409
  this.drawTileData(vec2(x,y), false);
406
410
  this.redrawEnd();
407
- if (this.glTexture)
408
- this.useWebGL(); // update WebGL texture
411
+ this.useWebGL();
409
412
  }
410
413
 
411
414
  /** Call to start the redraw process
@@ -413,6 +416,8 @@ class TileLayer extends CanvasLayer
413
416
  * @param {boolean} [clear] - Should it clear the canvas before drawing */
414
417
  redrawStart(clear=false)
415
418
  {
419
+ if (!this.context) return;
420
+
416
421
  // save current render settings
417
422
  /** @type {[HTMLCanvasElement|OffscreenCanvas, CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number]} */
418
423
  this.savedRenderSettings = [drawCanvas, drawContext, mainCanvasSize, cameraPos, cameraScale];
@@ -441,6 +446,8 @@ class TileLayer extends CanvasLayer
441
446
  /** Call to end the redraw process */
442
447
  redrawEnd()
443
448
  {
449
+ if (!this.context) return;
450
+
444
451
  ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
445
452
  glCopyToContext(drawContext);
446
453
  //debugSaveCanvas(this.canvas);
@@ -457,6 +464,8 @@ class TileLayer extends CanvasLayer
457
464
  */
458
465
  drawTileData(layerPos, clear=true)
459
466
  {
467
+ if (!this.context) return;
468
+
460
469
  // clear out where the tile was, for full opaque tiles this can be skipped
461
470
  const s = this.tileInfo.size;
462
471
  if (clear)
@@ -484,6 +493,7 @@ class TileLayer extends CanvasLayer
484
493
  * - there can be multiple tile collision layers
485
494
  * - tile collision layers should not overlap each other
486
495
  * @extends TileLayer
496
+ * @memberof TileLayers
487
497
  */
488
498
  class TileCollisionLayer extends TileLayer
489
499
  {
@@ -492,12 +502,10 @@ class TileCollisionLayer extends TileLayer
492
502
  * @param {Vector2} size - World space size
493
503
  * @param {TileInfo} [tileInfo] - Tile info for layer
494
504
  * @param {number} [renderOrder] - Objects are sorted by renderOrder
495
- * @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
496
505
  */
497
- constructor(position, size, tileInfo=tile(), renderOrder=0, useWebGL=glEnable)
506
+ constructor(position, size, tileInfo=tile(), renderOrder=0)
498
507
  {
499
- const scale = vec2(1); // collision layers are not scaled
500
- super(position, size.floor(), tileInfo, scale, renderOrder, useWebGL);
508
+ super(position, size.floor(), tileInfo, renderOrder);
501
509
 
502
510
  /** @property {Array<number>} - The tile collision grid */
503
511
  this.collisionData = [];
@@ -527,6 +535,7 @@ class TileCollisionLayer extends TileLayer
527
535
  * @param {Vector2} size - width and height of tile collision 2d grid */
528
536
  initCollision(size)
529
537
  {
538
+ ASSERT(isVector2(size), 'size must be a Vector2');
530
539
  this.size = size.floor();
531
540
  this.collisionData = [];
532
541
  this.collisionData.length = size.area();
@@ -538,6 +547,7 @@ class TileCollisionLayer extends TileLayer
538
547
  * @param {number} [data] */
539
548
  setCollisionData(gridPos, data=1)
540
549
  {
550
+ ASSERT(isVector2(gridPos), 'gridPos must be a Vector2');
541
551
  const i = (gridPos.y|0)*this.size.x + gridPos.x|0;
542
552
  gridPos.arrayCheck(this.size) && (this.collisionData[i] = data);
543
553
  }
@@ -547,6 +557,7 @@ class TileCollisionLayer extends TileLayer
547
557
  * @return {number} */
548
558
  getCollisionData(gridPos)
549
559
  {
560
+ ASSERT(isVector2(gridPos), 'gridPos must be a Vector2');
550
561
  const i = (gridPos.y|0)*this.size.x + gridPos.x|0;
551
562
  return gridPos.arrayCheck(this.size) ? this.collisionData[i] : 0;
552
563
  }
@@ -558,6 +569,9 @@ class TileCollisionLayer extends TileLayer
558
569
  * @return {boolean} */
559
570
  collisionTest(pos, size=new Vector2, object)
560
571
  {
572
+ ASSERT(isVector2(pos) && isVector2(size), 'pos and size must be Vector2s');
573
+ ASSERT(!object || object instanceof EngineObject, 'object must be an EngineObject');
574
+
561
575
  // transform to local layer space
562
576
  const posX = pos.x - this.pos.x;
563
577
  const posY = pos.y - this.pos.y;
@@ -588,6 +602,9 @@ class TileCollisionLayer extends TileLayer
588
602
  * @return {Vector2} */
589
603
  collisionRaycast(posStart, posEnd, object)
590
604
  {
605
+ ASSERT(isVector2(posStart) && isVector2(posEnd), 'positions must be Vector2s');
606
+ ASSERT(!object || object instanceof EngineObject, 'object must be an EngineObject');
607
+
591
608
  // transform to local layer space
592
609
  const posStartX = posStart.x - this.pos.x;
593
610
  const posStartY = posStart.y - this.pos.y;
@@ -231,6 +231,8 @@ function formatTime(t)
231
231
  async function fetchJSON(url)
232
232
  {
233
233
  const response = await fetch(url);
234
+ if (!response.ok)
235
+ throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);
234
236
  return response.json();
235
237
  }
236
238
 
@@ -241,6 +243,13 @@ async function fetchJSON(url)
241
243
  * @memberof Utilities */
242
244
  function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
243
245
 
246
+ /**
247
+ * Check if object is a valid string or can be converted to one
248
+ * @param {any} s
249
+ * @return {boolean}
250
+ * @memberof Utilities */
251
+ function isString(s) { return s !== undefined && s !== null && typeof s.toString() === 'string'; }
252
+
244
253
  ///////////////////////////////////////////////////////////////////////////////
245
254
 
246
255
  /** Random global functions
@@ -303,6 +312,7 @@ function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
303
312
  /**
304
313
  * Seeded random number generator
305
314
  * - Can be used to create a deterministic random number sequence
315
+ * @memberof Engine
306
316
  * @example
307
317
  * let r = new RandomGenerator(123); // random number generator with seed 123
308
318
  * let a = r.float(); // random value between 0 and 1
@@ -399,6 +409,7 @@ function ASSERT_VECTOR2_NORMAL(v)
399
409
  /**
400
410
  * 2D Vector object with vector math library
401
411
  * - Functions do not change this so they can be chained together
412
+ * @memberof Engine
402
413
  * @example
403
414
  * let a = new Vector2(2, 3); // vector with coordinates (2, 3)
404
415
  * let b = new Vector2; // vector with coordinates (0, 0)
@@ -539,9 +550,10 @@ class Vector2
539
550
  return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
540
551
  }
541
552
 
542
- /** Set the integer direction of this vector, corresponding to multiples of 90 degree rotation (0-3)
553
+ /** Sets this this vector to point in the specified integer direction (0-3), corresponding to multiples of 90 degree rotation
543
554
  * @param {number} [direction]
544
- * @param {number} [length] */
555
+ * @param {number} [length]
556
+ * @return {Vector2} */
545
557
  setDirection(direction, length=1)
546
558
  {
547
559
  ASSERT_NUMBER_VALID(direction);
@@ -549,8 +561,10 @@ class Vector2
549
561
  direction = mod(direction, 4);
550
562
  ASSERT(direction===0 || direction===1 || direction===2 || direction===3,
551
563
  'Vector2.setDirection() direction must be an integer between 0 and 3.');
552
- return vec2(direction%2 ? direction-1 ? -length : length : 0,
553
- direction%2 ? 0 : direction ? -length : length);
564
+
565
+ this.x = direction%2 ? direction-1 ? -length : length : 0;
566
+ this.y = direction%2 ? 0 : direction ? -length : length;
567
+ return this;
554
568
  }
555
569
 
556
570
  /** Returns the integer direction of this vector, corresponding to multiples of 90 degree rotation (0-3)
@@ -646,6 +660,7 @@ function ASSERT_COLOR_VALID(c) { ASSERT(isColor(c), 'Color is invalid.', c); }
646
660
 
647
661
  /**
648
662
  * Color object (red, green, blue, alpha) with some helpful functions
663
+ * @memberof Engine
649
664
  * @example
650
665
  * let a = new Color; // white
651
666
  * let b = new Color(1, 0, 0); // red
@@ -823,7 +838,8 @@ class Color
823
838
  * @return {Color} */
824
839
  setHex(hex)
825
840
  {
826
- ASSERT(typeof hex === 'string' && hex[0] === '#', 'Color hex code must be a string starting with #');
841
+ ASSERT(isString(hex), 'Color hex code must be a string');
842
+ ASSERT(hex[0] === '#', 'Color hex code must start with #');
827
843
  ASSERT([4,5,7,9].includes(hex.length), 'Invalid hex');
828
844
 
829
845
  if (hex.length < 6)
@@ -865,77 +881,78 @@ class Color
865
881
  }
866
882
 
867
883
  ///////////////////////////////////////////////////////////////////////////////
868
- // default colors
884
+ // Default Colors
869
885
 
870
886
  /** Color - White #ffffff
871
887
  * @type {Color}
872
888
  * @memberof Utilities */
873
- const WHITE = rgb();
889
+ const WHITE = protectEngineConstant(rgb());
874
890
 
875
- /** Color - Clear White #ffffff with 0 alpha
891
+ /** Color - Clear White #757474ff with 0 alpha
876
892
  * @type {Color}
877
893
  * @memberof Utilities */
878
- const CLEAR_WHITE = rgb(1,1,1,0);
894
+ const CLEAR_WHITE = protectEngineConstant(rgb(1,1,1,0));
879
895
 
880
896
  /** Color - Black #000000
881
897
  * @type {Color}
882
898
  * @memberof Utilities */
883
- const BLACK = rgb(0,0,0);
899
+ const BLACK = protectEngineConstant(rgb(0,0,0));
884
900
 
885
901
  /** Color - Clear Black #000000 with 0 alpha
886
902
  * @type {Color}
887
903
  * @memberof Utilities */
888
- const CLEAR_BLACK = rgb(0,0,0,0);
904
+ const CLEAR_BLACK = protectEngineConstant(rgb(0,0,0,0));
889
905
 
890
906
  /** Color - Gray #808080
891
907
  * @type {Color}
892
908
  * @memberof Utilities */
893
- const GRAY = rgb(.5,.5,.5);
909
+ const GRAY = protectEngineConstant(rgb(.5,.5,.5));
894
910
 
895
911
  /** Color - Red #ff0000
896
912
  * @type {Color}
897
913
  * @memberof Utilities */
898
- const RED = rgb(1,0,0);
914
+ const RED = protectEngineConstant(rgb(1,0,0));
899
915
 
900
916
  /** Color - Orange #ff8000
901
917
  * @type {Color}
902
918
  * @memberof Utilities */
903
- const ORANGE = rgb(1,.5,0);
919
+ const ORANGE = protectEngineConstant(rgb(1,.5,0));
904
920
 
905
921
  /** Color - Yellow #ffff00
906
922
  * @type {Color}
907
923
  * @memberof Utilities */
908
- const YELLOW = rgb(1,1,0);
924
+ const YELLOW = protectEngineConstant(rgb(1,1,0));
909
925
 
910
926
  /** Color - Green #00ff00
911
927
  * @type {Color}
912
928
  * @memberof Utilities */
913
- const GREEN = rgb(0,1,0);
929
+ const GREEN = protectEngineConstant(rgb(0,1,0));
914
930
 
915
931
  /** Color - Cyan #00ffff
916
932
  * @type {Color}
917
933
  * @memberof Utilities */
918
- const CYAN = rgb(0,1,1);
934
+ const CYAN = protectEngineConstant(rgb(0,1,1));
919
935
 
920
936
  /** Color - Blue #0000ff
921
937
  * @type {Color}
922
938
  * @memberof Utilities */
923
- const BLUE = rgb(0,0,1);
939
+ const BLUE = protectEngineConstant(rgb(0,0,1));
924
940
 
925
941
  /** Color - Purple #8000ff
926
942
  * @type {Color}
927
943
  * @memberof Utilities */
928
- const PURPLE = rgb(.5,0,1);
944
+ const PURPLE = protectEngineConstant(rgb(.5,0,1));
929
945
 
930
946
  /** Color - Magenta #ff00ff
931
947
  * @type {Color}
932
948
  * @memberof Utilities */
933
- const MAGENTA = rgb(1,0,1);
949
+ const MAGENTA = protectEngineConstant(rgb(1,0,1));
934
950
 
935
951
  ///////////////////////////////////////////////////////////////////////////////
936
952
 
937
953
  /**
938
954
  * Timer object tracks how long has passed since it was set
955
+ * @memberof Engine
939
956
  * @example
940
957
  * let a = new Timer; // creates a timer that is not set
941
958
  * a.set(3); // sets the timer to 3 seconds
@@ -997,4 +1014,34 @@ class Timer
997
1014
  /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
998
1015
  * @return {number} */
999
1016
  valueOf() { return this.get(); }
1017
+ }
1018
+
1019
+ ///////////////////////////////////////////////////////////////////////////////
1020
+ // Helper functions used by the engine
1021
+
1022
+ // make color constants immutable with debug assertions
1023
+ function protectEngineConstant(obj)
1024
+ {
1025
+ if (debug)
1026
+ {
1027
+ // get properties and store original values
1028
+ const props = Object.keys(obj), values = {};
1029
+ props.forEach(prop => values[prop] = obj[prop]);
1030
+
1031
+ // replace with getters/setters that assert
1032
+ props.forEach(prop =>
1033
+ {
1034
+ Object.defineProperty(obj, prop, {
1035
+ get: () => values[prop],
1036
+ set: (value) =>
1037
+ {
1038
+ ASSERT(false, `Cannot modify engine constant. Attempted to set constant (${obj}) property '${prop}' to '${value}'.`);
1039
+ },
1040
+ enumerable: true
1041
+ });
1042
+ });
1043
+ }
1044
+
1045
+ // freeze the object to prevent adding new properties
1046
+ return Object.freeze(obj);
1000
1047
  }