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
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
  {
@@ -172,48 +173,54 @@ class TileInfo
172
173
  }
173
174
 
174
175
  /**
175
- * Set this tile to use a full image
176
- * @param {HTMLImageElement|OffscreenCanvas} image
177
- * @param {WebGLTexture} [glTexture] - WebGL texture
176
+ * Set this tile to use a full image in a texture info
177
+ * @param {TextureInfo} textureInfo
178
178
  * @return {TileInfo}
179
179
  */
180
- setFullImage(image, glTexture)
180
+ setFullImage(textureInfo)
181
181
  {
182
182
  this.pos = new Vector2;
183
- this.size = new Vector2(image.width, image.height);
184
- this.textureInfo = new TextureInfo(image, glTexture);
183
+ this.size = textureInfo.size.copy();
184
+ this.textureInfo = textureInfo;
185
185
  // do not use padding or bleed
186
186
  this.bleedScale = this.padding = 0;
187
187
  return this;
188
188
  }
189
189
  }
190
190
 
191
- /** Texture Info - Stores info about each texture */
191
+ /**
192
+ * Tile Info - Stores info about each texture
193
+ * @memberof Draw
194
+ */
192
195
  class TextureInfo
193
196
  {
194
197
  /**
195
198
  * Create a TextureInfo, called automatically by the engine
196
199
  * @param {HTMLImageElement|OffscreenCanvas} image
197
- * @param {WebGLTexture} [glTexture] - WebGL texture
200
+ * @param {boolean} [useWebGL] - Should use WebGL if available?
198
201
  */
199
- constructor(image, glTexture)
202
+ constructor(image, useWebGL=true)
200
203
  {
201
- /** @property {HTMLImageElement} - image source */
204
+ /** @property {HTMLImageElement|OffscreenCanvas} - image source */
202
205
  this.image = image;
203
206
  /** @property {Vector2} - size of the image */
204
- this.size = vec2(image.width, image.height);
207
+ this.size = image ? vec2(image.width, image.height) : vec2();
205
208
  /** @property {Vector2} - inverse of the size, cached for rendering */
206
- this.sizeInverse = vec2(1/image.width, 1/image.height);
209
+ this.sizeInverse = image ? vec2(1/image.width, 1/image.height) : vec2();
207
210
  /** @property {WebGLTexture} - WebGL texture */
208
- this.glTexture = glTexture;
211
+ this.glTexture = undefined;
212
+ useWebGL && this.createWebGLTexture();
209
213
  }
210
214
 
211
- createWebGLTexture()
212
- {
213
- ASSERT(!this.glTexture);
214
- if (glEnable)
215
- this.glTexture = glCreateTexture(this.image);
216
- }
215
+ /** Creates the WebGL texture, updates if already created */
216
+ createWebGLTexture() { glRegisterTextureInfo(this); }
217
+
218
+ /** Destroys the WebGL texture */
219
+ destroyWebGLTexture() { glUnregisterTextureInfo(this); }
220
+
221
+ /** Check if the texture is webgl enabled
222
+ * @return {boolean} */
223
+ hasWebGL() { return !!this.glTexture; }
217
224
  }
218
225
 
219
226
  ///////////////////////////////////////////////////////////////////////////////
@@ -234,10 +241,11 @@ class TextureInfo
234
241
  function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
235
242
  angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
236
243
  {
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');
244
+ ASSERT(isVector2(pos), 'pos must be a vec2');
245
+ ASSERT(isVector2(size), 'size must be a vec2');
246
+ ASSERT(isColor(color), 'color is invalid');
247
+ ASSERT(isNumber(angle), 'angle must be a number');
248
+ ASSERT(!additiveColor || isColor(additiveColor), 'additiveColor must be a color');
241
249
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
242
250
 
243
251
  const textureInfo = tileInfo && tileInfo.textureInfo;
@@ -332,10 +340,10 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
332
340
  * @memberof Draw */
333
341
  function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0, useWebGL=glEnable, screenSpace=false, context)
334
342
  {
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');
343
+ ASSERT(isVector2(pos), 'pos must be a vec2');
344
+ ASSERT(isVector2(size), 'size must be a vec2');
345
+ ASSERT(isColor(colorTop) && isColor(colorBottom), 'color is invalid');
346
+ ASSERT(isNumber(angle), 'angle must be a number');
339
347
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
340
348
  if (useWebGL)
341
349
  {
@@ -393,11 +401,11 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
393
401
  * @memberof Draw */
394
402
  function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace, context)
395
403
  {
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');
404
+ ASSERT(Array.isArray(points), 'points must be an array');
405
+ ASSERT(isNumber(width), 'width must be a number');
406
+ ASSERT(isColor(color), 'color is invalid');
407
+ ASSERT(isVector2(pos), 'pos must be a vec2');
408
+ ASSERT(isNumber(angle), 'angle must be a number');
401
409
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
402
410
  if (useWebGL)
403
411
  {
@@ -468,8 +476,8 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
468
476
  * @memberof Draw */
469
477
  function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, lineColor=BLACK, angle=0, useWebGL=glEnable, screenSpace=false, context)
470
478
  {
471
- ASSERT(isVector2(size), 'drawRegularPoly size should be a vec2');
472
- ASSERT(isNumber(sides), 'drawRegularPoly sides should be a number');
479
+ ASSERT(isVector2(size), 'size must be a vec2');
480
+ ASSERT(isNumber(sides), 'sides must be a number');
473
481
 
474
482
  // build regular polygon points
475
483
  const points = [];
@@ -495,12 +503,13 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
495
503
  * @memberof Draw */
496
504
  function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace=false, context=undefined)
497
505
  {
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');
506
+ ASSERT(isVector2(pos), 'pos must be a vec2');
507
+ ASSERT(Array.isArray(points), 'points must be an array');
508
+ ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
509
+ ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
510
+ ASSERT(isNumber(angle), 'angle must be a number');
503
511
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
512
+
504
513
  if (useWebGL)
505
514
  {
506
515
  let scale = 1;
@@ -547,13 +556,14 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
547
556
  * @memberof Draw */
548
557
  function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
549
558
  {
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');
559
+ ASSERT(isVector2(pos), 'pos must be a vec2');
560
+ ASSERT(isVector2(size), 'size must be a vec2');
561
+ ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
562
+ ASSERT(isNumber(angle), 'angle must be a number');
563
+ ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
564
+ ASSERT(lineWidth >= 0 && lineWidth < size.x && lineWidth < size.y, 'invalid lineWidth');
556
565
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
566
+
557
567
  if (useWebGL)
558
568
  {
559
569
  // draw as a regular polygon
@@ -590,21 +600,32 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
590
600
  * @memberof Draw */
591
601
  function drawCircle(pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
592
602
  {
593
- ASSERT(isNumber(size), 'drawCircle size should be a number');
603
+ ASSERT(isNumber(size), 'size must be a number');
594
604
  drawEllipse(pos, vec2(size), color, 0, lineWidth, lineColor, useWebGL, screenSpace, context);
595
605
  }
596
606
 
607
+ /**
608
+ * @callback Canvas2DDrawFunction - A function that draws to a 2D canvas context
609
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
610
+ * @memberof Draw
611
+ */
612
+
597
613
  /** Draw directly to a 2d canvas context in world space
598
614
  * @param {Vector2} pos
599
615
  * @param {Vector2} size
600
616
  * @param {number} angle
601
617
  * @param {boolean} [mirror]
602
- * @param {Function} [drawFunction]
618
+ * @param {Canvas2DDrawFunction} [drawFunction]
603
619
  * @param {boolean} [screenSpace=false]
604
620
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
605
621
  * @memberof Draw */
606
622
  function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpace=false, context=drawContext)
607
623
  {
624
+ ASSERT(isVector2(pos), 'pos must be a vec2');
625
+ ASSERT(isVector2(size), 'size must be a vec2');
626
+ ASSERT(isNumber(angle), 'angle must be a number');
627
+ ASSERT(typeof drawFunction === 'function', 'drawFunction must be a function');
628
+
608
629
  if (!screenSpace)
609
630
  {
610
631
  // transform from world space to screen space
@@ -632,12 +653,13 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
632
653
  * @param {Color} [lineColor=(0,0,0,1)]
633
654
  * @param {CanvasTextAlign} [textAlign='center']
634
655
  * @param {string} [font=fontDefault]
656
+ * @param {string} [fontStyle]
635
657
  * @param {number} [maxWidth]
636
658
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
637
659
  * @memberof Draw */
638
- function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, maxWidth, context=drawContext)
660
+ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, context=drawContext)
639
661
  {
640
- drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, maxWidth, context);
662
+ drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, fontStyle, maxWidth, context);
641
663
  }
642
664
 
643
665
  /** Draw text on overlay canvas in world space
@@ -650,11 +672,12 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
650
672
  * @param {Color} [lineColor=(0,0,0,1)]
651
673
  * @param {CanvasTextAlign} [textAlign='center']
652
674
  * @param {string} [font=fontDefault]
675
+ * @param {string} [fontStyle]
653
676
  * @param {number} [maxWidth]
654
677
  * @memberof Draw */
655
- function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, maxWidth)
678
+ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth)
656
679
  {
657
- drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, maxWidth, overlayContext);
680
+ drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, overlayContext);
658
681
  }
659
682
 
660
683
  /** Draw text on overlay canvas in screen space
@@ -667,16 +690,27 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
667
690
  * @param {Color} [lineColor=(0,0,0,1)]
668
691
  * @param {CanvasTextAlign} [textAlign]
669
692
  * @param {string} [font=fontDefault]
693
+ * @param {string} [fontStyle]
670
694
  * @param {number} [maxWidth]
671
695
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
672
696
  * @memberof Draw */
673
- function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, maxWidth, context=overlayContext)
697
+ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, context=overlayContext)
674
698
  {
699
+ ASSERT(isString(text), 'text must be a string');
700
+ ASSERT(isVector2(pos), 'pos must be a vec2');
701
+ ASSERT(isNumber(size), 'size must be a number');
702
+ ASSERT(isColor(color), 'color must be a color');
703
+ ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
704
+ ASSERT(isColor(lineColor), 'lineColor must be a color');
705
+ ASSERT(['left','center','right'].includes(textAlign), 'align must be left, center, or right');
706
+ ASSERT(isString(font), 'font must be a string');
707
+ ASSERT(isString(fontStyle), 'fontStyle must be a string');
708
+
675
709
  context.fillStyle = color.toString();
676
710
  context.strokeStyle = lineColor.toString();
677
711
  context.lineWidth = lineWidth;
678
712
  context.textAlign = textAlign;
679
- context.font = size + 'px '+ font;
713
+ context.font = fontStyle + ' ' + size + 'px '+ font;
680
714
  context.textBaseline = 'middle';
681
715
 
682
716
  const lines = (text+'').split('\n');
@@ -873,6 +907,7 @@ let engineFontImage;
873
907
  * - 96 characters (from space to tilde) are stored in an image
874
908
  * - Uses a default 8x8 font if none is supplied
875
909
  * - You can also use fonts from the main tile sheet
910
+ * @memberof Draw
876
911
  * @example
877
912
  * // use built in font
878
913
  * const font = new FontImage;
@@ -883,11 +918,11 @@ let engineFontImage;
883
918
  class FontImage
884
919
  {
885
920
  /** Create an image font
886
- * @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
887
- * @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
888
- * @param {Vector2} [paddingSize=(0,1)] - How much extra space to add between characters
921
+ * @param {HTMLImageElement} [image] - Image for the font, default if undefined
922
+ * @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
923
+ * @param {Vector2} [paddingSize=(0,1)] - How much space between characters
889
924
  */
890
- constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), context=overlayContext)
925
+ constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1))
891
926
  {
892
927
  // load default font image
893
928
  if (!engineFontImage)
@@ -929,7 +964,7 @@ class FontImage
929
964
  * @param {boolean} [center]
930
965
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
931
966
  */
932
- drawTextScreen(text, pos, scale=4, center, context=overlayContext)
967
+ drawTextScreen(text, pos, scale=4, center=true, context=overlayContext)
933
968
  {
934
969
  context.save();
935
970
  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,
@@ -59,8 +59,14 @@ function medalsInit(saveName)
59
59
  }
60
60
  }
61
61
 
62
+ /**
63
+ * @callback MedalCallbackFunction - Function that processes a medal
64
+ * @param {Medal} medal
65
+ * @memberof Medals
66
+ */
67
+
62
68
  /** Calls a function for each medal
63
- * @param {Function} callback
69
+ * @param {MedalCallbackFunction} callback
64
70
  * @memberof Medals */
65
71
  function medalsForEach(callback)
66
72
  { Object.values(medals).forEach(medal=>callback(medal)); }
@@ -69,6 +75,7 @@ function medalsForEach(callback)
69
75
 
70
76
  /**
71
77
  * Medal - Tracks an unlockable medal
78
+ * @memberof Medals
72
79
  * @example
73
80
  * // create a medal
74
81
  * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
@@ -137,11 +144,12 @@ class Medal
137
144
  const height = medalDisplaySize.y;
138
145
  const x = overlayCanvas.width - width;
139
146
  const y = -height*hidePercent;
147
+ const backgroundColor = hsl(0,0,.9);
140
148
 
141
149
  // draw containing rect and clip to that region
142
150
  context.save();
143
151
  context.beginPath();
144
- context.fillStyle = new Color(.9,.9,.9).toString();
152
+ context.fillStyle = backgroundColor.toString();
145
153
  context.strokeStyle = BLACK.toString();
146
154
  context.lineWidth = 3;
147
155
  context.rect(x, y, width, height);
@@ -24,6 +24,7 @@
24
24
  * - Collision for objects can be set to be solid to block other objects
25
25
  * - Objects may get pushed into overlapping other solid objects, if so they will push away
26
26
  * - Solid objects are more performance intensive and should be used sparingly
27
+ * @memberof Engine
27
28
  * @example
28
29
  * // create an engine object, normally you would first extend the class with your own
29
30
  * const pos = vec2(2,3);
@@ -32,18 +33,18 @@
32
33
  class EngineObject
33
34
  {
34
35
  /** Create an engine object and adds it to the list of objects
35
- * @param {Vector2} [pos=(0,0)] - World space position of the object
36
- * @param {Vector2} [size=(1,1)] - World space size of the object
37
- * @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
38
- * @param {number} [angle] - Angle the object is rotated by
39
- * @param {Color} [color=(1,1,1,1)] - Color to apply to tile when rendered
40
- * @param {number} [renderOrder] - Objects sorted by renderOrder before being rendered
36
+ * @param {Vector2} [pos=(0,0)] - World space position of the object
37
+ * @param {Vector2} [size=(1,1)] - World space size of the object
38
+ * @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
39
+ * @param {number} [angle] - Angle the object is rotated by
40
+ * @param {Color} [color=WHITE] - Color to apply to tile when rendered
41
+ * @param {number} [renderOrder] - Objects sorted by renderOrder before being rendered
41
42
  */
42
- constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color=new Color, renderOrder=0)
43
+ constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color=WHITE, renderOrder=0)
43
44
  {
44
45
  // check passed in params
45
- ASSERT(isVector2(pos), 'object pos should be a vec2');
46
- ASSERT(isVector2(size), 'object size should be a vec2');
46
+ ASSERT(isVector2(pos), 'object pos must be a vec2');
47
+ ASSERT(isVector2(size), 'object size must be a vec2');
47
48
  ASSERT(!tileInfo || tileInfo instanceof TileInfo, 'object tileInfo should be a TileInfo or undefined');
48
49
  ASSERT(typeof angle === 'number' && isFinite(angle), 'object angle should be a number');
49
50
  ASSERT(isColor(color), 'object color should be a valid rgba color');
@@ -328,7 +329,7 @@ class EngineObject
328
329
  drawTile(this.pos, this.drawSize || this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
329
330
  }
330
331
 
331
- /** Destroy this object, destroy its children, detach it's parent, and mark it for removal */
332
+ /** Destroy this object, destroy its children, detach its parent, and mark it for removal */
332
333
  destroy()
333
334
  {
334
335
  if (this.destroyed)
@@ -402,6 +403,8 @@ class EngineObject
402
403
  addChild(child, localPos=vec2(), localAngle=0)
403
404
  {
404
405
  ASSERT(!child.parent && !this.children.includes(child));
406
+ ASSERT(child instanceof EngineObject, 'child must be an EngineObject');
407
+ ASSERT(child !== this, 'cannot add self as child');
405
408
  this.children.push(child);
406
409
  child.parent = this;
407
410
  child.localPos = localPos.copy();
@@ -413,6 +416,7 @@ class EngineObject
413
416
  removeChild(child)
414
417
  {
415
418
  ASSERT(child.parent === this && this.children.includes(child));
419
+ ASSERT(child instanceof EngineObject, 'child must be an EngineObject');
416
420
  this.children.splice(this.children.indexOf(child), 1);
417
421
  child.parent = 0;
418
422
  }
@@ -4,9 +4,16 @@
4
4
 
5
5
  'use strict';
6
6
 
7
+ /**
8
+ * @callback ParticleCallbackFunction - Function that processes a particle
9
+ * @param {Particle} particle
10
+ * @memberof Engine
11
+ */
12
+
7
13
  /**
8
14
  * Particle Emitter - Spawns particles with the given settings
9
15
  * @extends EngineObject
16
+ * @memberof Engine
10
17
  * @example
11
18
  * // create a particle emitter
12
19
  * let pos = vec2(2,3);
@@ -16,7 +23,7 @@
16
23
  * tile(0, 16), // tileInfo
17
24
  * rgb(1,1,1,1), rgb(0,0,0,1), // colorStartA, colorStartB
18
25
  * rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
19
- * 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
26
+ * 1, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
20
27
  * .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
21
28
  * .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
22
29
  * );
@@ -31,10 +38,10 @@ class ParticleEmitter extends EngineObject
31
38
  * @param {number} [emitRate] - How many particles per second to spawn, does not emit if 0
32
39
  * @param {number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
33
40
  * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
34
- * @param {Color} [colorStartA=(1,1,1,1)] - Color at start of life 1, randomized between start colors
35
- * @param {Color} [colorStartB=(1,1,1,1)] - Color at start of life 2, randomized between start colors
36
- * @param {Color} [colorEndA=(1,1,1,0)] - Color at end of life 1, randomized between end colors
37
- * @param {Color} [colorEndB=(1,1,1,0)] - Color at end of life 2, randomized between end colors
41
+ * @param {Color} [colorStartA=WHITE] - Color at start of life 1, randomized between start colors
42
+ * @param {Color} [colorStartB=WHITE] - Color at start of life 2, randomized between start colors
43
+ * @param {Color} [colorEndA=CLEAR_WHITE] - Color at end of life 1, randomized between end colors
44
+ * @param {Color} [colorEndB=CLEAR_WHITE] - Color at end of life 2, randomized between end colors
38
45
  * @param {number} [particleTime] - How long particles live
39
46
  * @param {number} [sizeStart] - How big are particles at start
40
47
  * @param {number} [sizeEnd] - How big are particles at end
@@ -61,10 +68,10 @@ class ParticleEmitter extends EngineObject
61
68
  emitRate = 100,
62
69
  emitConeAngle = PI,
63
70
  tileInfo,
64
- colorStartA = new Color,
65
- colorStartB = new Color,
66
- colorEndA = new Color(1,1,1,0),
67
- colorEndB = new Color(1,1,1,0),
71
+ colorStartA = WHITE,
72
+ colorStartB = WHITE,
73
+ colorEndA = CLEAR_WHITE,
74
+ colorEndB = CLEAR_WHITE,
68
75
  particleTime = .5,
69
76
  sizeStart = .1,
70
77
  sizeEnd = 1,
@@ -87,7 +94,8 @@ class ParticleEmitter extends EngineObject
87
94
 
88
95
  // emitter settings
89
96
  /** @property {number|Vector2} - World space size of the emitter (float for circle diameter, vec2 for rect) */
90
- this.emitSize = emitSize
97
+ this.emitSize = emitSize instanceof Vector2 ?
98
+ emitSize.copy() : emitSize;
91
99
  /** @property {number} - How long to stay alive (0 is forever) */
92
100
  this.emitTime = emitTime;
93
101
  /** @property {number} - How many particles per second to spawn, does not emit if 0 */
@@ -97,13 +105,13 @@ class ParticleEmitter extends EngineObject
97
105
 
98
106
  // color settings
99
107
  /** @property {Color} - Color at start of life 1, randomized between start colors */
100
- this.colorStartA = colorStartA;
108
+ this.colorStartA = colorStartA.copy();
101
109
  /** @property {Color} - Color at start of life 2, randomized between start colors */
102
- this.colorStartB = colorStartB;
110
+ this.colorStartB = colorStartB.copy();
103
111
  /** @property {Color} - Color at end of life 1, randomized between end colors */
104
- this.colorEndA = colorEndA;
112
+ this.colorEndA = colorEndA.copy();
105
113
  /** @property {Color} - Color at end of life 2, randomized between end colors */
106
- this.colorEndB = colorEndB;
114
+ this.colorEndB = colorEndB.copy();
107
115
  /** @property {boolean} - Should color be randomized linearly or across each component */
108
116
  this.randomColorLinear = randomColorLinear;
109
117
 
@@ -138,9 +146,9 @@ class ParticleEmitter extends EngineObject
138
146
  this.localSpace = localSpace;
139
147
  /** @property {number} - If non zero the particle is drawn as a trail, stretched in the direction of velocity */
140
148
  this.trailScale = 0;
141
- /** @property {Function} - Callback when particle is destroyed */
149
+ /** @property {ParticleCallbackFunction} - Callback when particle is destroyed */
142
150
  this.particleDestroyCallback = undefined;
143
- /** @property {Function} - Callback when particle is created */
151
+ /** @property {ParticleCallbackFunction} - Callback when particle is created */
144
152
  this.particleCreateCallback = undefined;
145
153
  /** @property {number} - Track particle emit time */
146
154
  this.emitTimeBuffer = 0;
@@ -241,6 +249,7 @@ class ParticleEmitter extends EngineObject
241
249
  /**
242
250
  * Particle Object - Created automatically by Particle Emitters
243
251
  * @extends EngineObject
252
+ * @memberof Engine
244
253
  */
245
254
  class Particle extends EngineObject
246
255
  {
@@ -259,7 +268,7 @@ class Particle extends EngineObject
259
268
  * @param {boolean} additive - Does it use additive blend mode
260
269
  * @param {number} trailScale - If a trail, how long to make it
261
270
  * @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
262
- * @param {Function} [destroyCallback] - Callback when particle dies
271
+ * @param {ParticleCallbackFunction} [destroyCallback] - Callback when particle dies
263
272
  */
264
273
  constructor(position, tileInfo, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, fadeRate, additive, trailScale, localSpaceEmitter, destroyCallback
265
274
  )
@@ -268,14 +277,14 @@ class Particle extends EngineObject
268
277
 
269
278
  /** @property {Color} - Color at start of life */
270
279
  this.colorStart = colorStart;
271
- /** @property {Color} - Calculated change in color */
272
- this.colorEndDelta = colorEnd.subtract(colorStart);
280
+ /** @property {Color} - Color at end of life */
281
+ this.colorEnd = colorEnd;
273
282
  /** @property {number} - How long to live for */
274
283
  this.lifeTime = lifeTime;
275
284
  /** @property {number} - Size at start of life */
276
285
  this.sizeStart = sizeStart;
277
- /** @property {number} - Calculated change in size */
278
- this.sizeEndDelta = sizeEnd - sizeStart;
286
+ /** @property {number} - Size at end of life */
287
+ this.sizeEnd = sizeEnd;
279
288
  /** @property {number} - How quick to fade in/out */
280
289
  this.fadeRate = fadeRate;
281
290
  /** @property {boolean} - Is it additive */
@@ -284,7 +293,7 @@ class Particle extends EngineObject
284
293
  this.trailScale = trailScale;
285
294
  /** @property {ParticleEmitter} - Parent emitter if local space */
286
295
  this.localSpaceEmitter = localSpaceEmitter;
287
- /** @property {Function} - Called when particle dies */
296
+ /** @property {ParticleCallbackFunction} - Called when particle dies */
288
297
  this.destroyCallback = destroyCallback;
289
298
  // particles do not clamp speed by default
290
299
  this.clampSpeed = false;
@@ -311,54 +320,58 @@ class Particle extends EngineObject
311
320
  /** Render the particle, automatically called each frame, sorted by renderOrder */
312
321
  render()
313
322
  {
314
- // modulate size and color
315
- const p = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1;
316
- const radius = this.sizeStart + p * this.sizeEndDelta;
317
- const size = vec2(radius);
323
+ // lerp color and size
324
+ const p1 = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1, p2 = 1-p1;
325
+ const radius = p2 * this.sizeStart + p1 * this.sizeEnd;
326
+ this.size.x = this.size.y = radius;
327
+ this.color.r = p2 * this.colorStart.r + p1 * this.colorEnd.r;
328
+ this.color.g = p2 * this.colorStart.g + p1 * this.colorEnd.g;
329
+ this.color.b = p2 * this.colorStart.b + p1 * this.colorEnd.b;
330
+ this.color.a = p2 * this.colorStart.a + p1 * this.colorEnd.a;
331
+
332
+ // fade alpha
318
333
  const fadeRate = this.fadeRate/2;
319
- const color = new Color(
320
- this.colorStart.r + p * this.colorEndDelta.r,
321
- this.colorStart.g + p * this.colorEndDelta.g,
322
- this.colorStart.b + p * this.colorEndDelta.b,
323
- (this.colorStart.a + p * this.colorEndDelta.a) *
324
- (p < fadeRate ? p/fadeRate : p > 1-fadeRate ? (1-p)/fadeRate : 1)); // fade alpha
334
+ this.color.a *= p1 < fadeRate ? p1/fadeRate :
335
+ p1 > 1-fadeRate ? (1-p1)/fadeRate : 1;
325
336
 
326
337
  // draw the particle
327
338
  this.additive && setBlendMode(true);
328
339
 
340
+ // update the position and angle for drawing
329
341
  let pos = this.pos, angle = this.angle;
330
342
  if (this.localSpaceEmitter)
331
343
  {
332
344
  // in local space of emitter
333
- pos = this.localSpaceEmitter.pos.add(pos.rotate(-this.localSpaceEmitter.angle));
345
+ const a = this.localSpaceEmitter.angle;
346
+ const c = Math.cos(a), s = Math.sin(a);
347
+ pos = this.localSpaceEmitter.pos.add(
348
+ new Vector2(pos.x*c - pos.y*s, pos.x*s + pos.y*c));
334
349
  angle += this.localSpaceEmitter.angle;
335
350
  }
336
351
  if (this.trailScale)
337
352
  {
338
353
  // trail style particles
339
- let velocity = this.velocity;
340
- if (this.localSpaceEmitter)
341
- velocity = velocity.rotate(-this.localSpaceEmitter.angle);
342
- const speed = velocity.length();
354
+ const direction = this.localSpaceEmitter ?
355
+ this.velocity.rotate(-this.localSpaceEmitter.angle) :
356
+ this.velocity;
357
+ const speed = direction.length();
343
358
  if (speed)
344
359
  {
345
- const direction = velocity.scale(1/speed);
360
+ // stretch in direction of motion
346
361
  const trailLength = speed * this.trailScale;
347
- size.y = max(size.x, trailLength);
348
- angle = direction.angle();
349
- drawTile(pos.add(direction.multiply(vec2(0,-trailLength/2))), size, this.tileInfo, color, angle, this.mirror);
362
+ this.size.y = max(this.size.x, trailLength);
363
+ angle = Math.atan2(direction.x, direction.y);
364
+ drawTile(pos, this.size, this.tileInfo, this.color, angle, this.mirror);
350
365
  }
351
366
  }
352
367
  else
353
- drawTile(pos, size, this.tileInfo, color, angle, this.mirror);
368
+ drawTile(pos, this.size, this.tileInfo, this.color, angle, this.mirror);
354
369
  this.additive && setBlendMode();
355
- debugParticles && debugRect(pos, size, '#f005', 0, angle);
370
+ debugParticles && debugRect(pos, this.size, '#f005', 0, angle);
356
371
 
357
- if (p === 1)
372
+ if (p1 === 1)
358
373
  {
359
- // destroy particle when it's time runs out
360
- this.color = color;
361
- this.size = size;
374
+ // destroy particle when its time runs out
362
375
  this.destroyCallback && this.destroyCallback(this);
363
376
  this.destroyed = 1;
364
377
  }