littlejsengine 1.16.2 → 1.17.1

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 (47) hide show
  1. package/dist/littlejs.d.ts +200 -166
  2. package/dist/littlejs.esm.js +1231 -1086
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +888 -735
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +884 -731
  7. package/examples/box2d/gameObjects.js +1 -1
  8. package/examples/breakout/game.js +5 -6
  9. package/examples/electron/index.html +2 -2
  10. package/examples/electron/tiles.png +0 -0
  11. package/examples/htmlMenu/tiles.png +0 -0
  12. package/examples/index.html +1 -1
  13. package/examples/logo.png +0 -0
  14. package/examples/module/tiles.png +0 -0
  15. package/examples/platformer/gameEffects.js +23 -22
  16. package/examples/platformer/gameLevel.js +24 -25
  17. package/examples/shorts/base.html +1 -1
  18. package/examples/shorts/clock.js +3 -3
  19. package/examples/shorts/fontImage.js +4 -3
  20. package/examples/shorts/parallax.js +1 -1
  21. package/examples/shorts/sequencer.js +1 -1
  22. package/examples/shorts/shapes.js +1 -1
  23. package/examples/shorts/texture.js +10 -6
  24. package/examples/shorts/tiles.png +0 -0
  25. package/examples/shorts/tiltedView.js +2 -0
  26. package/examples/starter/index.html +2 -2
  27. package/examples/starter/tiles.png +0 -0
  28. package/examples/typescript/tiles.png +0 -0
  29. package/examples/uiSystem/game.js +1 -1
  30. package/examples/uiSystem/tiles.png +0 -0
  31. package/package.json +1 -1
  32. package/plugins/postProcess.js +47 -28
  33. package/plugins/uiSystem.js +15 -15
  34. package/reference.md +1 -1
  35. package/src/engine.js +174 -174
  36. package/src/engineAudio.js +4 -6
  37. package/src/engineDebug.js +4 -4
  38. package/src/engineDraw.js +179 -122
  39. package/src/engineExport.js +326 -334
  40. package/src/engineFont.png +0 -0
  41. package/src/engineInput.js +14 -20
  42. package/src/engineMath.js +15 -104
  43. package/src/engineObject.js +26 -25
  44. package/src/engineParticles.js +2 -2
  45. package/src/engineTileLayer.js +196 -170
  46. package/src/engineUtilities.js +100 -4
  47. package/src/engineWebGL.js +123 -72
@@ -32,7 +32,7 @@ function tileCollisionGetData(pos)
32
32
 
33
33
  /** Check if a tile layer collides with another object
34
34
  * @param {Vector2} pos
35
- * @param {Vector2} [size=(0,0)]
35
+ * @param {Vector2} [size=vec2()]
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}
@@ -150,20 +150,20 @@ function tileLayersLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisionLa
150
150
  class TileLayerData
151
151
  {
152
152
  /** Create a tile layer data object, one for each tile in a TileLayer
153
- * @param {number} [tile] - The tile to use, untextured if undefined
153
+ * @param {number} [tile] - The tile to use, untextured if undefined
154
154
  * @param {number} [direction] - Integer direction of tile, in 90 degree increments
155
- * @param {boolean} [mirror] - If the tile should be mirrored along the x axis
156
- * @param {Color} [color] - Color of the tile */
155
+ * @param {boolean} [mirror] - If the tile should be mirrored along the x axis
156
+ * @param {Color} [color] - Color of the tile */
157
157
  constructor(tile, direction=0, mirror=false, color=new Color)
158
158
  {
159
- /** @property {number} - The tile to use, untextured if undefined */
160
- this.tile = tile;
161
- /** @property {number} - Integer direction of tile, in 90 degree increments */
159
+ /** @property {number} - The tile to use, untextured if undefined */
160
+ this.tile = tile;
161
+ /** @property {number} - Integer direction of tile, in 90 degree increments */
162
162
  this.direction = direction;
163
163
  /** @property {boolean} - If the tile should be mirrored along the x axis */
164
- this.mirror = mirror;
165
- /** @property {Color} - Color of the tile */
166
- this.color = color.copy();
164
+ this.mirror = mirror;
165
+ /** @property {Color} - Color of the tile */
166
+ this.color = color.copy();
167
167
  }
168
168
 
169
169
  /** Set this tile to clear, it will not be rendered */
@@ -174,7 +174,7 @@ class TileLayerData
174
174
  /**
175
175
  * Canvas Layer - cached off screen rendering system
176
176
  * - Contains an offscreen canvas that can be rendered to
177
- * - WebGL rendering is optional, call useWebGL to enable
177
+ * - WebGL rendering is optional, call updateWebGL to enable/update
178
178
  * @extends EngineObject
179
179
  * @memberof TileLayers
180
180
  * @example
@@ -188,8 +188,9 @@ class CanvasLayer extends EngineObject
188
188
  * @param {number} [angle] - Angle the layer is rotated by
189
189
  * @param {number} [renderOrder] - Objects sorted by renderOrder
190
190
  * @param {Vector2} [canvasSize] - Default size of canvas, can be changed later
191
+ * @param {boolean} [useWebGL] - Should this layer use WebGL for rendering
191
192
  */
192
- constructor(position, size, angle=0, renderOrder=0, canvasSize=vec2(512))
193
+ constructor(position, size, angle=0, renderOrder=0, canvasSize=vec2(512), useWebGL=glEnable)
193
194
  {
194
195
  ASSERT(isVector2(canvasSize), 'canvasSize must be a Vector2');
195
196
  super(position, size, undefined, angle, WHITE, renderOrder);
@@ -199,13 +200,10 @@ class CanvasLayer extends EngineObject
199
200
  /** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this layer */
200
201
  this.context = this.canvas?.getContext('2d');
201
202
  /** @property {TextureInfo} - Texture info to use for this object rendering */
202
- const useWebGL = false; // do not use webgl by default
203
203
  this.textureInfo = new TextureInfo(this.canvas, useWebGL);
204
- /** @property {boolean} - True if WebGL texture needs to be refreshed */
205
- this.refreshWebGL = false;
206
204
 
207
205
  // disable physics by default
208
- this.mass = this.gravityScale = this.friction = this.restitution = 0;
206
+ this.mass = 0;
209
207
  }
210
208
 
211
209
  /** Destroy this canvas layer */
@@ -220,7 +218,7 @@ class CanvasLayer extends EngineObject
220
218
  // Render the layer, called automatically by the engine
221
219
  render()
222
220
  {
223
- this.draw(this.pos, this.size, this.angle, this.color, this.mirror, this.additiveColor);
221
+ this.draw(this.pos, this.size, this.color, this.angle, this.mirror, this.additiveColor);
224
222
  }
225
223
 
226
224
  /** Draw this canvas layer centered in world space, with color applied if using WebGL
@@ -233,103 +231,54 @@ class CanvasLayer extends EngineObject
233
231
  * @param {boolean} [screenSpace] - If true the pos and size are in screen space
234
232
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
235
233
  * @memberof Draw */
236
- draw(pos, size, angle=0, color=WHITE, mirror=false, additiveColor, screenSpace=false, context)
234
+ draw(pos, size, color=WHITE, angle=0, mirror=false, additiveColor, screenSpace=false, context)
237
235
  {
238
- const useWebGL = glEnable && this.textureInfo.hasWebGL();
239
- if (useWebGL && this.refreshWebGL)
240
- {
241
- // update the WebGL texture
242
- this.textureInfo.createWebGLTexture();
243
- this.refreshWebGL = false;
244
- }
245
-
246
236
  // draw the canvas layer as a single tile that uses the whole texture
247
237
  const tileInfo = new TileInfo().setFullImage(this.textureInfo);
238
+ const useWebGL = this.hasWebGL();
248
239
  drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
249
240
  }
250
241
 
251
- /**
252
- * @callback Canvas2DDrawCallback - Function that draws to a canvas 2D context
253
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
254
- * @memberof TileLayers
255
- */
256
-
257
- /** Draw onto the layer canvas in world space (bypass WebGL)
258
- * @param {Vector2} pos
259
- * @param {Vector2} size
260
- * @param {number} angle
261
- * @param {boolean} mirror
262
- * @param {Canvas2DDrawCallback} drawFunction */
263
- drawCanvas2D(pos, size, angle, mirror, drawFunction)
264
- {
265
- if (!this.context) return;
266
-
267
- const context = this.context;
268
- context.save();
269
- pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
270
- size = size.multiply(this.tileInfo.size);
271
- context.translate(pos.x, this.canvas.height - pos.y);
272
- context.rotate(angle);
273
- context.scale(mirror ? -size.x : size.x, size.y);
274
- drawFunction(context);
275
- context.restore();
276
- }
277
-
278
242
  /** Draw a tile onto the layer canvas in world space
279
243
  * @param {Vector2} pos
280
- * @param {Vector2} [size=(1,1)]
244
+ * @param {Vector2} [size=vec2(1)]
281
245
  * @param {TileInfo} [tileInfo]
282
- * @param {Color} [color=(1,1,1,1)]
283
- * @param {number} [angle=0]
284
- * @param {boolean} [mirror=false] */
285
- drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle, mirror)
246
+ * @param {Color} [color=WHITE]
247
+ * @param {number} [angle]
248
+ * @param {boolean} [mirror] */
249
+ drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle=0, mirror=false)
286
250
  {
287
- this.drawCanvas2D(pos, size, angle, mirror, (context)=>
288
- {
289
- const textureInfo = tileInfo && tileInfo.textureInfo;
290
- if (textureInfo)
291
- {
292
- context.globalAlpha = color.a; // only alpha is supported
293
- context.drawImage(textureInfo.image,
294
- tileInfo.pos.x, tileInfo.pos.y,
295
- tileInfo.size.x, tileInfo.size.y, -.5, -.5, 1, 1);
296
- context.globalAlpha = 1;
297
- }
298
- else
299
- {
300
- // untextured
301
- context.fillStyle = color.toString();
302
- context.fillRect(-.5, -.5, 1, 1);
303
- }
304
- });
251
+ pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
252
+ size = size.multiply(this.tileInfo.size);
253
+ pos.y = this.canvas.height - pos.y;
254
+
255
+ // draw the tile onto the layer canvas
256
+ const oldMainCanvasSize = mainCanvasSize;
257
+ mainCanvasSize = vec2(this.canvas.width, this.canvas.height);
258
+ const useWebGL = this.hasWebGL();
259
+ useWebGL && glSetRenderTarget(this.textureInfo.glTexture);
260
+ const drawContext = useWebGL ? undefined : this.context;
261
+ drawTile(pos, size, tileInfo, color, angle, mirror, undefined, useWebGL, true, drawContext);
262
+ useWebGL && glSetRenderTarget();
263
+ mainCanvasSize = oldMainCanvasSize;
305
264
  }
306
265
 
307
266
  /** Draw a rectangle onto the layer canvas in world space
308
267
  * @param {Vector2} pos
309
- * @param {Vector2} [size=(1,1)]
310
- * @param {Color} [color=(1,1,1,1)]
311
- * @param {number} [angle=0] */
268
+ * @param {Vector2} [size=vec2(1)]
269
+ * @param {Color} [color=WHITE]
270
+ * @param {number} [angle] */
312
271
  drawRect(pos, size, color, angle)
313
272
  { this.drawTile(pos, size, undefined, color, angle); }
314
273
 
315
- /** Create or update the WebGL texture for this layer
316
- * @param {boolean} [enable] - enable WebGL rendering and update the texture
317
- * @param {boolean} [immediate] - shoulkd the texture be updated immediately
318
- */
319
- useWebGL(enable=true, immediate=false)
320
- {
321
- if (!immediate && enable && this.textureInfo.hasWebGL())
322
- {
323
- // refresh the texture when needed
324
- this.refreshWebGL = true;
325
- return;
326
- }
274
+ /** Create WebGL texture if necessary and copy layer canvas to it */
275
+ updateWebGL()
276
+ { this.textureInfo.createWebGLTexture(); }
327
277
 
328
- if (enable)
329
- this.textureInfo.createWebGLTexture();
330
- else
331
- this.textureInfo.destroyWebGLTexture();
332
- }
278
+ /** Check if this layer is using WebGL
279
+ * @return {boolean} */
280
+ hasWebGL()
281
+ { return glEnable && this.textureInfo.hasWebGL(); }
333
282
  }
334
283
 
335
284
  ///////////////////////////////////////////////////////////////////////////////
@@ -348,50 +297,67 @@ class CanvasLayer extends EngineObject
348
297
  class TileLayer extends CanvasLayer
349
298
  {
350
299
  /** Create a tile layer object
351
- * @param {Vector2} position - World space position
352
- * @param {Vector2} size - World space size
353
- * @param {TileInfo} [tileInfo] - Default tile info for layer (used for size and texture)
300
+ * @param {Vector2} position - World space position
301
+ * @param {Vector2} size - World space size
302
+ * @param {TileInfo} [tileInfo] - Default tile info for layer (used for size and texture)
354
303
  * @param {number} [renderOrder] - Objects are sorted by renderOrder
355
304
  */
356
305
  constructor(position, size, tileInfo=tile(), renderOrder=0)
357
306
  {
358
307
  const canvasSize = tileInfo ? size.multiply(tileInfo.size) : size;
359
- super(position, size, 0, renderOrder, canvasSize);
360
-
361
- // set tile info
362
- this.tileInfo = tileInfo;
363
-
364
- // init tile data
308
+ const useWebGL = true;
309
+ super(position, size, 0, renderOrder, canvasSize, useWebGL);
310
+
311
+ /** @property {TileInfo} - Default tile info for layer */
312
+ this.tileInfo = undefined;
313
+ /** @property {Array<TileLayerData>} - Default tile info for layer */
365
314
  this.data = [];
366
- for (let j = this.size.area(); j--;)
367
- this.data.push(new TileLayerData);
315
+ /** @property {boolean} - Is this layer using a webgl texture? */
316
+ this.isUsingWebGL = false;
368
317
 
369
318
  if (headlessMode)
370
319
  {
371
- // disable rendering
372
- this.redraw = () => {};
373
- this.render = () => {};
374
- this.redrawStart = () => {};
375
- this.redrawEnd = () => {};
376
- this.drawTileData = () => {};
377
- this.drawCanvas2D = () => {};
378
- this.useWebGL = () => {};
320
+ // disable rendering in headless mode
321
+ this.render = () => {};
322
+ this.redraw = () => {};
323
+ this.redrawStart = () => {};
324
+ this.redrawEnd = () => {};
325
+ this.drawTileData = () => {};
326
+ this.redrawTileData = () => {};
327
+ this.drawLayerTile = () => {};
328
+ this.drawLayerRect = () => {};
329
+ this.clearLayerRect = () => {};
330
+ return;
331
+ }
332
+
333
+ if (tileInfo)
334
+ {
335
+ // set tile info
336
+ this.tileInfo = tileInfo.frame(0);
337
+ this.tileInfo.bleed = 0; // disable bleed for tile layers
379
338
  }
339
+
340
+ // init tile data
341
+ for (let j = this.size.area(); j--;)
342
+ this.data.push(new TileLayerData);
380
343
  }
381
344
 
382
345
  /** Set data at a given position in the array
383
346
  * @param {Vector2} layerPos - Local position in array
384
- * @param {TileLayerData} data - Data to set
347
+ * @param {TileLayerData} data - Data to set
385
348
  * @param {boolean} [redraw] - Force the tile to redraw if true */
386
349
  setData(layerPos, data, redraw=false)
387
350
  {
351
+ layerPos = layerPos.floor();
388
352
  ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
389
353
  ASSERT(data instanceof TileLayerData, 'data must be a TileLayerData');
390
- if (layerPos.arrayCheck(this.size))
391
- {
392
- this.data[(layerPos.y|0)*this.size.x+layerPos.x|0] = data;
393
- redraw && this.drawTileData(layerPos);
394
- }
354
+
355
+ if (!layerPos.arrayCheck(this.size)) return;
356
+ this.data[(layerPos.y|0)*this.size.x+layerPos.x|0] = data;
357
+
358
+ if (!redraw) return;
359
+ const isRedraw = drawContext === this.context;
360
+ isRedraw ? this.drawTileData(layerPos) : this.redrawTileData(layerPos);
395
361
  }
396
362
 
397
363
  /** Get data at a given position in the array
@@ -400,7 +366,18 @@ class TileLayer extends CanvasLayer
400
366
  getData(layerPos)
401
367
  {
402
368
  ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
403
- return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0];
369
+ return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0];
370
+ }
371
+
372
+ // Update the tile layer, refresh texture if needed
373
+ update()
374
+ {
375
+ if (!glEnable && this.isUsingWebGL)
376
+ {
377
+ // redraw the layer if webgl was disabled or context lost
378
+ this.isUsingWebGL = false;
379
+ this.redraw();
380
+ }
404
381
  }
405
382
 
406
383
  // Render the tile layer, called automatically by the engine
@@ -408,40 +385,35 @@ class TileLayer extends CanvasLayer
408
385
  {
409
386
  ASSERT(drawContext !== this.context, 'must call redrawEnd() after drawing tiles!');
410
387
 
411
- if (this.refreshWebGL)
412
- {
413
- // update the WebGL texture
414
- this.textureInfo.createWebGLTexture();
415
- this.refreshWebGL = false;
416
- }
417
-
418
- // draw the tile layer as a single tile
419
- const tileInfo = new TileInfo().setFullImage(this.textureInfo);
420
388
  const size = this.drawSize || this.size;
421
389
  const pos = this.pos.add(size.scale(.5));
422
- const useWebGL = glEnable && this.textureInfo.hasWebGL();
423
- drawTile(pos, size, tileInfo, WHITE, 0, false, CLEAR_BLACK, useWebGL);
390
+ this.draw(pos, this.size, this.color, this.angle, this.mirror, this.additiveColor);
424
391
  }
425
392
 
393
+ /** Called after this layer is redrawn, does nothing by default */
394
+ onRedraw() {}
395
+
426
396
  /** Draw all the tile data to an offscreen canvas
427
- * - This may be slow in some browsers but only needs to be done once */
397
+ * - This may be slow if not using webgl but only needs to be done once */
428
398
  redraw()
429
399
  {
430
400
  this.redrawStart(true);
431
401
  for (let x = this.size.x; x--;)
432
402
  for (let y = this.size.y; y--;)
433
403
  this.drawTileData(vec2(x,y), false);
404
+ this.hasWebGL() && glFlush();
405
+ this.onRedraw();
434
406
  this.redrawEnd();
435
- this.useWebGL();
436
407
  }
437
408
 
438
409
  /** Call to start the redraw process
439
- * - This can be used to manually update small parts of the level
410
+ * - This can be used to manually update parts of the level
440
411
  * @param {boolean} [clear] - Should it clear the canvas before drawing */
441
412
  redrawStart(clear=false)
442
413
  {
443
414
  if (!this.context) return;
444
-
415
+ ASSERT(drawContext !== this.context);
416
+
445
417
  // save current render settings
446
418
  /** @type {[CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number, Color]} */
447
419
  this.savedRenderSettings = [drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor];
@@ -449,33 +421,37 @@ class TileLayer extends CanvasLayer
449
421
  // set the draw canvas and context to this layer
450
422
  // use camera settings to match this layer's canvas
451
423
  drawContext = this.context;
452
- cameraPos = this.size.scale(.5);
453
- const tileSize = this.tileInfo ? this.tileInfo.size : vec2(1);
454
- cameraScale = tileSize.x;
455
- canvasClearColor = CLEAR_BLACK;
424
+ const tileSize = this.tileInfo?.size ?? vec2(1);
456
425
  mainCanvasSize = this.size.multiply(tileSize);
457
- if (clear)
426
+ canvasClearColor = CLEAR_BLACK;
427
+ cameraPos = this.size.multiply(tileSize).scale(.5);
428
+ cameraScale = 1;
429
+
430
+ // set render target to this layer
431
+ this.isUsingWebGL = this.hasWebGL();
432
+ if (this.isUsingWebGL)
433
+ glSetRenderTarget(this.textureInfo.glTexture, clear);
434
+ else
458
435
  {
459
- // clear and set size
460
- this.canvas.width = mainCanvasSize.x;
461
- this.canvas.height = mainCanvasSize.y;
436
+ // disable smoothing for pixel art
437
+ this.context.imageSmoothingEnabled = !tilesPixelated;
438
+ if (clear)
439
+ {
440
+ // clear and set size
441
+ this.canvas.width = mainCanvasSize.x;
442
+ this.canvas.height = mainCanvasSize.y;
443
+ }
462
444
  }
463
-
464
- // disable smoothing for pixel art
465
- drawContext.imageSmoothingEnabled = !tilesPixelated;
466
-
467
- // setup gl rendering if enabled
468
- glPreRender();
469
445
  }
470
446
 
471
447
  /** Call to end the redraw process */
472
448
  redrawEnd()
473
449
  {
474
450
  if (!this.context) return;
451
+ ASSERT(drawContext === this.context);
475
452
 
476
- ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
477
- glCopyToContext(drawContext);
478
- //saveCanvas(this.canvas);
453
+ if (glEnable && this.textureInfo.glTexture)
454
+ glSetRenderTarget();
479
455
 
480
456
  // set stuff back to normal
481
457
  [drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor] = this.savedRenderSettings;
@@ -490,24 +466,75 @@ class TileLayer extends CanvasLayer
490
466
  drawTileData(layerPos, clear=true)
491
467
  {
492
468
  if (!this.context) return;
469
+ ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
493
470
 
494
- // clear out where the tile was, for full opaque tiles this can be skipped
495
- const s = this.tileInfo.size;
496
- if (clear)
497
- {
498
- const pos = layerPos.multiply(s);
499
- this.context.clearRect(pos.x, this.canvas.height-pos.y, s.x, -s.y);
500
- }
471
+ // clear out where the tile was, can be skipped for fully opaque tiles
472
+ const drawSize = this.tileInfo?.size ?? vec2(1);
473
+ const drawPos = layerPos.multiply(drawSize);
474
+ clear && this.clearLayerRect(drawPos, drawSize);
501
475
 
502
476
  // draw the tile if it has layer data
503
477
  const d = this.getData(layerPos);
504
- if (d.tile !== undefined)
505
- {
506
- ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
507
- const pos = layerPos.add(vec2(.5));
508
- const tileInfo = tile(d.tile, s, this.tileInfo.textureIndex, this.tileInfo.padding);
509
- drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
510
- }
478
+ if (!d.tile) return;
479
+
480
+ const tileInfo = this.tileInfo && this.tileInfo.tile(d.tile);
481
+ this.drawLayerTile(drawPos, drawSize, tileInfo, d.color, d.direction*PI/2, d.mirror);
482
+ }
483
+
484
+ /** Draw the tile at a given position in the tile grid
485
+ * This can be used to clear tiles when they are destroyed
486
+ * For better performance use drawTileData inside a redrawStart/End block
487
+ * @param {Vector2} layerPos
488
+ * @param {boolean} [clear] - should the old tile be cleared
489
+ */
490
+ redrawTileData(layerPos, clear=true)
491
+ {
492
+ if (!this.context) return;
493
+ ASSERT(drawContext !== this.context, 'redrawStart() should not be active when calling redrawTileData(), instead use drawTileData()');
494
+
495
+ this.redrawStart();
496
+ this.drawTileData(layerPos, clear);
497
+ this.redrawEnd();
498
+ }
499
+
500
+ /** Draw textured tile in layer space
501
+ * @param {Vector2} pos - Position in pixel coordinates
502
+ * @param {Vector2} [size=vec2(1)] - Size of the tile
503
+ * @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
504
+ * @param {Color} [color=WHITE] - Color to modulate with
505
+ * @param {number} [angle] - Angle to rotate by
506
+ * @param {boolean} [mirror] - Is image flipped along the Y axis?
507
+ * @param {Color} [additiveColor] - Additive color to be applied if any */
508
+ drawLayerTile(pos, size=vec2(1), tileInfo, color=WHITE,
509
+ angle=0, mirror, additiveColor)
510
+ {
511
+ const drawPos = pos.add(size.scale(.5));
512
+ drawTile(drawPos, size, tileInfo, color, angle, mirror, additiveColor);
513
+ }
514
+
515
+ /** Clear a rectangle in layer space
516
+ * @param {Vector2} pos
517
+ * @param {Vector2} size
518
+ * @param {Color} [color=WHITE] - Color to modulate with
519
+ * @param {number} [angle] - Angle to rotate by
520
+ */
521
+ drawLayerRect(pos, size, color, angle=0)
522
+ { this.drawLayerTile(pos, size, undefined, color, angle); }
523
+
524
+ /** Clear a rectangle in layer space
525
+ * @param {Vector2} pos - position in pixel coordinates
526
+ * @param {Vector2} size
527
+ */
528
+ clearLayerRect(pos, size)
529
+ {
530
+ ASSERT(drawContext === this.context, 'must call redrawStart() before clearing tiles');
531
+
532
+ const x = pos.x, y = this.canvas.height - pos.y - size.y;
533
+ const useWebGL = this.hasWebGL();
534
+ if (useWebGL)
535
+ glClearRect(x, y, size.x, size.y);
536
+ else
537
+ this.context.clearRect(x, y, size.x, size.y);
511
538
  }
512
539
  }
513
540
 
@@ -516,7 +543,6 @@ class TileLayer extends CanvasLayer
516
543
  * Tile Collision Layer - a tile layer with collision
517
544
  * - adds collision data and functions to TileLayer
518
545
  * - there can be multiple tile collision layers
519
- * - tile collision layers should not overlap each other
520
546
  * @extends TileLayer
521
547
  * @memberof TileLayers
522
548
  */
@@ -588,7 +614,7 @@ class TileCollisionLayer extends TileLayer
588
614
 
589
615
  /** Check if collision with another object should occur
590
616
  * @param {Vector2} pos
591
- * @param {Vector2} [size=(0,0)]
617
+ * @param {Vector2} [size=vec2()]
592
618
  * @param {EngineObject} [object]
593
619
  * @return {boolean} */
594
620
  collisionTest(pos, size=new Vector2, object)
@@ -1,10 +1,7 @@
1
1
  /**
2
2
  * LittleJS Utility Classes and Functions
3
- * - General purpose math library
4
- * - Vector2 - fast, simple, easy 2D vector class
5
- * - Color - holds a rgba color with some math functions
3
+ * - General purpose utilities
6
4
  * - Timer - tracks time automatically
7
- * - RandomGenerator - seeded random number generator
8
5
  * @namespace Utilities
9
6
  */
10
7
 
@@ -70,6 +67,9 @@ function saveCanvas(canvas, filename='screenshot', type='image/png')
70
67
  * @memberof Utilities */
71
68
  function saveDataURL(url, filename='download', revokeTime)
72
69
  {
70
+ ASSERT(isString(url), 'saveDataURL requires url string');
71
+ ASSERT(isString(filename), 'saveDataURL requires filename string');
72
+
73
73
  // create link for saving screenshots
74
74
  const link = document.createElement('a');
75
75
  link.download = filename;
@@ -77,4 +77,100 @@ function saveDataURL(url, filename='download', revokeTime)
77
77
  link.click();
78
78
  if (revokeTime !== undefined)
79
79
  setTimeout(()=> URL.revokeObjectURL(url), revokeTime);
80
+ }
81
+
82
+ /** Share content using the native share dialog if available
83
+ * @param {string} title - title of the share
84
+ * @param {string} url - url to share
85
+ * @param {Function} [callback] - Called when share is complete
86
+ * @memberof Utilities */
87
+ function shareURL(title, url, callback)
88
+ {
89
+ ASSERT(isString(title), 'shareURL requires title string');
90
+ ASSERT(isString(url), 'shareURL requires url string');
91
+ navigator.share?.({title, url}).then(()=>callback?.());
92
+ }
93
+
94
+ ///////////////////////////////////////////////////////////////////////////////
95
+
96
+ /**
97
+ * Timer object tracks how long has passed since it was set
98
+ * @memberof Engine
99
+ * @example
100
+ * let a = new Timer; // creates a timer that is not set
101
+ * a.set(3); // sets the timer to 3 seconds
102
+ *
103
+ * let b = new Timer(1); // creates a timer with 1 second left
104
+ * b.unset(); // unset the timer
105
+ */
106
+ class Timer
107
+ {
108
+ /** Create a timer object set time passed in
109
+ * @param {number} [timeLeft] - How much time left before the timer
110
+ * @param {boolean} [useRealTime] - Should the timer keep running even when the game is paused? (useful for UI) */
111
+ constructor(timeLeft, useRealTime=false)
112
+ {
113
+ ASSERT(timeLeft === undefined || isNumber(timeLeft), 'Constructed Timer is invalid.', timeLeft);
114
+ this.useRealTime = useRealTime;
115
+ const globalTime = this.getGlobalTime();
116
+ this.time = timeLeft === undefined ? undefined : globalTime + timeLeft;
117
+ this.setTime = timeLeft;
118
+ }
119
+
120
+ /** Set the timer with seconds passed in
121
+ * @param {number} [timeLeft] - How much time left before the timer is elapsed in seconds */
122
+ set(timeLeft=0)
123
+ {
124
+ ASSERT(isNumber(timeLeft), 'Timer is invalid.', timeLeft);
125
+ const globalTime = this.getGlobalTime();
126
+ this.time = globalTime + timeLeft;
127
+ this.setTime = timeLeft;
128
+ }
129
+
130
+ /** Set if the timer should keep running even when the game is paused
131
+ * @param {boolean} [useRealTime] */
132
+ setUseRealTime(useRealTime=true)
133
+ {
134
+ ASSERT(!this.isSet(), 'Cannot change global time setting while timer is set.');
135
+ this.useRealTime = useRealTime;
136
+ }
137
+
138
+ /** Unset the timer */
139
+ unset() { this.time = undefined; }
140
+
141
+ /** Returns true if set
142
+ * @return {boolean} */
143
+ isSet() { return this.time !== undefined; }
144
+
145
+ /** Returns true if set and has not elapsed
146
+ * @return {boolean} */
147
+ active() { return this.getGlobalTime() < this.time; }
148
+
149
+ /** Returns true if set and elapsed
150
+ * @return {boolean} */
151
+ elapsed() { return this.getGlobalTime() >= this.time; }
152
+
153
+ /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
154
+ * @return {number} */
155
+ get() { return this.isSet()? this.getGlobalTime() - this.time : 0; }
156
+
157
+ /** Get percentage elapsed based on time it was set to, returns 0 if not set
158
+ * @return {number} */
159
+ getPercent() { return this.isSet()? 1-percent(this.time - this.getGlobalTime(), 0, this.setTime) : 0; }
160
+
161
+ /** Get the time this timer was set to, returns 0 if not set
162
+ * @return {number} */
163
+ getSetTime() { return this.isSet() ? this.setTime : 0; }
164
+
165
+ /** Get the current global time this timer is based on
166
+ * @return {number} */
167
+ getGlobalTime() { return this.useRealTime ? timeReal : time; }
168
+
169
+ /** Returns this timer expressed as a string
170
+ * @return {string} */
171
+ toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
172
+
173
+ /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
174
+ * @return {number} */
175
+ valueOf() { return this.get(); }
80
176
  }