littlejsengine 1.16.2 → 1.17.5

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 (57) hide show
  1. package/dist/littlejs.d.ts +291 -246
  2. package/dist/littlejs.esm.js +1520 -1271
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +1178 -920
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +1170 -912
  7. package/examples/box2d/gameObjects.js +4 -4
  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 +9 -8
  13. package/examples/logo.png +0 -0
  14. package/examples/module/build.bat +7 -0
  15. package/examples/module/build.js +121 -0
  16. package/examples/module/tiles.png +0 -0
  17. package/examples/platformer/game.js +1 -1
  18. package/examples/platformer/gameEffects.js +25 -29
  19. package/examples/platformer/gameLevel.js +27 -28
  20. package/examples/puzzle/game.js +2 -2
  21. package/examples/shorts/base.html +4 -1
  22. package/examples/shorts/box2dPool.js +2 -2
  23. package/examples/shorts/box2dTileLayer.js +48 -0
  24. package/examples/shorts/clock.js +3 -3
  25. package/examples/shorts/fontImage.js +4 -3
  26. package/examples/shorts/music.js +2 -2
  27. package/examples/shorts/musicPlayer.js +2 -2
  28. package/examples/shorts/parallax.js +1 -1
  29. package/examples/shorts/sequencer.js +1 -1
  30. package/examples/shorts/shapes.js +1 -1
  31. package/examples/shorts/texture.js +10 -6
  32. package/examples/shorts/tiles.png +0 -0
  33. package/examples/shorts/tiltedView.js +2 -0
  34. package/examples/starter/index.html +2 -2
  35. package/examples/starter/tiles.png +0 -0
  36. package/examples/typescript/tiles.png +0 -0
  37. package/examples/uiSystem/game.js +1 -1
  38. package/examples/uiSystem/tiles.png +0 -0
  39. package/package.json +5 -5
  40. package/plugins/box2d.js +167 -63
  41. package/plugins/postProcess.js +47 -28
  42. package/plugins/uiSystem.js +18 -18
  43. package/plugins/zzfxm.js +2 -2
  44. package/reference.md +4 -3
  45. package/src/engine.js +182 -181
  46. package/src/engineAudio.js +47 -63
  47. package/src/engineDebug.js +8 -8
  48. package/src/engineDraw.js +184 -124
  49. package/src/engineExport.js +325 -334
  50. package/src/engineFont.png +0 -0
  51. package/src/engineInput.js +18 -24
  52. package/src/engineMath.js +24 -113
  53. package/src/engineObject.js +27 -26
  54. package/src/engineParticles.js +2 -2
  55. package/src/engineTileLayer.js +229 -192
  56. package/src/engineUtilities.js +100 -4
  57. package/src/engineWebGL.js +124 -73
@@ -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
@@ -183,29 +183,27 @@ class TileLayerData
183
183
  class CanvasLayer extends EngineObject
184
184
  {
185
185
  /** Create a canvas layer object
186
- * @param {Vector2} [position] - World space position of the layer
186
+ * @param {Vector2} [pos] - World space position of the layer
187
187
  * @param {Vector2} [size] - World space size of the layer
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(pos, size, angle=0, renderOrder=0, canvasSize=vec2(512), useWebGL=true)
193
194
  {
194
195
  ASSERT(isVector2(canvasSize), 'canvasSize must be a Vector2');
195
- super(position, size, undefined, angle, WHITE, renderOrder);
196
+ super(pos, size, undefined, angle, WHITE, renderOrder);
196
197
 
197
198
  /** @property {HTMLCanvasElement} - The canvas used by this layer */
198
199
  this.canvas = headlessMode ? undefined : new OffscreenCanvas(canvasSize.x, canvasSize.y);
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,59 +297,93 @@ 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} pos - 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
304
+ * @param {boolean} [useWebGL] - Should this layer use WebGL for rendering
355
305
  */
356
- constructor(position, size, tileInfo=tile(), renderOrder=0)
306
+ constructor(pos, size, tileInfo=tile(), renderOrder=0, useWebGL=true)
357
307
  {
358
308
  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
309
+ super(pos, 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
 
363
+ /** Clear data at a given position in the array
364
+ * @param {Vector2} layerPos - Local position in array
365
+ * @param {boolean} [redraw] - Force the tile to redraw if true */
366
+ clearData(layerPos, redraw=false)
367
+ { this.setData(layerPos, new TileLayerData, redraw) }
368
+
397
369
  /** Get data at a given position in the array
398
370
  * @param {Vector2} layerPos - Local position in array
399
371
  * @return {TileLayerData} */
400
372
  getData(layerPos)
401
373
  {
402
374
  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];
375
+ return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0];
376
+ }
377
+
378
+ // Update the tile layer, refresh texture if needed
379
+ update()
380
+ {
381
+ if (!glEnable && this.isUsingWebGL)
382
+ {
383
+ // redraw the layer if webgl was disabled or context lost
384
+ this.isUsingWebGL = false;
385
+ this.redraw();
386
+ }
404
387
  }
405
388
 
406
389
  // Render the tile layer, called automatically by the engine
@@ -408,40 +391,35 @@ class TileLayer extends CanvasLayer
408
391
  {
409
392
  ASSERT(drawContext !== this.context, 'must call redrawEnd() after drawing tiles!');
410
393
 
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
394
  const size = this.drawSize || this.size;
421
395
  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);
396
+ this.draw(pos, this.size, this.color, this.angle, this.mirror, this.additiveColor);
424
397
  }
425
398
 
399
+ /** Called after this layer is redrawn, does nothing by default */
400
+ onRedraw() {}
401
+
426
402
  /** Draw all the tile data to an offscreen canvas
427
- * - This may be slow in some browsers but only needs to be done once */
403
+ * - This may be slow if not using webgl but only needs to be done once */
428
404
  redraw()
429
405
  {
430
406
  this.redrawStart(true);
431
407
  for (let x = this.size.x; x--;)
432
408
  for (let y = this.size.y; y--;)
433
409
  this.drawTileData(vec2(x,y), false);
410
+ this.isUsingWebGL && glFlush();
411
+ this.onRedraw();
434
412
  this.redrawEnd();
435
- this.useWebGL();
436
413
  }
437
414
 
438
415
  /** Call to start the redraw process
439
- * - This can be used to manually update small parts of the level
416
+ * - This can be used to manually update parts of the level
440
417
  * @param {boolean} [clear] - Should it clear the canvas before drawing */
441
418
  redrawStart(clear=false)
442
419
  {
443
420
  if (!this.context) return;
444
-
421
+ ASSERT(drawContext !== this.context);
422
+
445
423
  // save current render settings
446
424
  /** @type {[CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number, Color]} */
447
425
  this.savedRenderSettings = [drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor];
@@ -449,39 +427,42 @@ class TileLayer extends CanvasLayer
449
427
  // set the draw canvas and context to this layer
450
428
  // use camera settings to match this layer's canvas
451
429
  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;
430
+ const tileSize = this.tileInfo?.size ?? vec2(1);
456
431
  mainCanvasSize = this.size.multiply(tileSize);
457
- if (clear)
432
+ canvasClearColor = CLEAR_BLACK;
433
+ cameraPos = this.size.multiply(tileSize).scale(.5);
434
+ cameraScale = 1;
435
+
436
+ // set render target to this layer
437
+ this.isUsingWebGL = this.hasWebGL();
438
+ if (this.isUsingWebGL)
439
+ glSetRenderTarget(this.textureInfo.glTexture, clear);
440
+ else
458
441
  {
459
- // clear and set size
460
- this.canvas.width = mainCanvasSize.x;
461
- this.canvas.height = mainCanvasSize.y;
442
+ // disable smoothing for pixel art
443
+ this.context.imageSmoothingEnabled = !tilesPixelated;
444
+ if (clear)
445
+ {
446
+ // clear and set size
447
+ this.canvas.width = mainCanvasSize.x;
448
+ this.canvas.height = mainCanvasSize.y;
449
+ }
462
450
  }
463
-
464
- // disable smoothing for pixel art
465
- drawContext.imageSmoothingEnabled = !tilesPixelated;
466
-
467
- // setup gl rendering if enabled
468
- glPreRender();
469
451
  }
470
452
 
471
453
  /** Call to end the redraw process */
472
454
  redrawEnd()
473
455
  {
474
456
  if (!this.context) return;
475
-
476
- ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
477
- glCopyToContext(drawContext);
478
- //saveCanvas(this.canvas);
457
+ ASSERT(drawContext === this.context);
479
458
 
480
459
  // set stuff back to normal
460
+ if (this.isUsingWebGL)
461
+ glSetRenderTarget();
481
462
  [drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor] = this.savedRenderSettings;
482
463
  }
483
464
 
484
- /** Draw the tile at a given position in the tile grid
465
+ /** Draw the tile at a given position in the tile layer
485
466
  * This can be used to clear out tiles when they are destroyed
486
467
  * Tiles can also be redrawn if inside a redrawStart/End block
487
468
  * @param {Vector2} layerPos
@@ -490,24 +471,75 @@ class TileLayer extends CanvasLayer
490
471
  drawTileData(layerPos, clear=true)
491
472
  {
492
473
  if (!this.context) return;
474
+ ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
493
475
 
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
- }
476
+ // clear out where the tile was, can be skipped for fully opaque tiles
477
+ const drawSize = this.tileInfo?.size ?? vec2(1);
478
+ const drawPos = layerPos.multiply(drawSize);
479
+ clear && this.clearLayerRect(drawPos, drawSize);
501
480
 
502
481
  // draw the tile if it has layer data
503
482
  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
- }
483
+ if (!d.tile) return;
484
+
485
+ const tileInfo = this.tileInfo && this.tileInfo.tile(d.tile);
486
+ this.drawLayerTile(drawPos, drawSize, tileInfo, d.color, d.direction*PI/2, d.mirror);
487
+ }
488
+
489
+ /** Draw the tile at a given position in the tile layer
490
+ * This can be used to clear tiles when they are destroyed
491
+ * For better performance use drawTileData inside a redrawStart/End block
492
+ * @param {Vector2} layerPos
493
+ * @param {boolean} [clear] - should the old tile be cleared
494
+ */
495
+ redrawTileData(layerPos, clear=true)
496
+ {
497
+ if (!this.context) return;
498
+ ASSERT(drawContext !== this.context, 'redrawStart() should not be active when calling redrawTileData(), instead use drawTileData()');
499
+
500
+ this.redrawStart();
501
+ this.drawTileData(layerPos, clear);
502
+ this.redrawEnd();
503
+ }
504
+
505
+ /** Draw textured tile in layer space
506
+ * @param {Vector2} pos - Position in pixel coordinates
507
+ * @param {Vector2} [size=vec2(1)] - Size of the tile
508
+ * @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
509
+ * @param {Color} [color=WHITE] - Color to modulate with
510
+ * @param {number} [angle] - Angle to rotate by
511
+ * @param {boolean} [mirror] - Is image flipped along the Y axis?
512
+ * @param {Color} [additiveColor] - Additive color to be applied if any */
513
+ drawLayerTile(pos, size=vec2(1), tileInfo, color=WHITE,
514
+ angle=0, mirror, additiveColor)
515
+ {
516
+ const drawPos = pos.add(size.scale(.5));
517
+ drawTile(drawPos, size, tileInfo, color, angle, mirror, additiveColor, this.isUsingWebGL);
518
+ }
519
+
520
+ /** Clear a rectangle in layer space
521
+ * @param {Vector2} pos
522
+ * @param {Vector2} size
523
+ * @param {Color} [color=WHITE] - Color to modulate with
524
+ * @param {number} [angle] - Angle to rotate by
525
+ */
526
+ drawLayerRect(pos, size, color, angle=0)
527
+ { this.drawLayerTile(pos, size, undefined, color, angle); }
528
+
529
+ /** Clear a rectangle in layer space
530
+ * @param {Vector2} pos - position in pixel coordinates
531
+ * @param {Vector2} size
532
+ */
533
+ clearLayerRect(pos, size)
534
+ {
535
+ ASSERT(drawContext === this.context, 'must call redrawStart() before clearing tiles');
536
+
537
+ const x = pos.x, y = this.canvas.height - pos.y - size.y;
538
+ const useWebGL = this.hasWebGL();
539
+ if (useWebGL)
540
+ glClearRect(x, y, size.x, size.y);
541
+ else
542
+ this.context.clearRect(x, y, size.x, size.y);
511
543
  }
512
544
  }
513
545
 
@@ -516,21 +548,21 @@ class TileLayer extends CanvasLayer
516
548
  * Tile Collision Layer - a tile layer with collision
517
549
  * - adds collision data and functions to TileLayer
518
550
  * - there can be multiple tile collision layers
519
- * - tile collision layers should not overlap each other
520
551
  * @extends TileLayer
521
552
  * @memberof TileLayers
522
553
  */
523
554
  class TileCollisionLayer extends TileLayer
524
555
  {
525
556
  /** Create a tile layer object
526
- * @param {Vector2} position - World space position
527
- * @param {Vector2} size - World space size
528
- * @param {TileInfo} [tileInfo] - Tile info for layer
557
+ * @param {Vector2} pos - World space position
558
+ * @param {Vector2} size - World space size
559
+ * @param {TileInfo} [tileInfo] - Tile info for layer
529
560
  * @param {number} [renderOrder] - Objects are sorted by renderOrder
561
+ * @param {boolean} [useWebGL] - Should this layer use WebGL for rendering
530
562
  */
531
- constructor(position, size, tileInfo=tile(), renderOrder=0)
563
+ constructor(pos, size, tileInfo=tile(), renderOrder=0, useWebGL=true)
532
564
  {
533
- super(position, size.floor(), tileInfo, renderOrder);
565
+ super(pos, size.floor(), tileInfo, renderOrder, useWebGL);
534
566
 
535
567
  /** @property {Array<number>} - The tile collision grid */
536
568
  this.collisionData = [];
@@ -566,29 +598,34 @@ class TileCollisionLayer extends TileLayer
566
598
  this.collisionData.fill(0);
567
599
  }
568
600
 
569
- /** Set tile collision data for a given cell in the grid
570
- * @param {Vector2} gridPos
601
+ /** Set tile collision data for a given cell in the layer
602
+ * @param {Vector2} layerPos
571
603
  * @param {number} [data] */
572
- setCollisionData(gridPos, data=1)
604
+ setCollisionData(layerPos, data=1)
573
605
  {
574
- ASSERT(isVector2(gridPos), 'gridPos must be a Vector2');
575
- const i = (gridPos.y|0)*this.size.x + gridPos.x|0;
576
- gridPos.arrayCheck(this.size) && (this.collisionData[i] = data);
606
+ ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
607
+ const i = (layerPos.y|0)*this.size.x + layerPos.x|0;
608
+ layerPos.arrayCheck(this.size) && (this.collisionData[i] = data);
577
609
  }
578
610
 
579
- /** Get tile collision data for a given cell in the grid
580
- * @param {Vector2} gridPos
611
+ /** Clear tile collision data for a given cell in the layer
612
+ * @param {Vector2} layerPos */
613
+ clearCollisionData(layerPos)
614
+ { this.setCollisionData(layerPos, 0); }
615
+
616
+ /** Get tile collision data for a given cell in the layer
617
+ * @param {Vector2} layerPos
581
618
  * @return {number} */
582
- getCollisionData(gridPos)
619
+ getCollisionData(layerPos)
583
620
  {
584
- ASSERT(isVector2(gridPos), 'gridPos must be a Vector2');
585
- const i = (gridPos.y|0)*this.size.x + gridPos.x|0;
586
- return gridPos.arrayCheck(this.size) ? this.collisionData[i] : 0;
621
+ ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
622
+ const i = (layerPos.y|0)*this.size.x + layerPos.x|0;
623
+ return layerPos.arrayCheck(this.size) ? this.collisionData[i] : 0;
587
624
  }
588
625
 
589
626
  /** Check if collision with another object should occur
590
627
  * @param {Vector2} pos
591
- * @param {Vector2} [size=(0,0)]
628
+ * @param {Vector2} [size=vec2()]
592
629
  * @param {EngineObject} [object]
593
630
  * @return {boolean} */
594
631
  collisionTest(pos, size=new Vector2, object)