littlejsengine 1.13.1 → 1.13.4
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.
- package/README.md +15 -12
- package/dist/littlejs.d.ts +94 -74
- package/dist/littlejs.esm.js +422 -306
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +416 -306
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +414 -304
- package/examples/box2d/game.js +1 -1
- package/examples/box2d/gameObjects.js +1 -1
- package/examples/box2d/scenes.js +1 -1
- package/examples/breakoutTutorial/README.md +44 -41
- package/examples/breakoutTutorial/game.js +2 -2
- package/examples/electron/build.bat +7 -0
- package/examples/electron/build.js +124 -0
- package/examples/electron/electron.js +35 -0
- package/examples/electron/game.js +140 -0
- package/examples/electron/index.html +13 -0
- package/examples/electron/package.json +12 -0
- package/examples/electron/tiles.png +0 -0
- package/examples/empty/game.js +1 -0
- package/examples/htmlMenu/game.js +1 -1
- package/examples/index.html +154 -93
- package/examples/module/game.js +5 -2
- package/examples/particles/index.html +12 -3
- package/examples/platformer/gameEffects.js +3 -3
- package/examples/shorts/base.html +1 -1
- package/examples/shorts/blending.js +9 -5
- package/examples/shorts/box2d.js +1 -1
- package/examples/shorts/box2dCar.js +1 -1
- package/examples/shorts/clock.js +1 -1
- package/examples/shorts/colors.js +2 -2
- package/examples/shorts/maze.js +1 -1
- package/examples/shorts/nineSlice.js +9 -9
- package/examples/shorts/piano.js +2 -2
- package/examples/shorts/raycasting.js +2 -2
- package/examples/shorts/shapes.js +9 -9
- package/examples/shorts/slidingPuzzle.js +2 -2
- package/examples/shorts/starfield.js +5 -7
- package/examples/shorts/systemFont.js +1 -1
- package/examples/shorts/uiSystem.js +1 -0
- package/examples/starter/build.js +9 -8
- package/examples/starter/game.js +5 -2
- package/examples/starter/index.html +2 -2
- package/examples/stress/index.html +5 -5
- package/examples/style.css +5 -2
- package/examples/typescript/build.js +1 -1
- package/examples/typescript/game.js +2 -2
- package/examples/typescript/game.ts +5 -2
- package/examples/uiSystem/game.js +2 -1
- package/package.json +2 -2
- package/plugins/box2d.js +19 -94
- package/plugins/drawUtilities.js +24 -18
- package/plugins/postProcess.js +7 -0
- package/plugins/uiSystem.js +4 -4
- package/src/engine.js +7 -2
- package/src/engineAudio.js +1 -1
- package/src/engineDebug.js +2 -2
- package/src/engineDraw.js +243 -150
- package/src/engineExport.js +6 -0
- package/src/engineInput.js +2 -2
- package/src/engineMedals.js +4 -4
- package/src/engineObject.js +3 -3
- package/src/engineParticles.js +8 -1
- package/src/engineSettings.js +55 -8
- package/src/engineUtilities.js +7 -7
- package/src/engineWebGL.js +25 -5
package/src/engineDraw.js
CHANGED
|
@@ -52,6 +52,16 @@ let drawCanvas;
|
|
|
52
52
|
* @memberof Draw */
|
|
53
53
|
let drawContext;
|
|
54
54
|
|
|
55
|
+
/** Offscreen canvas that can be used for image processing
|
|
56
|
+
* @type {OffscreenCanvas}
|
|
57
|
+
* @memberof Draw */
|
|
58
|
+
let workCanvas;
|
|
59
|
+
|
|
60
|
+
/** Offscreen canvas that can be used for image processing
|
|
61
|
+
* @type {OffscreenCanvasRenderingContext2D}
|
|
62
|
+
* @memberof Draw */
|
|
63
|
+
let workContext;
|
|
64
|
+
|
|
55
65
|
/** The size of the main canvas (and other secondary canvases)
|
|
56
66
|
* @type {Vector2}
|
|
57
67
|
* @memberof Draw */
|
|
@@ -71,10 +81,10 @@ let drawCount;
|
|
|
71
81
|
* Create a tile info object using a grid based system
|
|
72
82
|
* - This can take vecs or floats for easier use and conversion
|
|
73
83
|
* - If an index is passed in, the tile size and index will determine the position
|
|
74
|
-
* @param {Vector2|number} [pos=0]
|
|
84
|
+
* @param {Vector2|number} [pos=0] - Index of tile in sheet
|
|
75
85
|
* @param {Vector2|number} [size=tileSizeDefault] - Size of tile in pixels
|
|
76
|
-
* @param {number} [textureIndex]
|
|
77
|
-
* @param {number} [padding]
|
|
86
|
+
* @param {number} [textureIndex] - Texture index to use
|
|
87
|
+
* @param {number} [padding] - How many pixels padding around tiles
|
|
78
88
|
* @return {TileInfo}
|
|
79
89
|
* @example
|
|
80
90
|
* tile(2) // a tile at index 2 using the default tile size of 16
|
|
@@ -201,55 +211,7 @@ class TextureInfo
|
|
|
201
211
|
}
|
|
202
212
|
|
|
203
213
|
///////////////////////////////////////////////////////////////////////////////
|
|
204
|
-
|
|
205
|
-
/** Convert from screen to world space coordinates
|
|
206
|
-
* @param {Vector2} screenPos
|
|
207
|
-
* @return {Vector2}
|
|
208
|
-
* @memberof Draw */
|
|
209
|
-
function screenToWorld(screenPos)
|
|
210
|
-
{
|
|
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);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/** Convert from world to screen space coordinates
|
|
226
|
-
* @param {Vector2} worldPos
|
|
227
|
-
* @return {Vector2}
|
|
228
|
-
* @memberof Draw */
|
|
229
|
-
function worldToScreen(worldPos)
|
|
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
|
-
}
|
|
242
|
-
return new Vector2
|
|
243
|
-
(
|
|
244
|
-
cameraPosRelativeX * cameraScale + mainCanvasSize.x/2 - .5,
|
|
245
|
-
cameraPosRelativeY * -cameraScale + mainCanvasSize.y/2 - .5
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/** Get the camera's visible area in world space
|
|
250
|
-
* @return {Vector2}
|
|
251
|
-
* @memberof Draw */
|
|
252
|
-
function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
214
|
+
// Drawing functions
|
|
253
215
|
|
|
254
216
|
/** Draw textured tile centered in world space, with color applied if using WebGL
|
|
255
217
|
* @param {Vector2} pos - Center of the tile in world space
|
|
@@ -272,6 +234,9 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=new Color,
|
|
|
272
234
|
ASSERT(isColor(color) && (!additiveColor || isColor(additiveColor)), 'drawTile color is invalid');
|
|
273
235
|
ASSERT(isNumber(angle), 'drawTile angle should be a number');
|
|
274
236
|
|
|
237
|
+
if (color.a <= 0 && (!additiveColor || additiveColor.a <= 0) || !size.x || !size.y)
|
|
238
|
+
return; // completely invisible, skip render
|
|
239
|
+
|
|
275
240
|
const textureInfo = tileInfo && tileInfo.textureInfo;
|
|
276
241
|
if (useWebGL)
|
|
277
242
|
{
|
|
@@ -322,18 +287,15 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=new Color,
|
|
|
322
287
|
if (textureInfo)
|
|
323
288
|
{
|
|
324
289
|
// calculate uvs and render
|
|
325
|
-
const x = tileInfo.pos.x
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
const h = tileInfo.size.y - 2*tileFixBleedScale;
|
|
329
|
-
context.globalAlpha = color.a; // only alpha is supported
|
|
330
|
-
context.drawImage(textureInfo.image, x, y, w, h, -.5, -.5, 1, 1);
|
|
331
|
-
context.globalAlpha = 1; // set back to full alpha
|
|
290
|
+
const x = tileInfo.pos.x, y = tileInfo.pos.y;
|
|
291
|
+
const w = tileInfo.size.x, h = tileInfo.size.y;
|
|
292
|
+
drawImageColor(context, textureInfo.image, x, y, w, h, -.5, -.5, 1, 1, color, additiveColor);
|
|
332
293
|
}
|
|
333
294
|
else
|
|
334
295
|
{
|
|
335
|
-
// if no tile info,
|
|
336
|
-
|
|
296
|
+
// if no tile info, use untextured rect
|
|
297
|
+
const c = additiveColor ? color.add(additiveColor) : color;
|
|
298
|
+
context.fillStyle = c.toString();
|
|
337
299
|
context.fillRect(-.5, -.5, 1, 1);
|
|
338
300
|
}
|
|
339
301
|
}, screenSpace, context);
|
|
@@ -346,7 +308,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=new Color,
|
|
|
346
308
|
* @param {Color} [color=(1,1,1,1)]
|
|
347
309
|
* @param {number} [angle]
|
|
348
310
|
* @param {boolean} [useWebGL=glEnable]
|
|
349
|
-
* @param {boolean} [screenSpace
|
|
311
|
+
* @param {boolean} [screenSpace]
|
|
350
312
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
351
313
|
* @memberof Draw */
|
|
352
314
|
function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
@@ -359,73 +321,90 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
359
321
|
* @param {Vector2} posB
|
|
360
322
|
* @param {number} [thickness]
|
|
361
323
|
* @param {Color} [color=(1,1,1,1)]
|
|
324
|
+
* @param {Vector2} [pos=(0,0)] - Offset to apply
|
|
325
|
+
* @param {number} [angle] - Angle to rotate by
|
|
362
326
|
* @param {boolean} [useWebGL=glEnable]
|
|
363
|
-
* @param {boolean} [screenSpace
|
|
327
|
+
* @param {boolean} [screenSpace]
|
|
364
328
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
365
329
|
* @memberof Draw */
|
|
366
|
-
function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
|
|
330
|
+
function drawLine(posA, posB, thickness=.1, color, pos=vec2(), angle=0, useWebGL, screenSpace, context)
|
|
367
331
|
{
|
|
368
332
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
369
333
|
const size = vec2(thickness, halfDelta.length()*2);
|
|
370
|
-
|
|
334
|
+
pos = pos.add(posA.add(halfDelta));
|
|
335
|
+
angle += halfDelta.angle();
|
|
336
|
+
drawRect(pos, size, color, angle, useWebGL, screenSpace, context);
|
|
371
337
|
}
|
|
372
338
|
|
|
373
339
|
/** Draw colored polygon using passed in points
|
|
374
|
-
* @param {Array<Vector2>}
|
|
340
|
+
* @param {Array<Vector2>} points - Array of Vector2 points
|
|
375
341
|
* @param {Color} [color=(1,1,1,1)]
|
|
376
|
-
* @param {number} [lineWidth
|
|
342
|
+
* @param {number} [lineWidth]
|
|
377
343
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
378
|
-
* @param {
|
|
344
|
+
* @param {Vector2} [pos=(0,0)] - Offset to apply
|
|
345
|
+
* @param {number} [angle] - Angle to rotate by
|
|
346
|
+
* @param {boolean} [useWebGL] - Webgl not supported
|
|
347
|
+
* @param {boolean} [screenSpace]
|
|
379
348
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
380
349
|
* @memberof Draw */
|
|
381
|
-
function drawPoly(points, color=new Color, lineWidth=0, lineColor=
|
|
350
|
+
function drawPoly(points, color=new Color, lineWidth=0, lineColor=BLACK, pos=vec2(), angle=0, useWebGL=false, screenSpace=false, context=drawContext)
|
|
382
351
|
{
|
|
383
|
-
ASSERT(
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if (lineWidth)
|
|
352
|
+
ASSERT(isVector2(pos) && pos.isValid(), 'drawPoly pos should be a vec2');
|
|
353
|
+
ASSERT(Array.isArray(points), 'drawPoly points should be an array');
|
|
354
|
+
ASSERT(isColor(color) && isColor(lineColor), 'drawPoly color is invalid');
|
|
355
|
+
ASSERT(isNumber(lineWidth), 'drawPoly lineWidth should be a number');
|
|
356
|
+
ASSERT(isNumber(angle), 'drawPoly angle should be a number');
|
|
357
|
+
ASSERT(!useWebGL, 'drawPoly webgl not supported');
|
|
358
|
+
drawCanvas2D(pos, vec2(1), angle, false, context=>
|
|
391
359
|
{
|
|
392
|
-
context.
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
360
|
+
context.beginPath();
|
|
361
|
+
for (const point of points)
|
|
362
|
+
context.lineTo(point.x, point.y);
|
|
363
|
+
context.closePath();
|
|
364
|
+
context.fillStyle = color.toString();
|
|
365
|
+
context.fill();
|
|
366
|
+
if (lineWidth)
|
|
367
|
+
{
|
|
368
|
+
context.strokeStyle = lineColor.toString();
|
|
369
|
+
context.lineWidth = lineWidth;
|
|
370
|
+
context.stroke();
|
|
371
|
+
}
|
|
372
|
+
}, screenSpace, context);
|
|
396
373
|
}
|
|
397
374
|
|
|
398
375
|
/** Draw colored ellipse using passed in point
|
|
399
376
|
* @param {Vector2} pos
|
|
400
|
-
* @param {
|
|
401
|
-
* @param {number} [height=1]
|
|
402
|
-
* @param {number} [angle=0]
|
|
377
|
+
* @param {Vector2} [size=(1,1)]
|
|
403
378
|
* @param {Color} [color=(1,1,1,1)]
|
|
404
|
-
* @param {number} [
|
|
379
|
+
* @param {number} [angle]
|
|
380
|
+
* @param {number} [lineWidth]
|
|
405
381
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
406
|
-
* @param {boolean} [
|
|
382
|
+
* @param {boolean} [useWebGL] - Webgl not supported
|
|
383
|
+
* @param {boolean} [screenSpace]
|
|
407
384
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
408
385
|
* @memberof Draw */
|
|
409
|
-
function drawEllipse(pos,
|
|
386
|
+
function drawEllipse(pos, size=vec2(1), color=new Color, angle=0, lineWidth=0, lineColor=BLACK, useWebGL=false, screenSpace=false, context=drawContext)
|
|
410
387
|
{
|
|
411
|
-
ASSERT(
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
context.fillStyle = color.toString();
|
|
420
|
-
context.beginPath();
|
|
421
|
-
context.ellipse(pos.x, pos.y, width, height, angle, 0, 9);
|
|
422
|
-
context.fill();
|
|
423
|
-
if (lineWidth)
|
|
388
|
+
ASSERT(isVector2(pos) && pos.isValid(), 'drawEllipse pos should be a vec2');
|
|
389
|
+
ASSERT(isVector2(size) && size.isValid(), 'drawEllipse size should be a vec2');
|
|
390
|
+
ASSERT(isColor(color) && isColor(lineColor), 'drawEllipse color is invalid');
|
|
391
|
+
ASSERT(isNumber(angle), 'drawEllipse angle should be a number');
|
|
392
|
+
ASSERT(isNumber(lineWidth), 'drawEllipse lineWidth should be a number');
|
|
393
|
+
ASSERT(lineWidth>=0 && lineWidth < size.x && lineWidth < size.y, 'drawEllipse invalid lineWidth');
|
|
394
|
+
ASSERT(!useWebGL, 'drawEllipse webgl not supported');
|
|
395
|
+
drawCanvas2D(pos, vec2(1), angle, false, context=>
|
|
424
396
|
{
|
|
425
|
-
context.
|
|
426
|
-
context.
|
|
427
|
-
context.
|
|
428
|
-
|
|
397
|
+
context.beginPath();
|
|
398
|
+
context.ellipse(0, 0, size.y, size.x, 0, 0, 9);
|
|
399
|
+
context.fillStyle = color.toString();
|
|
400
|
+
context.fill();
|
|
401
|
+
if (lineWidth)
|
|
402
|
+
{
|
|
403
|
+
context.strokeStyle = lineColor.toString();
|
|
404
|
+
context.lineWidth = lineWidth;
|
|
405
|
+
context.stroke();
|
|
406
|
+
}
|
|
407
|
+
}, screenSpace, context);
|
|
429
408
|
}
|
|
430
409
|
|
|
431
410
|
/** Draw colored circle using passed in point
|
|
@@ -434,11 +413,12 @@ function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth
|
|
|
434
413
|
* @param {Color} [color=(1,1,1,1)]
|
|
435
414
|
* @param {number} [lineWidth=0]
|
|
436
415
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
416
|
+
* @param {boolean} [useWebGL] - Webgl not supported
|
|
437
417
|
* @param {boolean} [screenSpace=false]
|
|
438
418
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
439
419
|
* @memberof Draw */
|
|
440
|
-
function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=
|
|
441
|
-
{ drawEllipse(pos, radius,
|
|
420
|
+
function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=BLACK, useWebGL=false, screenSpace, context=drawContext)
|
|
421
|
+
{ drawEllipse(pos, vec2(radius), color, 0, lineWidth, lineColor, useWebGL, screenSpace, context); }
|
|
442
422
|
|
|
443
423
|
/** Draw directly to a 2d canvas context in world space
|
|
444
424
|
* @param {Vector2} pos
|
|
@@ -465,6 +445,9 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
465
445
|
context.restore();
|
|
466
446
|
}
|
|
467
447
|
|
|
448
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
449
|
+
// Text Drawing Functions
|
|
450
|
+
|
|
468
451
|
/** Draw text on main canvas in world space
|
|
469
452
|
* Automatically splits new lines into rows
|
|
470
453
|
* @param {string} text
|
|
@@ -513,7 +496,7 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
|
|
|
513
496
|
* @param {number} [maxWidth]
|
|
514
497
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
515
498
|
* @memberof Draw */
|
|
516
|
-
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=
|
|
499
|
+
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, maxWidth=undefined, context=overlayContext)
|
|
517
500
|
{
|
|
518
501
|
context.fillStyle = color.toString();
|
|
519
502
|
context.strokeStyle = lineColor.toString();
|
|
@@ -534,22 +517,67 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
|
|
|
534
517
|
});
|
|
535
518
|
}
|
|
536
519
|
|
|
520
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
521
|
+
// Drawing utilities
|
|
522
|
+
|
|
523
|
+
/** Convert from screen to world space coordinates
|
|
524
|
+
* @param {Vector2} screenPos
|
|
525
|
+
* @return {Vector2}
|
|
526
|
+
* @memberof Draw */
|
|
527
|
+
function screenToWorld(screenPos)
|
|
528
|
+
{
|
|
529
|
+
let cameraPosRelativeX = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
|
|
530
|
+
let cameraPosRelativeY = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
|
|
531
|
+
if (cameraAngle)
|
|
532
|
+
{
|
|
533
|
+
// apply camera rotation
|
|
534
|
+
const cos = Math.cos(-cameraAngle), sin = Math.sin(-cameraAngle);
|
|
535
|
+
const rotatedX = cameraPosRelativeX * cos - cameraPosRelativeY * sin;
|
|
536
|
+
const rotatedY = cameraPosRelativeX * sin + cameraPosRelativeY * cos;
|
|
537
|
+
cameraPosRelativeX = rotatedX;
|
|
538
|
+
cameraPosRelativeY = rotatedY;
|
|
539
|
+
}
|
|
540
|
+
return new Vector2(cameraPosRelativeX + cameraPos.x, cameraPosRelativeY + cameraPos.y);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/** Convert from world to screen space coordinates
|
|
544
|
+
* @param {Vector2} worldPos
|
|
545
|
+
* @return {Vector2}
|
|
546
|
+
* @memberof Draw */
|
|
547
|
+
function worldToScreen(worldPos)
|
|
548
|
+
{
|
|
549
|
+
let cameraPosRelativeX = worldPos.x - cameraPos.x;
|
|
550
|
+
let cameraPosRelativeY = worldPos.y - cameraPos.y;
|
|
551
|
+
if (cameraAngle)
|
|
552
|
+
{
|
|
553
|
+
// apply inverse camera rotation
|
|
554
|
+
const cos = Math.cos(cameraAngle), sin = Math.sin(cameraAngle);
|
|
555
|
+
const rotatedX = cameraPosRelativeX * cos - cameraPosRelativeY * sin;
|
|
556
|
+
const rotatedY = cameraPosRelativeX * sin + cameraPosRelativeY * cos;
|
|
557
|
+
cameraPosRelativeX = rotatedX;
|
|
558
|
+
cameraPosRelativeY = rotatedY;
|
|
559
|
+
}
|
|
560
|
+
return new Vector2
|
|
561
|
+
(
|
|
562
|
+
cameraPosRelativeX * cameraScale + mainCanvasSize.x/2 - .5,
|
|
563
|
+
cameraPosRelativeY * -cameraScale + mainCanvasSize.y/2 - .5
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/** Get the camera's visible area in world space
|
|
568
|
+
* @return {Vector2}
|
|
569
|
+
* @memberof Draw */
|
|
570
|
+
function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
571
|
+
|
|
537
572
|
/** Enable normal or additive blend mode
|
|
538
573
|
* @param {boolean} [additive]
|
|
539
|
-
* @param {boolean} [useWebGL=glEnable]
|
|
540
574
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
541
575
|
* @memberof Draw */
|
|
542
|
-
function setBlendMode(additive=false,
|
|
576
|
+
function setBlendMode(additive=false, context)
|
|
543
577
|
{
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
else
|
|
548
|
-
{
|
|
549
|
-
if (!context)
|
|
550
|
-
context = drawContext;
|
|
551
|
-
context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
552
|
-
}
|
|
578
|
+
glAdditive = additive;
|
|
579
|
+
context ||= drawContext;
|
|
580
|
+
context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
553
581
|
}
|
|
554
582
|
|
|
555
583
|
/** Combines all LittleJS canvases onto the main canvas and clears them
|
|
@@ -566,6 +594,102 @@ function combineCanvases()
|
|
|
566
594
|
overlayCanvas.width |= 0;
|
|
567
595
|
}
|
|
568
596
|
|
|
597
|
+
/** Helper function to draw an image with color and additive color applied
|
|
598
|
+
* This is slower then normal drawImage when color is applied
|
|
599
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
|
600
|
+
* @param {HTMLImageElement|OffscreenCanvas} image
|
|
601
|
+
* @param {number} sx
|
|
602
|
+
* @param {number} sy
|
|
603
|
+
* @param {number} sWidth
|
|
604
|
+
* @param {number} sHeight
|
|
605
|
+
* @param {number} dx
|
|
606
|
+
* @param {number} dy
|
|
607
|
+
* @param {number} dWidth
|
|
608
|
+
* @param {number} dHeight
|
|
609
|
+
* @param {Color} color
|
|
610
|
+
* @param {Color} [additiveColor]
|
|
611
|
+
* @memberof Draw */
|
|
612
|
+
function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight, color, additiveColor)
|
|
613
|
+
{
|
|
614
|
+
function isWhite(c) { return c.r >= 1 && c.g >= 1 && c.b >= 1; }
|
|
615
|
+
function isBlack(c) { return c.r <= 0 && c.g <= 0 && c.b <= 0 && c.a <= 0; }
|
|
616
|
+
const sx2 = tileFixBleedScale;
|
|
617
|
+
const sy2 = tileFixBleedScale;
|
|
618
|
+
const sWidth2 = sWidth - 2*tileFixBleedScale;
|
|
619
|
+
const sHeight2 = sHeight - 2*tileFixBleedScale;
|
|
620
|
+
if (!canvasColorTiles || (additiveColor ? isWhite(color.add(additiveColor)) && additiveColor.a <= 0 : isWhite(color)))
|
|
621
|
+
{
|
|
622
|
+
// white texture with no additive alpha, no need to tint
|
|
623
|
+
context.globalAlpha = color.a;
|
|
624
|
+
context.drawImage(image, sx+sx2, sy+sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
625
|
+
context.globalAlpha = 1;
|
|
626
|
+
}
|
|
627
|
+
else
|
|
628
|
+
{
|
|
629
|
+
// copy to offscreen canvas
|
|
630
|
+
workCanvas.width = sWidth;
|
|
631
|
+
workCanvas.height = sHeight;
|
|
632
|
+
workContext.drawImage(image, sx, sy, sWidth, sHeight, 0, 0, sWidth, sHeight);
|
|
633
|
+
|
|
634
|
+
// tint image using offscreen work context
|
|
635
|
+
const imageData = workContext.getImageData(0, 0, sWidth, sHeight);
|
|
636
|
+
const data = imageData.data;
|
|
637
|
+
if (additiveColor && !isBlack(additiveColor))
|
|
638
|
+
{
|
|
639
|
+
// slower path with additive color
|
|
640
|
+
const colorMultiply = [color.r, color.g, color.b, color.a];
|
|
641
|
+
const colorAdd = [additiveColor.r * 255, additiveColor.g * 255, additiveColor.b * 255, additiveColor.a * 255];
|
|
642
|
+
for (let i = 0; i < data.length; ++i)
|
|
643
|
+
data[i] = data[i] * colorMultiply[i&3] + colorAdd[i&3] |0;
|
|
644
|
+
workContext.putImageData(imageData, 0, 0);
|
|
645
|
+
context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
646
|
+
}
|
|
647
|
+
else
|
|
648
|
+
{
|
|
649
|
+
// faster path with no additive color
|
|
650
|
+
for (let i = 0; i < data.length; i+=4)
|
|
651
|
+
{
|
|
652
|
+
data[i ] *= color.r;
|
|
653
|
+
data[i+1] *= color.g;
|
|
654
|
+
data[i+2] *= color.b;
|
|
655
|
+
}
|
|
656
|
+
workContext.putImageData(imageData, 0, 0);
|
|
657
|
+
context.globalAlpha = color.a;
|
|
658
|
+
context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
659
|
+
context.globalAlpha = 1;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
/** Returns true if fullscreen mode is active
|
|
666
|
+
* @return {boolean}
|
|
667
|
+
* @memberof Draw */
|
|
668
|
+
function isFullscreen() { return !!document.fullscreenElement; }
|
|
669
|
+
|
|
670
|
+
/** Toggle fullscreen mode
|
|
671
|
+
* @memberof Draw */
|
|
672
|
+
function toggleFullscreen()
|
|
673
|
+
{
|
|
674
|
+
const rootElement = mainCanvas.parentElement;
|
|
675
|
+
if (isFullscreen())
|
|
676
|
+
{
|
|
677
|
+
if (document.exitFullscreen)
|
|
678
|
+
document.exitFullscreen();
|
|
679
|
+
}
|
|
680
|
+
else if (rootElement.requestFullscreen)
|
|
681
|
+
rootElement.requestFullscreen();
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/** Set the cursor style
|
|
685
|
+
* @param {string} cursorStyle - CSS cursor style (auto, none, crosshair, etc)
|
|
686
|
+
* @memberof Draw */
|
|
687
|
+
function setCursor(cursorStyle = 'auto')
|
|
688
|
+
{
|
|
689
|
+
const rootElement = mainCanvas.parentElement;
|
|
690
|
+
rootElement.style.cursor = cursorStyle;
|
|
691
|
+
}
|
|
692
|
+
|
|
569
693
|
///////////////////////////////////////////////////////////////////////////////
|
|
570
694
|
|
|
571
695
|
let engineFontImage;
|
|
@@ -633,7 +757,7 @@ class FontImage
|
|
|
633
757
|
(text+'').split('\n').forEach((line, i)=>
|
|
634
758
|
{
|
|
635
759
|
const centerOffset = center ? line.length * size.x * scale / 2 |0 : 0;
|
|
636
|
-
for(let j=line.length; j--;)
|
|
760
|
+
for (let j=line.length; j--;)
|
|
637
761
|
{
|
|
638
762
|
// draw each character
|
|
639
763
|
let charCode = line[j].charCodeAt(0);
|
|
@@ -652,35 +776,4 @@ class FontImage
|
|
|
652
776
|
|
|
653
777
|
context.restore();
|
|
654
778
|
}
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
658
|
-
// Display functions
|
|
659
|
-
|
|
660
|
-
/** Returns true if fullscreen mode is active
|
|
661
|
-
* @return {boolean}
|
|
662
|
-
* @memberof Draw */
|
|
663
|
-
function isFullscreen() { return !!document.fullscreenElement; }
|
|
664
|
-
|
|
665
|
-
/** Toggle fullscreen mode
|
|
666
|
-
* @memberof Draw */
|
|
667
|
-
function toggleFullscreen()
|
|
668
|
-
{
|
|
669
|
-
const rootElement = mainCanvas.parentElement;
|
|
670
|
-
if (isFullscreen())
|
|
671
|
-
{
|
|
672
|
-
if (document.exitFullscreen)
|
|
673
|
-
document.exitFullscreen();
|
|
674
|
-
}
|
|
675
|
-
else if (rootElement.requestFullscreen)
|
|
676
|
-
rootElement.requestFullscreen();
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
/** Set the cursor style
|
|
680
|
-
* @param {string} cursorStyle - CSS cursor style (auto, none, crosshair, etc)
|
|
681
|
-
* @memberof Draw */
|
|
682
|
-
function setCursor(cursorStyle = 'auto')
|
|
683
|
-
{
|
|
684
|
-
const rootElement = mainCanvas.parentElement;
|
|
685
|
-
rootElement.style.cursor = cursorStyle;
|
|
686
779
|
}
|
package/src/engineExport.js
CHANGED
|
@@ -52,9 +52,11 @@ export
|
|
|
52
52
|
cameraPos,
|
|
53
53
|
cameraAngle,
|
|
54
54
|
cameraScale,
|
|
55
|
+
canvasColorTiles,
|
|
55
56
|
canvasMaxSize,
|
|
56
57
|
canvasFixedSize,
|
|
57
58
|
canvasPixelated,
|
|
59
|
+
overlayCanvasPixelated,
|
|
58
60
|
tilesPixelated,
|
|
59
61
|
fontDefault,
|
|
60
62
|
showSplashScreen,
|
|
@@ -91,9 +93,11 @@ export
|
|
|
91
93
|
setCameraPos,
|
|
92
94
|
setCameraAngle,
|
|
93
95
|
setCameraScale,
|
|
96
|
+
setCanvasColorTiles,
|
|
94
97
|
setCanvasMaxSize,
|
|
95
98
|
setCanvasFixedSize,
|
|
96
99
|
setCanvasPixelated,
|
|
100
|
+
setOverlayCanvasPixelated,
|
|
97
101
|
setTilesPixelated,
|
|
98
102
|
setFontDefault,
|
|
99
103
|
setShowSplashScreen,
|
|
@@ -169,6 +173,8 @@ export
|
|
|
169
173
|
rgb,
|
|
170
174
|
hsl,
|
|
171
175
|
isColor,
|
|
176
|
+
isVector2,
|
|
177
|
+
isNumber,
|
|
172
178
|
|
|
173
179
|
// Default Colors
|
|
174
180
|
WHITE,
|
package/src/engineInput.js
CHANGED
|
@@ -550,7 +550,7 @@ function touchGamepadRender()
|
|
|
550
550
|
}
|
|
551
551
|
else // draw cross shaped gamepad
|
|
552
552
|
{
|
|
553
|
-
for(let i=10; i--;)
|
|
553
|
+
for (let i=10; i--;)
|
|
554
554
|
{
|
|
555
555
|
const angle = i*PI/4;
|
|
556
556
|
context.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
|
|
@@ -594,4 +594,4 @@ function pointerLockExit() { document.exitPointerLock && document.exitPointerLoc
|
|
|
594
594
|
/** Check if pointer is locked (true if locked)
|
|
595
595
|
* @return {boolean}
|
|
596
596
|
* @memberof Input */
|
|
597
|
-
function pointerLockIsActive() { return document.pointerLockElement == mainCanvas }
|
|
597
|
+
function pointerLockIsActive() { return document.pointerLockElement == mainCanvas; }
|
package/src/engineMedals.js
CHANGED
|
@@ -142,7 +142,7 @@ class Medal
|
|
|
142
142
|
context.save();
|
|
143
143
|
context.beginPath();
|
|
144
144
|
context.fillStyle = new Color(.9,.9,.9).toString();
|
|
145
|
-
context.strokeStyle =
|
|
145
|
+
context.strokeStyle = BLACK.toString();
|
|
146
146
|
context.lineWidth = 3;
|
|
147
147
|
context.rect(x, y, width, height);
|
|
148
148
|
context.fill();
|
|
@@ -159,11 +159,11 @@ class Medal
|
|
|
159
159
|
const descriptionSize = height*.3;
|
|
160
160
|
const pos = vec2(x + medalDisplayIconSize + 2*gap.x, y + gap.y*2 + nameSize/2);
|
|
161
161
|
const textWidth = width - medalDisplayIconSize - 3*gap.x;
|
|
162
|
-
drawTextScreen(this.name, pos, nameSize,
|
|
162
|
+
drawTextScreen(this.name, pos, nameSize, BLACK, 0, undefined, 'left', undefined, textWidth);
|
|
163
163
|
|
|
164
164
|
// draw the description
|
|
165
165
|
pos.y = y + height - gap.y*2 - descriptionSize/2;
|
|
166
|
-
drawTextScreen(this.description, pos, descriptionSize,
|
|
166
|
+
drawTextScreen(this.description, pos, descriptionSize, BLACK, 0, undefined, 'left', undefined, textWidth);
|
|
167
167
|
context.restore();
|
|
168
168
|
}
|
|
169
169
|
|
|
@@ -177,7 +177,7 @@ class Medal
|
|
|
177
177
|
if (this.image)
|
|
178
178
|
overlayContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
|
|
179
179
|
else
|
|
180
|
-
drawTextScreen(this.icon, pos, size*.7,
|
|
180
|
+
drawTextScreen(this.icon, pos, size*.7, BLACK);
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
// Get local storage key used by the medal
|
package/src/engineObject.js
CHANGED
|
@@ -208,7 +208,7 @@ class EngineObject
|
|
|
208
208
|
if (o.mass) // push away if not fixed
|
|
209
209
|
o.velocity = o.velocity.subtract(velocity);
|
|
210
210
|
|
|
211
|
-
|
|
211
|
+
debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00');
|
|
212
212
|
continue;
|
|
213
213
|
}
|
|
214
214
|
|
|
@@ -270,7 +270,7 @@ class EngineObject
|
|
|
270
270
|
else // bounce if other object is fixed
|
|
271
271
|
this.velocity.x *= -restitution;
|
|
272
272
|
}
|
|
273
|
-
|
|
273
|
+
debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f0f');
|
|
274
274
|
}
|
|
275
275
|
}
|
|
276
276
|
if (this.collideTiles)
|
|
@@ -315,7 +315,7 @@ class EngineObject
|
|
|
315
315
|
this.pos.x = oldPos.x;
|
|
316
316
|
this.velocity.x *= -this.restitution;
|
|
317
317
|
}
|
|
318
|
-
|
|
318
|
+
debugPhysics && debugRect(this.pos, this.size, '#f00');
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
321
|
}
|
package/src/engineParticles.js
CHANGED
|
@@ -209,10 +209,17 @@ class ParticleEmitter extends EngineObject
|
|
|
209
209
|
const particle = new Particle(pos, this.tileInfo, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, this.fadeRate, this.additive, this.trailScale, this.localSpace && this, this.particleDestroyCallback);
|
|
210
210
|
particle.velocity = vec2().setAngle(velocityAngle, speed);
|
|
211
211
|
particle.angleVelocity = angleSpeed;
|
|
212
|
+
if (!this.localSpace)
|
|
213
|
+
{
|
|
214
|
+
// apply emitter velocity to particle
|
|
215
|
+
particle.velocity.x += this.velocity.x;
|
|
216
|
+
particle.velocity.y += this.velocity.y;
|
|
217
|
+
particle.angleVelocity += this.angleVelocity;
|
|
218
|
+
}
|
|
212
219
|
particle.fadeRate = this.fadeRate;
|
|
213
220
|
particle.damping = this.damping;
|
|
214
221
|
particle.angleDamping = this.angleDamping;
|
|
215
|
-
particle.restitution
|
|
222
|
+
particle.restitution = this.restitution;
|
|
216
223
|
particle.friction = this.friction;
|
|
217
224
|
particle.gravityScale = this.gravityScale;
|
|
218
225
|
particle.collideTiles = this.collideTiles;
|