littlejsengine 1.12.6 → 1.13.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 (83) hide show
  1. package/dist/littlejs.d.ts +387 -177
  2. package/dist/littlejs.esm.js +5934 -5294
  3. package/dist/littlejs.esm.min.js +4 -1
  4. package/dist/littlejs.js +5887 -5263
  5. package/dist/littlejs.min.js +4 -1
  6. package/dist/littlejs.release.js +5426 -4829
  7. package/examples/box2d/game.js +1 -1
  8. package/examples/box2d/gameObjects.js +7 -6
  9. package/examples/box2d/index.html +1 -1
  10. package/examples/box2d/scenes.js +7 -7
  11. package/examples/breakout/game.js +1 -1
  12. package/examples/breakout/gameObjects.js +2 -2
  13. package/examples/breakout/index.html +1 -1
  14. package/examples/breakoutTutorial/README.md +2 -2
  15. package/examples/breakoutTutorial/game.js +1 -1
  16. package/examples/breakoutTutorial/index.html +1 -1
  17. package/examples/empty/index.html +1 -1
  18. package/examples/htmlMenu/index.html +1 -1
  19. package/examples/index.html +396 -153
  20. package/examples/module/game.js +1 -1
  21. package/examples/module/index.html +1 -1
  22. package/examples/platformer/game.js +4 -4
  23. package/examples/platformer/gameCharacter.js +6 -6
  24. package/examples/platformer/gameEffects.js +74 -57
  25. package/examples/platformer/gameLevel.js +61 -70
  26. package/examples/platformer/gameObjects.js +4 -4
  27. package/examples/platformer/index.html +1 -1
  28. package/examples/puzzle/index.html +1 -1
  29. package/examples/shorts/base.html +23 -3
  30. package/examples/shorts/box2dCar.js +8 -12
  31. package/examples/shorts/flappyGame.js +52 -0
  32. package/examples/shorts/helloWorld.js +6 -3
  33. package/examples/shorts/hillGlideGame.js +56 -0
  34. package/examples/shorts/landerGame.js +32 -0
  35. package/examples/shorts/maze.js +47 -0
  36. package/examples/shorts/medals.js +42 -0
  37. package/examples/shorts/nineSlice.js +21 -0
  38. package/examples/shorts/piano.js +52 -0
  39. package/examples/shorts/platformer.js +24 -20
  40. package/examples/shorts/{pong.js → pongGame.js} +1 -1
  41. package/examples/shorts/raycasting.js +71 -0
  42. package/examples/shorts/slidingPuzzle.js +42 -0
  43. package/examples/shorts/spaceGame.js +56 -0
  44. package/examples/shorts/starfield.js +17 -0
  45. package/examples/shorts/tileLayer.js +3 -5
  46. package/examples/shorts/tiles.png +0 -0
  47. package/examples/shorts/tiltedView.js +8 -7
  48. package/examples/shorts/topDown.js +18 -26
  49. package/examples/shorts/uiSystem.js +4 -7
  50. package/examples/starter/build.bat +6 -1
  51. package/examples/starter/game.js +2 -2
  52. package/examples/starter/index.html +23 -13
  53. package/examples/stress/index.html +2 -3
  54. package/examples/style.css +136 -0
  55. package/examples/typescript/build.js +1 -2
  56. package/examples/typescript/game.js +2 -2
  57. package/examples/typescript/game.ts +1 -1
  58. package/examples/typescript/index.html +1 -1
  59. package/examples/uiSystem/game.js +8 -24
  60. package/examples/uiSystem/index.html +1 -1
  61. package/package.json +1 -1
  62. package/plugins/box2d.js +8 -8
  63. package/plugins/drawUtilities.js +126 -0
  64. package/plugins/newgrounds.js +1 -1
  65. package/plugins/pluginExport.js +7 -1
  66. package/plugins/postProcess.js +2 -2
  67. package/plugins/uiSystem.js +153 -65
  68. package/plugins/zzfxm.js +1 -1
  69. package/reference.md +10 -10
  70. package/src/engine.js +50 -29
  71. package/src/engineAudio.js +14 -5
  72. package/src/engineBuild.bat +6 -1
  73. package/src/engineBuild.js +20 -5
  74. package/src/engineDebug.js +53 -26
  75. package/src/engineDraw.js +108 -57
  76. package/src/engineExport.js +21 -9
  77. package/src/engineInput.js +109 -37
  78. package/src/engineObject.js +68 -67
  79. package/src/engineParticles.js +26 -9
  80. package/src/engineSettings.js +15 -16
  81. package/src/engineTileLayer.js +312 -153
  82. package/src/engineUtilities.js +182 -130
  83. package/src/engineWebGL.js +47 -36
@@ -26,7 +26,6 @@ const engineSourceFiles =
26
26
  `${SOURCE_FOLDER}/engineParticles.js`,
27
27
  `${SOURCE_FOLDER}/engineMedals.js`,
28
28
  `${SOURCE_FOLDER}/engineWebGL.js`,
29
- `${SOURCE_FOLDER}/engine.js`,
30
29
  ];
31
30
  const enginePluginFiles =
32
31
  [
@@ -35,6 +34,7 @@ const enginePluginFiles =
35
34
  `${PLUGIN_FOLDER}/zzfxm.js`,
36
35
  `${PLUGIN_FOLDER}/uiSystem.js`,
37
36
  `${PLUGIN_FOLDER}/box2d.js`,
37
+ `${PLUGIN_FOLDER}/drawUtilities.js`,
38
38
  ];
39
39
  const engineExtraFiles =
40
40
  [
@@ -49,7 +49,7 @@ const asciiArt =`
49
49
  OOO OOO OO OO OO=OO-oo\\
50
50
  `;
51
51
  const license = '// LittleJS Engine - MIT License - Copyright 2021 Frank Force\n'+
52
- '// https://github.com/KilledByAPixel/LittleJS\n';
52
+ '// https://github.com/KilledByAPixel/LittleJS\n\n';
53
53
 
54
54
  console.log(asciiArt);
55
55
  console.log('Choo Choo... Building LittleJS Engine!\n');
@@ -75,6 +75,7 @@ Build
75
75
  'Build Engine -- all',
76
76
  `${BUILD_FOLDER}/${ENGINE_NAME}.js`,
77
77
  [
78
+ `${SOURCE_FOLDER}/engine.js`,
78
79
  `${SOURCE_FOLDER}/engineDebug.js`,
79
80
  ...engineSourceFiles,
80
81
  ...enginePluginFiles
@@ -87,6 +88,7 @@ Build
87
88
  'Build Engine -- release',
88
89
  `${BUILD_FOLDER}/${ENGINE_NAME}.release.js`,
89
90
  [
91
+ `${SOURCE_FOLDER}/engine.js`,
90
92
  `${SOURCE_FOLDER}/engineRelease.js`,
91
93
  ...engineSourceFiles,
92
94
  ...enginePluginFiles
@@ -99,7 +101,7 @@ Build
99
101
  'Build Engine -- minified',
100
102
  `${BUILD_FOLDER}/${ENGINE_NAME}.min.js`,
101
103
  [`${BUILD_FOLDER}/${ENGINE_NAME}.release.js`],
102
- [closureCompilerStep, uglifyBuildStep]
104
+ [closureCompilerStep, uglifyBuildStep, addLicenseStep]
103
105
  );
104
106
 
105
107
  Build
@@ -123,7 +125,7 @@ Build
123
125
  `${SOURCE_FOLDER}/engineExport.js`,
124
126
  `${PLUGIN_FOLDER}/pluginExport.js`
125
127
  ],
126
- [uglifyBuildStep]
128
+ [uglifyBuildStep, addLicenseStep]
127
129
  );
128
130
 
129
131
  console.log(`Engine built in ${((Date.now() - startTime)/1e3).toFixed(2)} seconds!`);
@@ -141,7 +143,7 @@ function Build(message, outputFile, files=[], buildSteps=[], isPrimaryBuild)
141
143
  if (isPrimaryBuild)
142
144
  {
143
145
  // add license and strict mode to top
144
- buffer += license + '\n';
146
+ buffer += license;
145
147
  buffer += "'use strict';\n\n";
146
148
  }
147
149
 
@@ -189,6 +191,19 @@ function uglifyBuildStep(filename)
189
191
  catch (e) { handleError(e,'Failed to run Uglify minification step!'); }
190
192
  };
191
193
 
194
+ // Add license to top of file
195
+ function addLicenseStep(filename)
196
+ {
197
+ try
198
+ {
199
+ // add license to top of minified file
200
+ let fileContent = fs.readFileSync(filename, 'utf8');
201
+ fileContent = license + fileContent;
202
+ fs.writeFileSync(filename, fileContent);
203
+ }
204
+ catch (e) { handleError(e, 'Failed to add license to minified file!'); }
205
+ };
206
+
192
207
  // Build TypeScript definitions
193
208
  function typeScriptBuildStep(filename)
194
209
  {
@@ -47,19 +47,19 @@ let debugKey = 'Escape';
47
47
  let debugOverlay = false;
48
48
 
49
49
  // Engine internal variables not exposed to documentation
50
- let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParticles = false, debugGamepads = false, debugMedals = false, debugTakeScreenshot, downloadLink;
50
+ let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParticles = false, debugGamepads = false, debugMedals = false, debugTakeScreenshot, downloadLink, debugCanvas;
51
51
 
52
52
  ///////////////////////////////////////////////////////////////////////////////
53
53
  // Debug helper functions
54
54
 
55
55
  /** Asserts if the expression is false, does not do anything in release builds
56
- * @param {boolean} assert
57
- * @param {Object} [output]
56
+ * @param {boolean} assert
57
+ * @param {...Object} [output] - error message output
58
58
  * @memberof Debug */
59
- function ASSERT(assert, output)
59
+ function ASSERT(assert, ...output)
60
60
  {
61
61
  if (enableAsserts)
62
- output ? console.assert(assert, output) : console.assert(assert);
62
+ console.assert(assert, ...output);
63
63
  }
64
64
 
65
65
  /** Draw a debug rectangle in world space
@@ -132,16 +132,22 @@ function debugLine(posA, posB, color, thickness=.1, time)
132
132
  }
133
133
 
134
134
  /** Draw a debug combined axis aligned bounding box in world space
135
- * @param {Vector2} pA - position A
136
- * @param {Vector2} sA - size A
137
- * @param {Vector2} pB - position B
138
- * @param {Vector2} sB - size B
135
+ * @param {Vector2} posA
136
+ * @param {Vector2} sizeA
137
+ * @param {Vector2} posB
138
+ * @param {Vector2} sizeB
139
139
  * @param {string} [color]
140
140
  * @memberof Debug */
141
- function debugOverlap(pA, sA, pB, sB, color)
141
+ function debugOverlap(posA, sizeA, posB, sizeB, color)
142
142
  {
143
- const minPos = vec2(min(pA.x - sA.x/2, pB.x - sB.x/2), min(pA.y - sA.y/2, pB.y - sB.y/2));
144
- const maxPos = vec2(max(pA.x + sA.x/2, pB.x + sB.x/2), max(pA.y + sA.y/2, pB.y + sB.y/2));
143
+ const minPos = vec2(
144
+ min(posA.x - sizeA.x/2, posB.x - sizeB.x/2),
145
+ min(posA.y - sizeA.y/2, posB.y - sizeB.y/2)
146
+ );
147
+ const maxPos = vec2(
148
+ max(posA.x + sizeA.x/2, posB.x + sizeB.x/2),
149
+ max(posA.y + sizeA.y/2, posB.y + sizeB.y/2)
150
+ );
145
151
  debugRect(minPos.lerp(maxPos,.5), maxPos.subtract(minPos), color);
146
152
  }
147
153
 
@@ -169,12 +175,25 @@ function debugClear() { debugPrimitives = []; }
169
175
  function debugScreenshot() { debugTakeScreenshot = 1; }
170
176
 
171
177
  /** Save a canvas to disk
172
- * @param {HTMLCanvasElement} canvas
173
- * @param {string} [filename]
174
- * @param {string} [type]
178
+ * @param {HTMLCanvasElement|OffscreenCanvas} canvas
179
+ * @param {string} [filename]
180
+ * @param {string} [type]
175
181
  * @memberof Debug */
176
182
  function debugSaveCanvas(canvas, filename='screenshot', type='image/png')
177
- { debugSaveDataURL(canvas.toDataURL(type), filename); }
183
+ {
184
+ if (canvas instanceof OffscreenCanvas)
185
+ {
186
+ // copy to temporary canvas and save
187
+ if (!debugCanvas)
188
+ debugCanvas = document.createElement('canvas');
189
+ debugCanvas.width = canvas.width;
190
+ debugCanvas.height = canvas.height;
191
+ debugCanvas.getContext('2d').drawImage(canvas, 0, 0);
192
+ debugSaveDataURL(debugCanvas.toDataURL(type), filename);
193
+ }
194
+ else
195
+ debugSaveDataURL(canvas.toDataURL(type), filename);
196
+ }
178
197
 
179
198
  /** Save a text file to disk
180
199
  * @param {string} text
@@ -252,7 +271,8 @@ function debugRender()
252
271
  if (debugVideoCaptureIsActive())
253
272
  return; // don't show debug info when capturing video
254
273
 
255
- glCopyToContext(mainContext);
274
+ // flush any gl sprites before drawing debug info
275
+ glFlush();
256
276
 
257
277
  if (debugTakeScreenshot)
258
278
  {
@@ -329,8 +349,11 @@ function debugRender()
329
349
  }
330
350
  }
331
351
 
332
- if (tileCollisionLayers.length) // show floored tile pick if there is tile collision
333
- drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(0,0,1,.5), 0, false);
352
+ if (tileCollisionTest(mousePos))
353
+ {
354
+ // show floored tile pick for tile collision
355
+ drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(1,1,0,.5));
356
+ }
334
357
  mainContext = saveContext;
335
358
  }
336
359
 
@@ -405,10 +428,10 @@ function debugRender()
405
428
  mainContext = overlayContext;
406
429
  const raycastHitPos = tileCollisionRaycast(debugObject.pos, mousePos);
407
430
  raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), rgb(0,1,1,.3));
408
- drawLine(mousePos, debugObject.pos, .1, raycastHitPos ? rgb(1,0,0,.5) : rgb(0,1,0,.5), false);
431
+ drawLine(mousePos, debugObject.pos, .1, raycastHitPos ? rgb(1,0,0,.5) : rgb(0,1,0,.5));
409
432
 
410
433
  const debugText = 'mouse pos = ' + mousePos +
411
- '\nmouse collision = ' + getTileCollisionData(mousePos) +
434
+ '\nmouse collision = ' + tileCollisionGetData(mousePos) +
412
435
  '\n\n--- object info ---\n' +
413
436
  debugObject.toString();
414
437
  drawTextScreen(debugText, mousePosScreen, 24, rgb(), .05, undefined, 'center', 'monospace');
@@ -417,20 +440,24 @@ function debugRender()
417
440
 
418
441
  {
419
442
  // draw debug overlay
443
+ const fontSize = 20;
444
+ const lineHeight = fontSize * 1.2 | 0;
420
445
  overlayContext.save();
421
446
  overlayContext.fillStyle = '#fff';
422
447
  overlayContext.textAlign = 'left';
423
448
  overlayContext.textBaseline = 'top';
424
- overlayContext.font = '28px monospace';
449
+ overlayContext.font = fontSize + 'px monospace';
425
450
  overlayContext.shadowColor = '#000';
426
451
  overlayContext.shadowBlur = 9;
427
452
 
428
- let x = 9, y = -20, h = 30;
453
+ let x = 9, y = 0, h = lineHeight;
429
454
  if (debugOverlay)
430
455
  {
431
- overlayContext.fillText(engineName, x, y += h);
432
- overlayContext.fillText('Objects: ' + engineObjects.length, x, y += h);
456
+ overlayContext.fillText(engineName, x, y += h/2 );
433
457
  overlayContext.fillText('Time: ' + formatTime(time), x, y += h);
458
+ overlayContext.fillText('FPS: ' + averageFPS.toFixed(1), x, y += h);
459
+ overlayContext.fillText('Objects: ' + engineObjects.length, x, y += h);
460
+ overlayContext.fillText('Draw Count: ' + drawCount, x, y += h);
434
461
  overlayContext.fillText('---------', x, y += h);
435
462
  overlayContext.fillStyle = '#f00';
436
463
  overlayContext.fillText('ESC: Debug Overlay', x, y += h);
@@ -444,7 +471,7 @@ function debugRender()
444
471
  overlayContext.fillText('4: Debug Raycasts', x, y += h);
445
472
  overlayContext.fillStyle = '#fff';
446
473
  overlayContext.fillText('5: Save Screenshot', x, y += h);
447
- overlayContext.fillText('6: Capture Video', x, y += h);
474
+ overlayContext.fillText('6: Toggle Video Capture', x, y += h);
448
475
 
449
476
  let keysPressed = '';
450
477
  for(const i in inputData[0])
package/src/engineDraw.js CHANGED
@@ -42,6 +42,16 @@ let overlayCanvas;
42
42
  * @memberof Draw */
43
43
  let overlayContext;
44
44
 
45
+ /** The default canvas to use for drawing, usually mainCanvas
46
+ * @type {HTMLCanvasElement|OffscreenCanvas}
47
+ * @memberof Draw */
48
+ let drawCanvas;
49
+
50
+ /** The default 2d context to use for drawing, usually mainContext
51
+ * @type {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D}
52
+ * @memberof Draw */
53
+ let drawContext;
54
+
45
55
  /** The size of the main canvas (and other secondary canvases)
46
56
  * @type {Vector2}
47
57
  * @memberof Draw */
@@ -73,31 +83,36 @@ let drawCount;
73
83
  * tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
74
84
  * @memberof Draw
75
85
  */
76
- function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
86
+ function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
77
87
  {
78
88
  if (headlessMode)
79
89
  return new TileInfo;
80
90
 
81
91
  // if size is a number, make it a vector
82
- if (typeof size === 'number')
92
+ if (typeof size == 'number')
83
93
  {
84
94
  ASSERT(size > 0);
85
- size = vec2(size);
95
+ size = new Vector2(size, size);
86
96
  }
87
97
 
88
- // use pos as a tile index
98
+ // create tile info object
99
+ const tileInfo = new TileInfo(new Vector2, size, textureIndex, padding);
100
+
101
+ // use get the pos of the tile
89
102
  const textureInfo = textureInfos[textureIndex];
90
103
  ASSERT(!!textureInfo, 'Texture not loaded');
91
- const sizePadded = size.add(vec2(padding*2));
92
- if (typeof pos === 'number')
104
+ const sizePaddedX = size.x + padding*2;
105
+ const sizePaddedY = size.y + padding*2;
106
+ if (typeof pos == 'number')
93
107
  {
94
- const cols = textureInfo.size.x / sizePadded.x |0;
95
- pos = cols>0 ? vec2(pos%cols, pos/cols|0) : vec2();
108
+ const cols = textureInfo.size.x / sizePaddedX |0;
109
+ ASSERT(cols>0, 'Tile size is too big for texture');
110
+ const posX = pos % cols, posY = (pos / cols) |0;
111
+ tileInfo.pos.set(posX*sizePaddedX+padding, posY*sizePaddedY+padding);
96
112
  }
97
- pos = vec2(pos.x*sizePadded.x+padding, pos.y*sizePadded.y+padding);
98
-
99
- // return a tile info object
100
- return new TileInfo(pos, size, textureIndex, padding);
113
+ else
114
+ tileInfo.pos.set(pos.x*sizePaddedX+padding, pos.y*sizePaddedY+padding);
115
+ return tileInfo;
101
116
  }
102
117
 
103
118
  /**
@@ -121,6 +136,8 @@ class TileInfo
121
136
  this.textureIndex = textureIndex;
122
137
  /** @property {number} - How many pixels padding around tiles */
123
138
  this.padding = padding;
139
+ /** @property {TextureInfo} - The texture info for this tile */
140
+ this.textureInfo = textureInfos[this.textureIndex];
124
141
  }
125
142
 
126
143
  /** Returns a copy of this tile offset by a vector
@@ -137,14 +154,22 @@ class TileInfo
137
154
  frame(frame)
138
155
  {
139
156
  ASSERT(typeof frame == 'number');
140
- return this.offset(vec2(frame*(this.size.x+this.padding*2), 0));
157
+ return this.offset(new Vector2(frame*(this.size.x+this.padding*2), 0));
141
158
  }
142
159
 
143
- /** Returns the texture info for this tile
144
- * @return {TextureInfo}
145
- */
146
- getTextureInfo()
147
- { return textureInfos[this.textureIndex]; }
160
+ /**
161
+ * Set this tile to use a full image
162
+ * @param {HTMLImageElement|OffscreenCanvas} image
163
+ * @param {WebGLTexture} [glTexture] - webgl texture
164
+ * @return {TileInfo}
165
+ */
166
+ setFullImage(image, glTexture)
167
+ {
168
+ this.pos = new Vector2;
169
+ this.size = new Vector2(image.width, image.height);
170
+ this.textureInfo = new TextureInfo(image, glTexture);
171
+ return this;
172
+ }
148
173
  }
149
174
 
150
175
  /** Texture Info - Stores info about each texture */
@@ -152,9 +177,10 @@ class TextureInfo
152
177
  {
153
178
  /**
154
179
  * Create a TextureInfo, called automatically by the engine
155
- * @param {HTMLImageElement} image
180
+ * @param {HTMLImageElement|OffscreenCanvas} image
181
+ * @param {WebGLTexture} [glTexture] - webgl texture
156
182
  */
157
- constructor(image)
183
+ constructor(image, glTexture)
158
184
  {
159
185
  /** @property {HTMLImageElement} - image source */
160
186
  this.image = image;
@@ -163,7 +189,14 @@ class TextureInfo
163
189
  /** @property {Vector2} - inverse of the size, cached for rendering */
164
190
  this.sizeInverse = vec2(1/image.width, 1/image.height);
165
191
  /** @property {WebGLTexture} - webgl texture */
166
- this.glTexture = glEnable && glCreateTexture(image);
192
+ this.glTexture = glTexture;
193
+ }
194
+
195
+ createWebGLTexture()
196
+ {
197
+ ASSERT(!this.glTexture);
198
+ if (glEnable)
199
+ this.glTexture = glCreateTexture(this.image);
167
200
  }
168
201
  }
169
202
 
@@ -175,11 +208,18 @@ class TextureInfo
175
208
  * @memberof Draw */
176
209
  function screenToWorld(screenPos)
177
210
  {
178
- return new Vector2
179
- (
180
- (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale + cameraPos.x,
181
- (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale + cameraPos.y
182
- );
211
+ let cameraPosRelativeX = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
212
+ let cameraPosRelativeY = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
213
+ if (cameraAngle)
214
+ {
215
+ // apply camera rotation
216
+ const cos = Math.cos(-cameraAngle), sin = Math.sin(-cameraAngle);
217
+ const rotatedX = cameraPosRelativeX * cos - cameraPosRelativeY * sin;
218
+ const rotatedY = cameraPosRelativeX * sin + cameraPosRelativeY * cos;
219
+ cameraPosRelativeX = rotatedX;
220
+ cameraPosRelativeY = rotatedY;
221
+ }
222
+ return new Vector2(cameraPosRelativeX + cameraPos.x, cameraPosRelativeY + cameraPos.y);
183
223
  }
184
224
 
185
225
  /** Convert from world to screen space coordinates
@@ -188,10 +228,21 @@ function screenToWorld(screenPos)
188
228
  * @memberof Draw */
189
229
  function worldToScreen(worldPos)
190
230
  {
231
+ let cameraPosRelativeX = worldPos.x - cameraPos.x;
232
+ let cameraPosRelativeY = worldPos.y - cameraPos.y;
233
+ if (cameraAngle)
234
+ {
235
+ // apply inverse camera rotation
236
+ const cos = Math.cos(cameraAngle), sin = Math.sin(cameraAngle);
237
+ const rotatedX = cameraPosRelativeX * cos - cameraPosRelativeY * sin;
238
+ const rotatedY = cameraPosRelativeX * sin + cameraPosRelativeY * cos;
239
+ cameraPosRelativeX = rotatedX;
240
+ cameraPosRelativeY = rotatedY;
241
+ }
191
242
  return new Vector2
192
243
  (
193
- (worldPos.x - cameraPos.x) * cameraScale + mainCanvasSize.x/2 - .5,
194
- (worldPos.y - cameraPos.y) * -cameraScale + mainCanvasSize.y/2 - .5
244
+ cameraPosRelativeX * cameraScale + mainCanvasSize.x/2 - .5,
245
+ cameraPosRelativeY * -cameraScale + mainCanvasSize.y/2 - .5
195
246
  );
196
247
  }
197
248
 
@@ -212,16 +263,16 @@ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
212
263
  * @param {boolean} [screenSpace=false] - If true the pos and size are in screen space
213
264
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
214
265
  * @memberof Draw */
215
- function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
266
+ function drawTile(pos, size=new Vector2(1), tileInfo, color=new Color,
216
267
  angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
217
268
  {
218
269
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
219
- ASSERT(typeof tileInfo !== 'number' || !tileInfo,
220
- 'this is an old style calls, to fix replace it with tile(tileIndex, tileSize)');
221
- ASSERT(isVector2(pos) && isVector2(size));
222
- ASSERT(isColor(color) && (!additiveColor || isColor(additiveColor)));
270
+ ASSERT(isVector2(pos) && pos.isValid(), 'drawTile pos should be a vec2');
271
+ ASSERT(isVector2(size) && size.isValid(), 'drawTile size should be a vec2');
272
+ ASSERT(isColor(color) && (!additiveColor || isColor(additiveColor)), 'drawTile color is invalid');
273
+ ASSERT(isNumber(angle), 'drawTile angle should be a number');
223
274
 
224
- const textureInfo = tileInfo && tileInfo.getTextureInfo();
275
+ const textureInfo = tileInfo && tileInfo.textureInfo;
225
276
  if (useWebGL)
226
277
  {
227
278
  if (screenSpace)
@@ -265,7 +316,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
265
316
  {
266
317
  // normal canvas 2D rendering method (slower)
267
318
  showWatermark && ++drawCount;
268
- size = vec2(size.x, -size.y); // fix upside down sprites
319
+ size = new Vector2(size.x, -size.y); // fix upside down sprites
269
320
  drawCanvas2D(pos, size, angle, mirror, (context)=>
270
321
  {
271
322
  if (textureInfo)
@@ -325,9 +376,9 @@ function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, contex
325
376
  * @param {number} [lineWidth=0]
326
377
  * @param {Color} [lineColor=(0,0,0,1)]
327
378
  * @param {boolean} [screenSpace=false]
328
- * @param {CanvasRenderingContext2D} [context=mainContext]
379
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
329
380
  * @memberof Draw */
330
- function drawPoly(points, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
381
+ function drawPoly(points, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=drawContext)
331
382
  {
332
383
  ASSERT(isColor(color) && isColor(lineColor));
333
384
  context.fillStyle = color.toString();
@@ -353,9 +404,9 @@ function drawPoly(points, color=new Color, lineWidth=0, lineColor=new Color(0,0,
353
404
  * @param {number} [lineWidth=0]
354
405
  * @param {Color} [lineColor=(0,0,0,1)]
355
406
  * @param {boolean} [screenSpace=false]
356
- * @param {CanvasRenderingContext2D} [context=mainContext]
407
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
357
408
  * @memberof Draw */
358
- function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
409
+ function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=drawContext)
359
410
  {
360
411
  ASSERT(isColor(color) && isColor(lineColor));
361
412
  if (!screenSpace)
@@ -384,21 +435,21 @@ function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth
384
435
  * @param {number} [lineWidth=0]
385
436
  * @param {Color} [lineColor=(0,0,0,1)]
386
437
  * @param {boolean} [screenSpace=false]
387
- * @param {CanvasRenderingContext2D} [context=mainContext]
438
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
388
439
  * @memberof Draw */
389
- function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
440
+ function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=drawContext)
390
441
  { drawEllipse(pos, radius, radius, 0, color, lineWidth, lineColor, screenSpace, context); }
391
442
 
392
443
  /** Draw directly to a 2d canvas context in world space
393
444
  * @param {Vector2} pos
394
445
  * @param {Vector2} size
395
446
  * @param {number} angle
396
- * @param {boolean} mirror
397
- * @param {Function} drawFunction
447
+ * @param {boolean} [mirror]
448
+ * @param {Function} [drawFunction]
398
449
  * @param {boolean} [screenSpace=false]
399
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
450
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
400
451
  * @memberof Draw */
401
- function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, context=mainContext)
452
+ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpace=false, context=drawContext)
402
453
  {
403
454
  if (!screenSpace)
404
455
  {
@@ -408,7 +459,7 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
408
459
  }
409
460
  context.save();
410
461
  context.translate(pos.x+.5, pos.y+.5);
411
- context.rotate(angle);
462
+ context.rotate(angle-cameraAngle);
412
463
  context.scale(mirror ? -size.x : size.x, -size.y);
413
464
  drawFunction(context);
414
465
  context.restore();
@@ -425,9 +476,9 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
425
476
  * @param {CanvasTextAlign} [textAlign='center']
426
477
  * @param {string} [font=fontDefault]
427
478
  * @param {number} [maxWidth]
428
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
479
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
429
480
  * @memberof Draw */
430
- function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, maxWidth, context=mainContext)
481
+ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, maxWidth, context=drawContext)
431
482
  {
432
483
  drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, maxWidth, context);
433
484
  }
@@ -473,22 +524,22 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
473
524
  context.lineJoin = 'round';
474
525
 
475
526
  const lines = (text+'').split('\n');
476
- pos = pos.copy();
477
- pos.y -= (lines.length-1) * size/2; // center text vertically
527
+ let posY = pos.y;
528
+ posY -= (lines.length-1) * size/2; // center text vertically
478
529
  lines.forEach(line=>
479
530
  {
480
- lineWidth && context.strokeText(line, pos.x, pos.y, maxWidth);
481
- context.fillText(line, pos.x, pos.y, maxWidth);
482
- pos.y += size;
531
+ lineWidth && context.strokeText(line, pos.x, posY, maxWidth);
532
+ context.fillText(line, pos.x, posY, maxWidth);
533
+ posY += size;
483
534
  });
484
535
  }
485
536
 
486
537
  /** Enable normal or additive blend mode
487
538
  * @param {boolean} [additive]
488
539
  * @param {boolean} [useWebGL=glEnable]
489
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
540
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
490
541
  * @memberof Draw */
491
- function setBlendMode(additive, useWebGL=glEnable, context)
542
+ function setBlendMode(additive=false, useWebGL=glEnable, context)
492
543
  {
493
544
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
494
545
  if (useWebGL)
@@ -496,7 +547,7 @@ function setBlendMode(additive, useWebGL=glEnable, context)
496
547
  else
497
548
  {
498
549
  if (!context)
499
- context = mainContext;
550
+ context = drawContext;
500
551
  context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
501
552
  }
502
553
  }
@@ -507,7 +558,7 @@ function setBlendMode(additive, useWebGL=glEnable, context)
507
558
  function combineCanvases()
508
559
  {
509
560
  // combine canvases
510
- glCopyToContext(mainContext, true);
561
+ glCopyToContext(mainContext);
511
562
  mainContext.drawImage(overlayCanvas, 0, 0);
512
563
 
513
564
  // clear canvases