littlejsengine 1.15.9 → 1.16.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 (48) hide show
  1. package/dist/littlejs.d.ts +66 -83
  2. package/dist/littlejs.esm.js +318 -330
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +306 -319
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +229 -242
  7. package/examples/box2d/game.js +1 -1
  8. package/examples/breakout/game.js +3 -3
  9. package/examples/electron/game.js +1 -2
  10. package/examples/electron/index.html +2 -2
  11. package/examples/htmlMenu/game.js +0 -1
  12. package/examples/index.html +1 -1
  13. package/examples/module/game.js +1 -2
  14. package/examples/particles/index.html +1 -1
  15. package/examples/platformer/game.js +4 -4
  16. package/examples/puzzle/game.js +2 -2
  17. package/examples/shorts/base.html +3 -3
  18. package/examples/shorts/box2dPool.js +2 -2
  19. package/examples/shorts/empty.js +1 -1
  20. package/examples/shorts/{systemFont.js → fontImage.js} +3 -3
  21. package/examples/shorts/fps.js +1 -1
  22. package/examples/shorts/helloWorld.js +2 -2
  23. package/examples/shorts/nineSlice.js +2 -2
  24. package/examples/shorts/slidingPuzzle.js +1 -1
  25. package/examples/starter/game.js +1 -2
  26. package/examples/starter/index.html +2 -2
  27. package/examples/stress/index.html +1 -1
  28. package/examples/typescript/game.js +1 -2
  29. package/examples/typescript/game.ts +1 -2
  30. package/package.json +1 -1
  31. package/plugins/box2d.js +8 -8
  32. package/plugins/drawUtilities.js +6 -6
  33. package/plugins/postProcess.js +13 -20
  34. package/plugins/uiSystem.js +15 -3
  35. package/reference.md +4 -6
  36. package/src/engine.js +35 -37
  37. package/src/engineAudio.js +3 -3
  38. package/src/engineDebug.js +78 -78
  39. package/src/engineDraw.js +107 -109
  40. package/src/engineExport.js +12 -11
  41. package/src/engineInput.js +1 -1
  42. package/src/engineMedals.js +3 -3
  43. package/src/engineObject.js +4 -2
  44. package/src/engineRelease.js +1 -1
  45. package/src/engineSettings.js +20 -30
  46. package/src/engineTileLayer.js +5 -4
  47. package/src/engineUtilities.js +2 -10
  48. package/src/engineWebGL.js +6 -5
package/src/engineDraw.js CHANGED
@@ -10,7 +10,6 @@
10
10
  * There are 3 canvas/contexts available to draw to...
11
11
  * mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
12
12
  * glCanvas - Used by the accelerated WebGL batch rendering system.
13
- * overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
14
13
  *
15
14
  * The WebGL rendering system is very fast with some caveats...
16
15
  * - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
@@ -32,16 +31,6 @@ let mainCanvas;
32
31
  * @memberof Draw */
33
32
  let mainContext;
34
33
 
35
- /** A canvas that appears on top of everything the same size as mainCanvas
36
- * @type {HTMLCanvasElement}
37
- * @memberof Draw */
38
- let overlayCanvas;
39
-
40
- /** 2d context for overlayCanvas
41
- * @type {CanvasRenderingContext2D}
42
- * @memberof Draw */
43
- let overlayContext;
44
-
45
34
  /** The default canvas to use for drawing, usually mainCanvas
46
35
  * @type {HTMLCanvasElement|OffscreenCanvas}
47
36
  * @memberof Draw */
@@ -62,6 +51,16 @@ let workCanvas;
62
51
  * @memberof Draw */
63
52
  let workContext;
64
53
 
54
+ /** Offscreen canvas with willReadFrequently that can be used for image processing
55
+ * @type {OffscreenCanvas}
56
+ * @memberof Draw */
57
+ let workReadCanvas;
58
+
59
+ /** Offscreen canvas with willReadFrequently that can be used for image processing
60
+ * @type {OffscreenCanvasRenderingContext2D}
61
+ * @memberof Draw */
62
+ let workReadContext;
63
+
65
64
  /** The size of the main canvas (and other secondary canvases)
66
65
  * @type {Vector2}
67
66
  * @memberof Draw */
@@ -84,7 +83,7 @@ let drawCount;
84
83
  * - This can take vecs or floats for easier use and conversion
85
84
  * - If an index is passed in, the tile size and index will determine the position
86
85
  * @param {Vector2|number} [pos=0] - Position of the tile in pixels, or tile index
87
- * @param {Vector2|number} [size=tileSizeDefault] - Size of tile in pixels
86
+ * @param {Vector2|number} [size] - Size of tile in pixels
88
87
  * @param {number} [textureIndex] - Texture index to use
89
88
  * @param {number} [padding] - How many pixels padding around tiles
90
89
  * @return {TileInfo}
@@ -94,7 +93,7 @@ let drawCount;
94
93
  * tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
95
94
  * tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
96
95
  * @memberof Draw */
97
- function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
96
+ function tile(pos=new Vector2, size=tileDefaultSize, textureIndex=0, padding=tileDefaultPadding)
98
97
  {
99
98
  if (headlessMode)
100
99
  return new TileInfo;
@@ -133,13 +132,13 @@ function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
133
132
  class TileInfo
134
133
  {
135
134
  /** Create a tile info object
136
- * @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
137
- * @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
138
- * @param {number} [textureIndex] - Texture index to use
139
- * @param {number} [padding] - How many pixels padding around tiles
140
- * @param {number} [bleedScale] - How many pixels smaller to draw tiles
135
+ * @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
136
+ * @param {Vector2} [size] - Size of tile in pixels
137
+ * @param {number} [textureIndex] - Texture index to use
138
+ * @param {number} [padding] - How many pixels padding around tiles
139
+ * @param {number} [bleed] - How many pixels smaller to draw tiles
141
140
  */
142
- constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0, bleedScale=tileFixBleedScale)
141
+ constructor(pos=vec2(), size=tileDefaultSize, textureIndex=0, padding=tileDefaultPadding, bleed=tileDefaultBleed)
143
142
  {
144
143
  /** @property {Vector2} - Top left corner of tile in pixels */
145
144
  this.pos = pos.copy();
@@ -152,7 +151,7 @@ class TileInfo
152
151
  /** @property {TextureInfo} - The texture info for this tile */
153
152
  this.textureInfo = textureInfos[this.textureIndex];
154
153
  /** @property {number} - Shrinks tile by this many pixels to prevent neighbors bleeding */
155
- this.bleedScale = bleedScale;
154
+ this.bleed = bleed;
156
155
  }
157
156
 
158
157
  /** Returns a copy of this tile offset by a vector
@@ -160,7 +159,7 @@ class TileInfo
160
159
  * @return {TileInfo}
161
160
  */
162
161
  offset(offset)
163
- { return new TileInfo(this.pos.add(offset), this.size, this.textureIndex, this.padding, this.bleedScale); }
162
+ { return new TileInfo(this.pos.add(offset), this.size, this.textureIndex, this.padding, this.bleed); }
164
163
 
165
164
  /** Returns a copy of this tile offset by a number of animation frames
166
165
  * @param {number} frame - Offset to apply in animation frames
@@ -183,7 +182,7 @@ class TileInfo
183
182
  this.size = textureInfo.size.copy();
184
183
  this.textureInfo = textureInfo;
185
184
  // do not use padding or bleed
186
- this.bleedScale = this.padding = 0;
185
+ this.bleed = this.padding = 0;
187
186
  return this;
188
187
  }
189
188
  }
@@ -249,7 +248,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
249
248
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
250
249
 
251
250
  const textureInfo = tileInfo && tileInfo.textureInfo;
252
- const bleedScale = tileInfo ? tileInfo.bleedScale : 0;
251
+ const bleed = tileInfo?.bleed ?? 0;
253
252
  if (useWebGL && glEnable)
254
253
  {
255
254
  ASSERT(!!glContext, 'WebGL is not enabled!');
@@ -264,13 +263,13 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
264
263
  const w = tileInfo.size.x * sizeInverse.x;
265
264
  const h = tileInfo.size.y * sizeInverse.y;
266
265
  glSetTexture(textureInfo.glTexture);
267
- if (bleedScale)
266
+ if (bleed)
268
267
  {
269
- const tileImageFixBleedX = sizeInverse.x*bleedScale;
270
- const tileImageFixBleedY = sizeInverse.y*bleedScale;
268
+ const bleedX = sizeInverse.x*bleed;
269
+ const bleedY = sizeInverse.y*bleed;
271
270
  glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
272
- x + tileImageFixBleedX, y + tileImageFixBleedY,
273
- x - tileImageFixBleedX + w, y - tileImageFixBleedY + h,
271
+ x + bleedX, y + bleedY,
272
+ x - bleedX + w, y - bleedY + h,
274
273
  color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
275
274
  }
276
275
  else
@@ -298,7 +297,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
298
297
  // calculate uvs and render
299
298
  const x = tileInfo.pos.x, y = tileInfo.pos.y;
300
299
  const w = tileInfo.size.x, h = tileInfo.size.y;
301
- drawImageColor(context, textureInfo.image, x, y, w, h, -.5, -.5, 1, 1, color, additiveColor, bleedScale);
300
+ drawImageColor(context, textureInfo.image, x, y, w, h, -.5, -.5, 1, 1, color, additiveColor, bleed);
302
301
  }
303
302
  else
304
303
  {
@@ -663,26 +662,7 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
663
662
  drawTextScreen(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, angle, context);
664
663
  }
665
664
 
666
- /** Draw text on overlay canvas in world space
667
- * Automatically splits new lines into rows
668
- * @param {string|number} text
669
- * @param {Vector2} pos
670
- * @param {number} [size]
671
- * @param {Color} [color=(1,1,1,1)]
672
- * @param {number} [lineWidth]
673
- * @param {Color} [lineColor=(0,0,0,1)]
674
- * @param {CanvasTextAlign} [textAlign='center']
675
- * @param {string} [font=fontDefault]
676
- * @param {string} [fontStyle]
677
- * @param {number} [maxWidth]
678
- * @param {number} [angle]
679
- * @memberof Draw */
680
- function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, angle=0)
681
- {
682
- drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, angle, overlayContext);
683
- }
684
-
685
- /** Draw text on overlay canvas in screen space
665
+ /** Draw text in screen space
686
666
  * Automatically splits new lines into rows
687
667
  * @param {string|number} text
688
668
  * @param {Vector2} pos
@@ -695,9 +675,9 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
695
675
  * @param {string} [fontStyle]
696
676
  * @param {number} [maxWidth]
697
677
  * @param {number} [angle]
698
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
678
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
699
679
  * @memberof Draw */
700
- function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=overlayContext)
680
+ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=mainContext)
701
681
  {
702
682
  ASSERT(isString(text), 'text must be a string');
703
683
  ASSERT(isVector2(pos), 'pos must be a vec2');
@@ -741,16 +721,16 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
741
721
  * @memberof Draw */
742
722
  function screenToWorld(screenPos)
743
723
  {
724
+ ASSERT(isVector2(screenPos), 'screenPos must be a vec2');
725
+
744
726
  let x = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
745
727
  let y = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
746
728
  if (cameraAngle)
747
729
  {
748
730
  // apply camera rotation
749
731
  const c = cos(-cameraAngle), s = sin(-cameraAngle);
750
- const rotatedX = x * c - y * s;
751
- const rotatedY = x * s + y * c;
752
- x = rotatedX;
753
- y = rotatedY;
732
+ const xr = x * c - y * s, yr = x * s + y * c;
733
+ x = xr; y = yr;
754
734
  }
755
735
  return new Vector2(x + cameraPos.x, y + cameraPos.y);
756
736
  }
@@ -761,16 +741,16 @@ function screenToWorld(screenPos)
761
741
  * @memberof Draw */
762
742
  function worldToScreen(worldPos)
763
743
  {
744
+ ASSERT(isVector2(worldPos), 'worldPos must be a vec2');
745
+
764
746
  let x = worldPos.x - cameraPos.x;
765
747
  let y = worldPos.y - cameraPos.y;
766
748
  if (cameraAngle)
767
749
  {
768
750
  // apply inverse camera rotation
769
751
  const c = cos(cameraAngle), s = sin(cameraAngle);
770
- const rotatedX = x * c - y * s;
771
- const rotatedY = x * s + y * c;
772
- x = rotatedX;
773
- y = rotatedY;
752
+ const xr = x * c - y * s, yr = x * s + y * c;
753
+ x = xr; y = yr;
774
754
  }
775
755
  return new Vector2
776
756
  (
@@ -785,16 +765,16 @@ function worldToScreen(worldPos)
785
765
  * @memberof Draw */
786
766
  function screenToWorldDelta(screenDelta)
787
767
  {
768
+ ASSERT(isVector2(screenDelta), 'screenDelta must be a vec2');
769
+
788
770
  let x = screenDelta.x / cameraScale;
789
771
  let y = screenDelta.y / -cameraScale;
790
772
  if (cameraAngle)
791
773
  {
792
774
  // apply camera rotation
793
775
  const c = cos(-cameraAngle), s = sin(-cameraAngle);
794
- const rotatedX = x * c - y * s;
795
- const rotatedY = x * s + y * c;
796
- x = rotatedX;
797
- y = rotatedY;
776
+ const xr = x * c - y * s, yr = x * s + y * c;
777
+ x = xr; y = yr;
798
778
  }
799
779
  return new Vector2(x, y);
800
780
  }
@@ -805,16 +785,16 @@ function screenToWorldDelta(screenDelta)
805
785
  * @memberof Draw */
806
786
  function worldToScreenDelta(worldDelta)
807
787
  {
788
+ ASSERT(isVector2(worldDelta), 'worldDelta must be a vec2');
789
+
808
790
  let x = worldDelta.x;
809
791
  let y = worldDelta.y;
810
792
  if (cameraAngle)
811
793
  {
812
794
  // apply inverse camera rotation
813
795
  const c = cos(cameraAngle), s = sin(cameraAngle);
814
- const rotatedX = x * c - y * s;
815
- const rotatedY = x * s + y * c;
816
- x = rotatedX;
817
- y = rotatedY;
796
+ const xr = x * c - y * s, yr = x * s + y * c;
797
+ x = xr; y = yr;
818
798
  }
819
799
  return new Vector2(x * cameraScale, y * -cameraScale);
820
800
  }
@@ -827,6 +807,10 @@ function worldToScreenDelta(worldDelta)
827
807
  * @memberof Draw */
828
808
  function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
829
809
  {
810
+ ASSERT(isVector2(screenPos), 'screenPos must be a vec2');
811
+ ASSERT(isVector2(screenSize), 'screenSize must be a vec2');
812
+ ASSERT(isNumber(screenAngle), 'screenAngle must be a number');
813
+
830
814
  return [
831
815
  screenToWorld(screenPos),
832
816
  screenSize.scale(1/cameraScale),
@@ -842,6 +826,10 @@ function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
842
826
  * @memberof Draw */
843
827
  function worldToScreenTransform(worldPos, worldSize, worldAngle=0)
844
828
  {
829
+ ASSERT(isVector2(worldPos), 'worldPos must be a vec2');
830
+ ASSERT(isVector2(worldSize), 'worldSize must be a vec2');
831
+ ASSERT(isNumber(worldAngle), 'worldAngle must be a number');
832
+
845
833
  return [
846
834
  worldToScreen(worldPos),
847
835
  worldSize.scale(cameraScale),
@@ -854,8 +842,8 @@ function worldToScreenTransform(worldPos, worldSize, worldAngle=0)
854
842
  * @memberof Draw */
855
843
  function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
856
844
 
857
- /** Check if a point or circle is on screen
858
- * If size is a Vector2, uses the largest dimension as diameter
845
+ /** Check if a box, point, or circle is on screen with a circle test
846
+ * If size is a Vector2, uses the length as diameter
859
847
  * This can be used to cull offscreen objects from render or update
860
848
  * @param {Vector2} pos - world space position
861
849
  * @param {Vector2|number} size - world space size or diameter
@@ -863,12 +851,30 @@ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
863
851
  * @memberof Draw */
864
852
  function isOnScreen(pos, size=0)
865
853
  {
866
- pos = worldToScreen(pos);
854
+ ASSERT(isVector2(pos), 'pos must be a vec2');
855
+ ASSERT(isVector2(size) || isNumber(size), 'size must be a vec2 or number');
856
+
857
+ // optimized circle on screen test
858
+ // pos = worldToScreen(pos);
859
+ let x = pos.x - cameraPos.x;
860
+ let y = pos.y - cameraPos.y;
861
+ if (cameraAngle)
862
+ {
863
+ // apply inverse camera rotation
864
+ const c = cos(cameraAngle), s = sin(cameraAngle);
865
+ const xr = x * c - y * s, yr = x * s + y * c;
866
+ x = xr; y = yr;
867
+ }
868
+ x *= cameraScale*2; y *= -cameraScale*2;
869
+
867
870
  if (size instanceof Vector2)
868
- size = max(size.x, size.y); // use largest dimension
869
- size *= cameraScale/2;
870
- return pos.x + size > 0 && pos.x - size < mainCanvasSize.x &&
871
- pos.y + size > 0 && pos.y - size < mainCanvasSize.y;
871
+ size = size.length(); // use length of vector as diameter
872
+ size *= cameraScale;
873
+
874
+ // check against screen bounds
875
+ const w = mainCanvasSize.x, h = mainCanvasSize.y;
876
+ return x + size > -w && x - size < w &&
877
+ y + size > -h && y - size < h;
872
878
  }
873
879
 
874
880
  /** Enable normal or additive blend mode
@@ -882,18 +888,19 @@ function setBlendMode(additive=false, context)
882
888
  context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
883
889
  }
884
890
 
885
- /** Combines all LittleJS canvases onto the main canvas and clears them
886
- * This is necessary for things like saving a screenshot
891
+ /** Combines LittleJS canvases onto the main canvas
892
+ * This is necessary for things like screenshots and video
887
893
  * @memberof Draw */
888
894
  function combineCanvases()
889
895
  {
890
- // combine canvases
891
- glCopyToContext(mainContext);
892
- mainContext.drawImage(overlayCanvas, 0, 0);
893
-
894
- // clear canvases
895
- glClearCanvas();
896
- overlayCanvas.width |= 0;
896
+ const w = mainCanvasSize.x, h = mainCanvasSize.y;
897
+ workCanvas.width = w;
898
+ workCanvas.height = h;
899
+ workContext.fillRect(0,0,w,h); // remove background alpha
900
+ glCopyToContext(workContext);
901
+ workContext.drawImage(mainCanvas, 0, 0);
902
+ mainCanvas.width |= 0;
903
+ mainContext.drawImage(workCanvas, 0, 0);
897
904
  }
898
905
 
899
906
  /** Helper function to draw an image with color and additive color applied
@@ -910,18 +917,18 @@ function combineCanvases()
910
917
  * @param {number} dHeight
911
918
  * @param {Color} color
912
919
  * @param {Color} [additiveColor]
913
- * @param {number} [bleedScale] - How much to shrink the source, used to fix bleeding
920
+ * @param {number} [bleed] - How many pixels to shrink the source, used to fix bleeding
914
921
  * @memberof Draw */
915
- function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight, color, additiveColor, bleedScale=0)
922
+ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight, color, additiveColor, bleed=0)
916
923
  {
917
924
  function isWhite(c) { return c.r >= 1 && c.g >= 1 && c.b >= 1; }
918
925
  function isBlack(c) { return c.r <= 0 && c.g <= 0 && c.b <= 0 && c.a <= 0; }
919
- const sx2 = bleedScale;
920
- const sy2 = bleedScale;
926
+ const sx2 = bleed;
927
+ const sy2 = bleed;
921
928
  sWidth = max(1,sWidth|0);
922
929
  sHeight = max(1,sHeight|0);
923
- const sWidth2 = sWidth - 2*bleedScale;
924
- const sHeight2 = sHeight - 2*bleedScale;
930
+ const sWidth2 = sWidth - 2*bleed;
931
+ const sHeight2 = sHeight - 2*bleed;
925
932
  if (!canvasColorTiles || (additiveColor ? isWhite(color.add(additiveColor)) && additiveColor.a <= 0 : isWhite(color)))
926
933
  {
927
934
  // white texture with no additive alpha, no need to tint
@@ -932,12 +939,12 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
932
939
  else
933
940
  {
934
941
  // copy to offscreen canvas
935
- workCanvas.width = sWidth;
936
- workCanvas.height = sHeight;
937
- workContext.drawImage(image, sx|0, sy|0, sWidth, sHeight, 0, 0, sWidth, sHeight);
942
+ workReadCanvas.width = sWidth;
943
+ workReadCanvas.height = sHeight;
944
+ workReadContext.drawImage(image, sx|0, sy|0, sWidth, sHeight, 0, 0, sWidth, sHeight);
938
945
 
939
946
  // tint image using offscreen work context
940
- const imageData = workContext.getImageData(0, 0, sWidth, sHeight);
947
+ const imageData = workReadContext.getImageData(0, 0, sWidth, sHeight);
941
948
  const data = imageData.data;
942
949
  if (additiveColor && !isBlack(additiveColor))
943
950
  {
@@ -946,8 +953,8 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
946
953
  const colorAdd = [additiveColor.r * 255, additiveColor.g * 255, additiveColor.b * 255, additiveColor.a * 255];
947
954
  for (let i = 0; i < data.length; ++i)
948
955
  data[i] = data[i] * colorMultiply[i&3] + colorAdd[i&3] |0;
949
- workContext.putImageData(imageData, 0, 0);
950
- context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
956
+ workReadContext.putImageData(imageData, 0, 0);
957
+ context.drawImage(workReadCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
951
958
  }
952
959
  else
953
960
  {
@@ -958,9 +965,9 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
958
965
  data[i+1] *= color.g;
959
966
  data[i+2] *= color.b;
960
967
  }
961
- workContext.putImageData(imageData, 0, 0);
968
+ workReadContext.putImageData(imageData, 0, 0);
962
969
  context.globalAlpha = color.a;
963
- context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
970
+ context.drawImage(workReadCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
964
971
  context.globalAlpha = 1;
965
972
  }
966
973
  }
@@ -1022,7 +1029,7 @@ class FontImage
1022
1029
  constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1))
1023
1030
  {
1024
1031
  // load default font image
1025
- if (!engineFontImage)
1032
+ if (!image && !engineFontImage)
1026
1033
  {
1027
1034
  engineFontImage = new Image;
1028
1035
  engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
@@ -1045,23 +1052,14 @@ class FontImage
1045
1052
  this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center, context);
1046
1053
  }
1047
1054
 
1048
- /** Draw text on overlay canvas in world space using the image font
1049
- * @param {string|number} text
1050
- * @param {Vector2} pos
1051
- * @param {number} [scale]
1052
- * @param {boolean} [center]
1053
- */
1054
- drawTextOverlay(text, pos, scale=4, center)
1055
- { this.drawText(text, pos, scale, center, overlayContext); }
1056
-
1057
- /** Draw text on overlay canvas in screen space using the image font
1055
+ /** Draw text in screen space using the image font
1058
1056
  * @param {string|number} text
1059
1057
  * @param {Vector2} pos
1060
1058
  * @param {number} [scale]
1061
1059
  * @param {boolean} [center]
1062
1060
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
1063
1061
  */
1064
- drawTextScreen(text, pos, scale=4, center=true, context=overlayContext)
1062
+ drawTextScreen(text, pos, scale=4, center=true, context=mainContext)
1065
1063
  {
1066
1064
  context.save();
1067
1065
  const size = this.tileSize;
@@ -27,7 +27,7 @@ export
27
27
  // Globals
28
28
  debug,
29
29
  debugOverlay,
30
- showWatermark,
30
+ debugWatermark,
31
31
 
32
32
  // Debug
33
33
  ASSERT,
@@ -60,13 +60,13 @@ export
60
60
  canvasMaxAspect,
61
61
  canvasFixedSize,
62
62
  canvasPixelated,
63
- overlayCanvasPixelated,
64
63
  tilesPixelated,
65
64
  fontDefault,
66
65
  showSplashScreen,
67
66
  headlessMode,
68
- tileSizeDefault,
69
- tileFixBleedScale,
67
+ tileDefaultSize,
68
+ tileDefaultPadding,
69
+ tileDefaultBleed,
70
70
  enablePhysicsSolver,
71
71
  objectDefaultMass,
72
72
  objectDefaultDamping,
@@ -105,14 +105,14 @@ export
105
105
  setCanvasMaxAspect,
106
106
  setCanvasFixedSize,
107
107
  setCanvasPixelated,
108
- setOverlayCanvasPixelated,
109
108
  setTilesPixelated,
110
109
  setFontDefault,
111
110
  setShowSplashScreen,
112
111
  setHeadlessMode,
113
112
  setGLEnable,
114
- setTileSizeDefault,
115
- setTileFixBleedScale,
113
+ setTileDefaultSize,
114
+ setTileDefaultPadding,
115
+ setTileDefaultBleed,
116
116
  setEnablePhysicsSolver,
117
117
  setObjectDefaultMass,
118
118
  setObjectDefaultDamping,
@@ -141,7 +141,7 @@ export
141
141
  setMedalDisplaySlideTime,
142
142
  setMedalDisplaySize,
143
143
  setMedalsPreventUnlock,
144
- setShowWatermark,
144
+ setDebugWatermark,
145
145
  setDebugKey,
146
146
 
147
147
  // Utilities
@@ -221,8 +221,10 @@ export
221
221
  mainContext,
222
222
  drawCanvas,
223
223
  drawContext,
224
- overlayCanvas,
225
- overlayContext,
224
+ workCanvas,
225
+ workContext,
226
+ workReadCanvas,
227
+ workReadContext,
226
228
  mainCanvasSize,
227
229
  textureInfos,
228
230
  drawCount,
@@ -240,7 +242,6 @@ export
240
242
  drawCircle,
241
243
  drawCanvas2D,
242
244
  drawText,
243
- drawTextOverlay,
244
245
  drawTextScreen,
245
246
  setBlendMode,
246
247
  combineCanvases,
@@ -722,7 +722,7 @@ function inputRender()
722
722
  return;
723
723
 
724
724
  // setup the canvas
725
- const context = overlayContext;
725
+ const context = mainContext;
726
726
  context.save();
727
727
  context.globalAlpha = alpha*touchGamepadAlpha;
728
728
  context.strokeStyle = '#fff';
@@ -139,10 +139,10 @@ class Medal
139
139
  */
140
140
  render(hidePercent=0)
141
141
  {
142
- const context = overlayContext;
142
+ const context = mainContext;
143
143
  const width = min(medalDisplaySize.x, mainCanvas.width);
144
144
  const height = medalDisplaySize.y;
145
- const x = overlayCanvas.width - width;
145
+ const x = mainCanvas.width - width;
146
146
  const y = -height*hidePercent;
147
147
  const backgroundColor = hsl(0,0,.9);
148
148
 
@@ -183,7 +183,7 @@ class Medal
183
183
  {
184
184
  // draw the image or icon
185
185
  if (this.image)
186
- overlayContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
186
+ mainContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
187
187
  else
188
188
  drawTextScreen(this.icon, pos, size*.7, BLACK);
189
189
  }
@@ -430,10 +430,11 @@ class EngineObject
430
430
  * @return {number} -1 if this.mirror is true, or 1 if not mirrored */
431
431
  getMirrorSign() { return this.mirror ? -1 : 1; }
432
432
 
433
- /** Attaches a child to this with a given local transform
433
+ /** Attaches a child to this with a local transform, returns child for chaining
434
434
  * @param {EngineObject} child
435
435
  * @param {Vector2} [localPos=(0,0)]
436
- * @param {number} [localAngle] */
436
+ * @param {number} [localAngle]
437
+ * @return {EngineObject} The child object added */
437
438
  addChild(child, localPos=vec2(), localAngle=0)
438
439
  {
439
440
  ASSERT(!child.parent && !this.children.includes(child));
@@ -443,6 +444,7 @@ class EngineObject
443
444
  child.parent = this;
444
445
  child.localPos = localPos.copy();
445
446
  child.localAngle = localAngle;
447
+ return child;
446
448
  }
447
449
 
448
450
  /** Removes a child from this one
@@ -6,7 +6,7 @@
6
6
 
7
7
  'use strict';
8
8
 
9
- let showWatermark = 0;
9
+ let debugWatermark = 0;
10
10
  let debugKey = '';
11
11
  const debug = 0;
12
12
  const debugOverlay = 0;