littlejsengine 1.9.9 → 1.10.2
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 +6 -0
- package/dist/littlejs.d.ts +107 -14
- package/dist/littlejs.esm.js +250 -102
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +226 -98
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +208 -95
- package/examples/box2d/game.js +29 -10
- package/examples/box2d/gameObjects.js +485 -0
- package/examples/box2d/index.html +7 -5
- package/examples/box2d/scenes.js +28 -247
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/game.js +1 -1
- package/examples/breakout/index.html +5 -4
- package/examples/breakoutTutorial/index.html +4 -3
- package/examples/electron/game.js +1 -1
- package/examples/electron/index.html +4 -3
- package/examples/empty/game.js +1 -1
- package/examples/empty/index.html +2 -1
- package/examples/htmlMenu/game.js +67 -0
- package/examples/htmlMenu/index.html +56 -0
- package/examples/htmlMenu/tiles.png +0 -0
- package/examples/index.html +33 -0
- package/examples/js13k/game.js +1 -1
- package/examples/js13k/index.html +17 -14
- package/examples/module/game.js +1 -1
- package/examples/module/index.html +3 -2
- package/examples/particles/index.html +4 -3
- package/examples/platformer/game.js +1 -1
- package/examples/platformer/gameCharacter.js +1 -0
- package/examples/platformer/gameEffects.js +0 -2
- package/examples/platformer/gameLevel.js +2 -2
- package/examples/platformer/gameObjects.js +1 -2
- package/examples/platformer/index.html +10 -9
- package/examples/puzzle/game.js +1 -1
- package/examples/puzzle/index.html +4 -3
- package/examples/starter/game.js +5 -5
- package/examples/starter/index.html +15 -14
- package/examples/stress/index.html +3 -2
- package/examples/typescript/game.js +1 -1
- package/examples/typescript/index.html +3 -2
- package/examples/uiSystem/game.js +134 -0
- package/examples/uiSystem/index.html +25 -0
- package/examples/uiSystem/tiles.png +0 -0
- package/jsconfig.json +11 -0
- package/package.json +1 -1
- package/plugins/box2d.js +116 -42
- package/plugins/postProcess.js +6 -7
- package/plugins/uiSystem.js +243 -0
- package/src/engine.js +48 -20
- package/src/engineAudio.js +22 -25
- package/src/engineDebug.js +18 -3
- package/src/engineDraw.js +101 -27
- package/src/engineExport.js +23 -4
- package/src/engineInput.js +9 -5
- package/src/engineObject.js +15 -12
- package/src/engineSettings.js +1 -1
- package/src/engineUtilities.js +1 -1
- package/src/engineWebGL.js +11 -4
package/dist/littlejs.js
CHANGED
|
@@ -128,7 +128,6 @@ function debugPoint(pos, color, time, angle)
|
|
|
128
128
|
* @memberof Debug */
|
|
129
129
|
function debugLine(posA, posB, color, thickness=.1, time)
|
|
130
130
|
{
|
|
131
|
-
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
132
131
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
133
132
|
const size = vec2(thickness, halfDelta.length()*2);
|
|
134
133
|
debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), true);
|
|
@@ -194,8 +193,24 @@ function debugSaveDataURL(dataURL, filename)
|
|
|
194
193
|
downloadLink.click();
|
|
195
194
|
}
|
|
196
195
|
|
|
196
|
+
/** Show error as full page of red text
|
|
197
|
+
* @memberof Debug */
|
|
198
|
+
function debugShowErrors()
|
|
199
|
+
{
|
|
200
|
+
onunhandledrejection = (event)=>showError(event.reason);
|
|
201
|
+
onerror = (event, source, lineno, colno)=>
|
|
202
|
+
showError(`${event}\n${source}\nLn ${lineno}, Col ${colno}`);
|
|
203
|
+
|
|
204
|
+
const showError = (message)=>
|
|
205
|
+
{
|
|
206
|
+
// replace entire page with error message
|
|
207
|
+
document.body.style.backgroundColor = '#111';
|
|
208
|
+
document.body.innerHTML = `<pre style=color:#f00;font-size:50px>` + message;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
197
212
|
///////////////////////////////////////////////////////////////////////////////
|
|
198
|
-
// Engine debug
|
|
213
|
+
// Engine debug functions (called automatically)
|
|
199
214
|
|
|
200
215
|
function debugInit()
|
|
201
216
|
{
|
|
@@ -326,7 +341,7 @@ function debugRender()
|
|
|
326
341
|
const pos = worldToScreen(p.pos);
|
|
327
342
|
overlayContext.translate(pos.x|0, pos.y|0);
|
|
328
343
|
overlayContext.rotate(p.angle);
|
|
329
|
-
overlayContext.scale(1, -1);
|
|
344
|
+
overlayContext.scale(1, p.text ? 1 : -1);
|
|
330
345
|
overlayContext.fillStyle = overlayContext.strokeStyle = p.color;
|
|
331
346
|
|
|
332
347
|
if (p.text != undefined)
|
|
@@ -1190,7 +1205,7 @@ class Color
|
|
|
1190
1205
|
* @return {String} */
|
|
1191
1206
|
toString(useAlpha = true)
|
|
1192
1207
|
{
|
|
1193
|
-
const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
|
|
1208
|
+
const toHex = (c)=> ((c=clamp(c)*255|0)<16 ? '0' : '') + c.toString(16);
|
|
1194
1209
|
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
1195
1210
|
}
|
|
1196
1211
|
|
|
@@ -1420,7 +1435,7 @@ let tileSizeDefault = vec2(16);
|
|
|
1420
1435
|
* @type {Number}
|
|
1421
1436
|
* @default
|
|
1422
1437
|
* @memberof Settings */
|
|
1423
|
-
let tileFixBleedScale =
|
|
1438
|
+
let tileFixBleedScale = 0;
|
|
1424
1439
|
|
|
1425
1440
|
///////////////////////////////////////////////////////////////////////////////
|
|
1426
1441
|
// Object settings
|
|
@@ -1966,9 +1981,9 @@ class EngineObject
|
|
|
1966
1981
|
|
|
1967
1982
|
// apply physics
|
|
1968
1983
|
const oldPos = this.pos.copy();
|
|
1969
|
-
this.velocity.y += gravity * this.gravityScale;
|
|
1970
1984
|
this.pos.x += this.velocity.x *= this.damping;
|
|
1971
|
-
this.pos.y += this.velocity.y
|
|
1985
|
+
this.pos.y += this.velocity.y = this.damping * this.velocity.y
|
|
1986
|
+
+ gravity * this.gravityScale;
|
|
1972
1987
|
this.angle += this.angleVelocity *= this.angleDamping;
|
|
1973
1988
|
|
|
1974
1989
|
// physics sanity checks
|
|
@@ -2097,19 +2112,22 @@ class EngineObject
|
|
|
2097
2112
|
const isBlockedX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
|
|
2098
2113
|
if (isBlockedY || !isBlockedX)
|
|
2099
2114
|
{
|
|
2100
|
-
// set if landed on ground
|
|
2101
|
-
this.groundObject = wasMovingDown;
|
|
2102
|
-
|
|
2103
2115
|
// bounce velocity
|
|
2104
2116
|
this.velocity.y *= -this.elasticity;
|
|
2105
2117
|
|
|
2106
|
-
//
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2118
|
+
// set if landed on ground
|
|
2119
|
+
if (this.groundObject = wasMovingDown)
|
|
2120
|
+
{
|
|
2121
|
+
// adjust position to slightly above nearest tile boundary
|
|
2122
|
+
// this prevents gap between object and ground
|
|
2123
|
+
const epsilon = .0001;
|
|
2124
|
+
this.pos.y = (oldPos.y-this.size.y/2|0)+this.size.y/2+epsilon;
|
|
2125
|
+
}
|
|
2126
|
+
else
|
|
2127
|
+
{
|
|
2128
|
+
// move to previous position
|
|
2129
|
+
this.pos.y = oldPos.y;
|
|
2130
|
+
}
|
|
2113
2131
|
}
|
|
2114
2132
|
if (isBlockedX)
|
|
2115
2133
|
{
|
|
@@ -2319,12 +2337,13 @@ let drawCount;
|
|
|
2319
2337
|
///////////////////////////////////////////////////////////////////////////////
|
|
2320
2338
|
|
|
2321
2339
|
/**
|
|
2322
|
-
* Create a tile info object
|
|
2340
|
+
* Create a tile info object using a grid based system
|
|
2323
2341
|
* - This can take vecs or floats for easier use and conversion
|
|
2324
2342
|
* - If an index is passed in, the tile size and index will determine the position
|
|
2325
|
-
* @param {(Number|Vector2)} [pos=
|
|
2343
|
+
* @param {(Number|Vector2)} [pos=0] - Index of tile in sheet
|
|
2326
2344
|
* @param {(Number|Vector2)} [size=tileSizeDefault] - Size of tile in pixels
|
|
2327
2345
|
* @param {Number} [textureIndex] - Texture index to use
|
|
2346
|
+
* @param {Number} [padding] - How many pixels padding around tiles
|
|
2328
2347
|
* @return {TileInfo}
|
|
2329
2348
|
* @example
|
|
2330
2349
|
* tile(2) // a tile at index 2 using the default tile size of 16
|
|
@@ -2333,7 +2352,7 @@ let drawCount;
|
|
|
2333
2352
|
* tile(vec2(4,8), vec2(30,10)) // a tile at pixel location (4,8) with a size of (30,10)
|
|
2334
2353
|
* @memberof Draw
|
|
2335
2354
|
*/
|
|
2336
|
-
function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
2355
|
+
function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
|
|
2337
2356
|
{
|
|
2338
2357
|
if (headlessMode)
|
|
2339
2358
|
return new TileInfo;
|
|
@@ -2345,17 +2364,17 @@ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
|
2345
2364
|
size = vec2(size);
|
|
2346
2365
|
}
|
|
2347
2366
|
|
|
2348
|
-
//
|
|
2367
|
+
// use pos as a tile index
|
|
2368
|
+
const textureInfo = textureInfos[textureIndex];
|
|
2369
|
+
ASSERT(textureInfo, 'Texture not loaded');
|
|
2370
|
+
const sizePadded = size.add(vec2(padding*2));
|
|
2371
|
+
const cols = textureInfo.size.x / sizePadded.x |0;
|
|
2349
2372
|
if (typeof pos === 'number')
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
ASSERT(textureInfo, 'Texture not loaded');
|
|
2353
|
-
const cols = textureInfo.size.x / size.x |0;
|
|
2354
|
-
pos = vec2((pos%cols)*size.x, (pos/cols|0)*size.y);
|
|
2355
|
-
}
|
|
2373
|
+
pos = vec2(pos%cols, pos/cols|0);
|
|
2374
|
+
pos = vec2(pos.x*sizePadded.x+padding, pos.y*sizePadded.y+padding);
|
|
2356
2375
|
|
|
2357
2376
|
// return a tile info object
|
|
2358
|
-
return new TileInfo(pos, size, textureIndex);
|
|
2377
|
+
return new TileInfo(pos, size, textureIndex, padding);
|
|
2359
2378
|
}
|
|
2360
2379
|
|
|
2361
2380
|
/**
|
|
@@ -2367,8 +2386,9 @@ class TileInfo
|
|
|
2367
2386
|
* @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
|
|
2368
2387
|
* @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
|
|
2369
2388
|
* @param {Number} [textureIndex] - Texture index to use
|
|
2389
|
+
* @param {Number} [padding] - How many pixels padding around tiles
|
|
2370
2390
|
*/
|
|
2371
|
-
constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
2391
|
+
constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
|
|
2372
2392
|
{
|
|
2373
2393
|
/** @property {Vector2} - Top left corner of tile in pixels */
|
|
2374
2394
|
this.pos = pos.copy();
|
|
@@ -2376,6 +2396,8 @@ class TileInfo
|
|
|
2376
2396
|
this.size = size.copy();
|
|
2377
2397
|
/** @property {Number} - Texture index to use */
|
|
2378
2398
|
this.textureIndex = textureIndex;
|
|
2399
|
+
/** @property {Number} - How many pixels padding around tiles */
|
|
2400
|
+
this.padding = padding;
|
|
2379
2401
|
}
|
|
2380
2402
|
|
|
2381
2403
|
/** Returns a copy of this tile offset by a vector
|
|
@@ -2392,7 +2414,7 @@ class TileInfo
|
|
|
2392
2414
|
frame(frame)
|
|
2393
2415
|
{
|
|
2394
2416
|
ASSERT(typeof frame == 'number');
|
|
2395
|
-
return this.offset(vec2(frame*this.size.x, 0));
|
|
2417
|
+
return this.offset(vec2(frame*(this.size.x+this.padding*2), 0));
|
|
2396
2418
|
}
|
|
2397
2419
|
|
|
2398
2420
|
/** Returns the texture info for this tile
|
|
@@ -2417,8 +2439,6 @@ class TextureInfo
|
|
|
2417
2439
|
this.size = vec2(image.width, image.height);
|
|
2418
2440
|
/** @property {WebGLTexture} - webgl texture */
|
|
2419
2441
|
this.glTexture = glEnable && glCreateTexture(image);
|
|
2420
|
-
/** @property {Vector2} - size to adjust tile to fix bleeding */
|
|
2421
|
-
this.fixBleedSize = vec2(tileFixBleedScale).divide(this.size);
|
|
2422
2442
|
}
|
|
2423
2443
|
}
|
|
2424
2444
|
|
|
@@ -2487,11 +2507,12 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
2487
2507
|
if (textureInfo)
|
|
2488
2508
|
{
|
|
2489
2509
|
// calculate uvs and render
|
|
2490
|
-
const
|
|
2491
|
-
const
|
|
2492
|
-
const
|
|
2493
|
-
const
|
|
2494
|
-
const
|
|
2510
|
+
const sizeInverse = vec2(1).divide(textureInfo.size);
|
|
2511
|
+
const x = tileInfo.pos.x * sizeInverse.x;
|
|
2512
|
+
const y = tileInfo.pos.y * sizeInverse.y;
|
|
2513
|
+
const w = tileInfo.size.x * sizeInverse.x;
|
|
2514
|
+
const h = tileInfo.size.y * sizeInverse.y;
|
|
2515
|
+
const tileImageFixBleed = sizeInverse.scale(tileFixBleedScale);
|
|
2495
2516
|
glSetTexture(textureInfo.glTexture);
|
|
2496
2517
|
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
2497
2518
|
x + tileImageFixBleed.x, y + tileImageFixBleed.y,
|
|
@@ -2508,6 +2529,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
2508
2529
|
{
|
|
2509
2530
|
// normal canvas 2D rendering method (slower)
|
|
2510
2531
|
showWatermark && ++drawCount;
|
|
2532
|
+
size = vec2(size.x, -size.y); // fix upside down sprites
|
|
2511
2533
|
drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
2512
2534
|
{
|
|
2513
2535
|
if (textureInfo)
|
|
@@ -2545,6 +2567,74 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
2545
2567
|
drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
|
|
2546
2568
|
}
|
|
2547
2569
|
|
|
2570
|
+
/** Draw colored polygon using passed in points
|
|
2571
|
+
* @param {Array} points - Array of Vector2 points
|
|
2572
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
2573
|
+
* @param {Number} [lineWidth=0]
|
|
2574
|
+
* @param {Color} [lineColor=(0,0,0,1)]
|
|
2575
|
+
* @param {Boolean} [screenSpace=false]
|
|
2576
|
+
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
2577
|
+
* @memberof Draw */
|
|
2578
|
+
function drawPoly(points, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
|
|
2579
|
+
{
|
|
2580
|
+
context.fillStyle = color.toString();
|
|
2581
|
+
context.beginPath();
|
|
2582
|
+
for (const point of screenSpace ? points : points.map(worldToScreen))
|
|
2583
|
+
context.lineTo(point.x, point.y);
|
|
2584
|
+
context.closePath();
|
|
2585
|
+
context.fill();
|
|
2586
|
+
if (lineWidth)
|
|
2587
|
+
{
|
|
2588
|
+
context.strokeStyle = lineColor.toString();
|
|
2589
|
+
context.lineWidth = screenSpace ? lineWidth : lineWidth*cameraScale;
|
|
2590
|
+
context.stroke();
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
/** Draw colored ellipse using passed in point
|
|
2595
|
+
* @param {Vector2} pos
|
|
2596
|
+
* @param {Number} [width=1]
|
|
2597
|
+
* @param {Number} [height=1]
|
|
2598
|
+
* @param {Number} [angle=0]
|
|
2599
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
2600
|
+
* @param {Number} [lineWidth=0]
|
|
2601
|
+
* @param {Color} [lineColor=(0,0,0,1)]
|
|
2602
|
+
* @param {Boolean} [screenSpace=false]
|
|
2603
|
+
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
2604
|
+
* @memberof Draw */
|
|
2605
|
+
function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
|
|
2606
|
+
{
|
|
2607
|
+
if (!screenSpace)
|
|
2608
|
+
{
|
|
2609
|
+
pos = worldToScreen(pos);
|
|
2610
|
+
width *= cameraScale;
|
|
2611
|
+
height *= cameraScale;
|
|
2612
|
+
lineWidth *= cameraScale;
|
|
2613
|
+
}
|
|
2614
|
+
context.fillStyle = color.toString();
|
|
2615
|
+
context.beginPath();
|
|
2616
|
+
context.ellipse(pos.x, pos.y, width, height, angle, 0, 9);
|
|
2617
|
+
context.fill();
|
|
2618
|
+
if (lineWidth)
|
|
2619
|
+
{
|
|
2620
|
+
context.strokeStyle = lineColor.toString();
|
|
2621
|
+
context.lineWidth = lineWidth;
|
|
2622
|
+
context.stroke();
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
/** Draw colored circle using passed in point
|
|
2627
|
+
* @param {Vector2} pos
|
|
2628
|
+
* @param {Number} [radius=1]
|
|
2629
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
2630
|
+
* @param {Number} [lineWidth=0]
|
|
2631
|
+
* @param {Color} [lineColor=(0,0,0,1)]
|
|
2632
|
+
* @param {Boolean} [screenSpace=false]
|
|
2633
|
+
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
2634
|
+
* @memberof Draw */
|
|
2635
|
+
function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
|
|
2636
|
+
{ drawEllipse(pos, radius, radius, 0, color, lineWidth, lineColor, screenSpace, context); }
|
|
2637
|
+
|
|
2548
2638
|
/** Draw colored line between two points
|
|
2549
2639
|
* @param {Vector2} posA
|
|
2550
2640
|
* @param {Vector2} posB
|
|
@@ -2615,10 +2705,11 @@ function setBlendMode(additive, useWebGL=glEnable, context)
|
|
|
2615
2705
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
2616
2706
|
* @param {String} [font=fontDefault]
|
|
2617
2707
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
2708
|
+
* @param {Number} [maxWidth]
|
|
2618
2709
|
* @memberof Draw */
|
|
2619
|
-
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context)
|
|
2710
|
+
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context, maxWidth)
|
|
2620
2711
|
{
|
|
2621
|
-
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, context);
|
|
2712
|
+
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, context, maxWidth);
|
|
2622
2713
|
}
|
|
2623
2714
|
|
|
2624
2715
|
/** Draw text on overlay canvas in screen space
|
|
@@ -2632,8 +2723,9 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
2632
2723
|
* @param {CanvasTextAlign} [textAlign]
|
|
2633
2724
|
* @param {String} [font=fontDefault]
|
|
2634
2725
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
2726
|
+
* @param {Number} [maxWidth]
|
|
2635
2727
|
* @memberof Draw */
|
|
2636
|
-
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
|
|
2728
|
+
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext, maxWidth=undefined)
|
|
2637
2729
|
{
|
|
2638
2730
|
context.fillStyle = color.toString();
|
|
2639
2731
|
context.lineWidth = lineWidth;
|
|
@@ -2646,8 +2738,8 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
|
|
|
2646
2738
|
pos = pos.copy();
|
|
2647
2739
|
(text+'').split('\n').forEach(line=>
|
|
2648
2740
|
{
|
|
2649
|
-
lineWidth && context.strokeText(line, pos.x, pos.y);
|
|
2650
|
-
context.fillText(line, pos.x, pos.y);
|
|
2741
|
+
lineWidth && context.strokeText(line, pos.x, pos.y, maxWidth);
|
|
2742
|
+
context.fillText(line, pos.x, pos.y, maxWidth);
|
|
2651
2743
|
pos.y += size;
|
|
2652
2744
|
});
|
|
2653
2745
|
}
|
|
@@ -2755,8 +2847,8 @@ function toggleFullscreen()
|
|
|
2755
2847
|
if (document.exitFullscreen)
|
|
2756
2848
|
document.exitFullscreen();
|
|
2757
2849
|
}
|
|
2758
|
-
else if (
|
|
2759
|
-
|
|
2850
|
+
else if (engineRoot.requestFullscreen)
|
|
2851
|
+
engineRoot.requestFullscreen();
|
|
2760
2852
|
}
|
|
2761
2853
|
/**
|
|
2762
2854
|
* LittleJS Input System
|
|
@@ -2927,7 +3019,7 @@ function inputInit()
|
|
|
2927
3019
|
|
|
2928
3020
|
onkeydown = (e)=>
|
|
2929
3021
|
{
|
|
2930
|
-
if (debug && e.target !=
|
|
3022
|
+
if (debug && e.target != engineRoot) return;
|
|
2931
3023
|
if (!e.repeat)
|
|
2932
3024
|
{
|
|
2933
3025
|
isUsingGamepad = false;
|
|
@@ -2940,7 +3032,7 @@ function inputInit()
|
|
|
2940
3032
|
|
|
2941
3033
|
onkeyup = (e)=>
|
|
2942
3034
|
{
|
|
2943
|
-
if (debug && e.target !=
|
|
3035
|
+
if (debug && e.target != engineRoot) return;
|
|
2944
3036
|
inputData[0][e.code] = 4;
|
|
2945
3037
|
if (inputWASDEmulateDirection)
|
|
2946
3038
|
inputData[0][remapKey(e.code)] = 4;
|
|
@@ -2959,6 +3051,10 @@ function inputInit()
|
|
|
2959
3051
|
// mouse event handlers
|
|
2960
3052
|
onmousedown = (e)=>
|
|
2961
3053
|
{
|
|
3054
|
+
// fix stalled audio requiring user interaction
|
|
3055
|
+
if (soundEnable && !headlessMode && audioContext && audioContext.state != 'running')
|
|
3056
|
+
audioContext.resume();
|
|
3057
|
+
|
|
2962
3058
|
isUsingGamepad = false;
|
|
2963
3059
|
inputData[0][e.button] = 3;
|
|
2964
3060
|
mousePosScreen = mouseToScreen(e);
|
|
@@ -2970,7 +3066,7 @@ function inputInit()
|
|
|
2970
3066
|
oncontextmenu = (e)=> false; // prevent right click menu
|
|
2971
3067
|
|
|
2972
3068
|
// init touch input
|
|
2973
|
-
if (isTouchDevice && touchInputEnable
|
|
3069
|
+
if (isTouchDevice && touchInputEnable)
|
|
2974
3070
|
touchInputInit();
|
|
2975
3071
|
}
|
|
2976
3072
|
|
|
@@ -3127,8 +3223,8 @@ function touchInputInit()
|
|
|
3127
3223
|
function handleTouchDefault(e)
|
|
3128
3224
|
{
|
|
3129
3225
|
// fix stalled audio requiring user interaction
|
|
3130
|
-
if (soundEnable && audioContext && audioContext.state != 'running')
|
|
3131
|
-
|
|
3226
|
+
if (soundEnable && !headlessMode && audioContext && audioContext.state != 'running')
|
|
3227
|
+
audioContext.resume();
|
|
3132
3228
|
|
|
3133
3229
|
// check if touching and pass to mouse events
|
|
3134
3230
|
const touching = e.touches.length;
|
|
@@ -3341,6 +3437,7 @@ class Sound
|
|
|
3341
3437
|
// generate zzfx sound now for fast playback
|
|
3342
3438
|
const defaultRandomness = .05;
|
|
3343
3439
|
this.randomness = zzfxSound[1] || defaultRandomness;
|
|
3440
|
+
zzfxSound[1] = 0; // generate without randomness
|
|
3344
3441
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
3345
3442
|
this.sampleRate = zzfxR;
|
|
3346
3443
|
}
|
|
@@ -3573,10 +3670,6 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
3573
3670
|
|
|
3574
3671
|
///////////////////////////////////////////////////////////////////////////////
|
|
3575
3672
|
|
|
3576
|
-
// internal tracking if audio was suspended when last sound was played
|
|
3577
|
-
// allows first suspended sound to play when audio is resumed
|
|
3578
|
-
let audioSuspended = false;
|
|
3579
|
-
|
|
3580
3673
|
/** Play cached audio samples with given settings
|
|
3581
3674
|
* @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
|
|
3582
3675
|
* @param {Number} [volume] - How much to scale volume by
|
|
@@ -3591,21 +3684,11 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3591
3684
|
{
|
|
3592
3685
|
if (!soundEnable || headlessMode) return;
|
|
3593
3686
|
|
|
3594
|
-
// prevent sounds from building up if they can't be played
|
|
3595
|
-
const audioWasSuspended = audioSuspended;
|
|
3596
|
-
if (audioSuspended = audioContext.state != 'running')
|
|
3597
|
-
{
|
|
3598
|
-
// fix stalled audio
|
|
3599
|
-
audioContext.resume();
|
|
3600
|
-
|
|
3601
|
-
// prevent suspended sounds from building up
|
|
3602
|
-
if (audioWasSuspended)
|
|
3603
|
-
return;
|
|
3604
|
-
}
|
|
3605
|
-
|
|
3606
3687
|
// create buffer and source
|
|
3607
|
-
const
|
|
3608
|
-
|
|
3688
|
+
const channelCount = sampleChannels.length;
|
|
3689
|
+
const sampleLength = sampleChannels[0].length;
|
|
3690
|
+
const buffer = audioContext.createBuffer(channelCount, sampleLength, sampleRate);
|
|
3691
|
+
const source = audioContext.createBufferSource();
|
|
3609
3692
|
|
|
3610
3693
|
// copy samples to buffer and setup source
|
|
3611
3694
|
sampleChannels.forEach((c,i)=> buffer.getChannelData(i).set(c));
|
|
@@ -3619,10 +3702,19 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3619
3702
|
gainNode.connect(audioGainNode);
|
|
3620
3703
|
|
|
3621
3704
|
// connect source to stereo panner and gain
|
|
3622
|
-
|
|
3705
|
+
const pannerNode = new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)});
|
|
3706
|
+
source.connect(pannerNode).connect(gainNode);
|
|
3707
|
+
|
|
3708
|
+
// play the sound
|
|
3709
|
+
if (audioContext.state != 'running')
|
|
3710
|
+
{
|
|
3711
|
+
// fix stalled audio and play
|
|
3712
|
+
audioContext.resume().then(()=>source.start());
|
|
3713
|
+
}
|
|
3714
|
+
else
|
|
3715
|
+
source.start();
|
|
3623
3716
|
|
|
3624
|
-
//
|
|
3625
|
-
source.start();
|
|
3717
|
+
// return sound
|
|
3626
3718
|
return source;
|
|
3627
3719
|
}
|
|
3628
3720
|
|
|
@@ -3635,7 +3727,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3635
3727
|
* @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
|
|
3636
3728
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
3637
3729
|
* @memberof Audio */
|
|
3638
|
-
function zzfx(...zzfxSound) { return
|
|
3730
|
+
function zzfx(...zzfxSound) { return playSamples([zzfxG(...zzfxSound)]); }
|
|
3639
3731
|
|
|
3640
3732
|
/** Sample rate used for all ZzFX sounds
|
|
3641
3733
|
* @default 44100
|
|
@@ -3644,7 +3736,7 @@ const zzfxR = 44100;
|
|
|
3644
3736
|
|
|
3645
3737
|
/** Generate samples for a ZzFX sound
|
|
3646
3738
|
* @param {Number} [volume] - Volume scale (percent)
|
|
3647
|
-
* @param {Number} [randomness] -
|
|
3739
|
+
* @param {Number} [randomness] - How much to randomize frequency (percent Hz)
|
|
3648
3740
|
* @param {Number} [frequency] - Frequency of sound (Hz)
|
|
3649
3741
|
* @param {Number} [attack] - Attack time, how fast sound starts (seconds)
|
|
3650
3742
|
* @param {Number} [sustain] - Sustain time, how long sound holds (seconds)
|
|
@@ -3670,7 +3762,7 @@ const zzfxR = 44100;
|
|
|
3670
3762
|
function zzfxG
|
|
3671
3763
|
(
|
|
3672
3764
|
// parameters
|
|
3673
|
-
volume = 1, randomness =
|
|
3765
|
+
volume = 1, randomness = .05, frequency = 220, attack = 0, sustain = 0,
|
|
3674
3766
|
release = .1, shape = 0, shapeCurve = 1, slide = 0, deltaSlide = 0,
|
|
3675
3767
|
pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, modulation = 0,
|
|
3676
3768
|
bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0, filter = 0
|
|
@@ -3681,7 +3773,8 @@ function zzfxG
|
|
|
3681
3773
|
// init parameters
|
|
3682
3774
|
let PI2 = PI*2, sampleRate = zzfxR,
|
|
3683
3775
|
startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
|
|
3684
|
-
startFrequency = frequency *=
|
|
3776
|
+
startFrequency = frequency *=
|
|
3777
|
+
rand(1 + randomness, 1-randomness) * PI2 / sampleRate,
|
|
3685
3778
|
b = [], t = 0, tm = 0, i = 0, j = 1, r = 0, c = 0, s = 0, f, length,
|
|
3686
3779
|
|
|
3687
3780
|
// biquad LP/HP filter
|
|
@@ -4781,10 +4874,10 @@ function glInit()
|
|
|
4781
4874
|
|
|
4782
4875
|
// create the canvas and textures
|
|
4783
4876
|
glCanvas = document.createElement('canvas');
|
|
4784
|
-
glContext = glCanvas.getContext('webgl2');
|
|
4877
|
+
glContext = glCanvas.getContext('webgl2', {antialias:!canvasPixelated});
|
|
4785
4878
|
|
|
4786
4879
|
// some browsers are much faster without copying the gl buffer so we just overlay it instead
|
|
4787
|
-
glOverlay &&
|
|
4880
|
+
glOverlay && engineRoot.appendChild(glCanvas);
|
|
4788
4881
|
|
|
4789
4882
|
// setup vertex and fragment shaders
|
|
4790
4883
|
glShader = glCreateProgram(
|
|
@@ -4839,7 +4932,8 @@ function glPreRender()
|
|
|
4839
4932
|
// set up the shader
|
|
4840
4933
|
glContext.useProgram(glShader);
|
|
4841
4934
|
glContext.activeTexture(gl_TEXTURE0);
|
|
4842
|
-
|
|
4935
|
+
if (textureInfos[0])
|
|
4936
|
+
glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
|
|
4843
4937
|
|
|
4844
4938
|
// set vertex attributes
|
|
4845
4939
|
let offset = glAdditive = glBatchAdditive = 0;
|
|
@@ -4937,8 +5031,14 @@ function glCreateTexture(image)
|
|
|
4937
5031
|
// build the texture
|
|
4938
5032
|
const texture = glContext.createTexture();
|
|
4939
5033
|
glContext.bindTexture(gl_TEXTURE_2D, texture);
|
|
4940
|
-
if (image)
|
|
5034
|
+
if (image && image.width)
|
|
4941
5035
|
glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
|
|
5036
|
+
else
|
|
5037
|
+
{
|
|
5038
|
+
// create a white texture
|
|
5039
|
+
const whitePixel = new Uint8Array([255, 255, 255, 255]);
|
|
5040
|
+
glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, 1, 1, 0, gl_RGBA, gl_UNSIGNED_BYTE, whitePixel);
|
|
5041
|
+
}
|
|
4942
5042
|
|
|
4943
5043
|
// use point filtering for pixelated rendering
|
|
4944
5044
|
const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
|
|
@@ -5081,7 +5181,7 @@ const engineName = 'LittleJS';
|
|
|
5081
5181
|
* @type {String}
|
|
5082
5182
|
* @default
|
|
5083
5183
|
* @memberof Engine */
|
|
5084
|
-
const engineVersion = '1.
|
|
5184
|
+
const engineVersion = '1.10.2';
|
|
5085
5185
|
|
|
5086
5186
|
/** Frames per second to update
|
|
5087
5187
|
* @type {Number}
|
|
@@ -5126,6 +5226,12 @@ let timeReal = 0;
|
|
|
5126
5226
|
* @memberof Engine */
|
|
5127
5227
|
let paused = false;
|
|
5128
5228
|
|
|
5229
|
+
/** The root element that engine is attached to
|
|
5230
|
+
* @type {HTMLElement}
|
|
5231
|
+
* @default document.body
|
|
5232
|
+
* @memberof Engine */
|
|
5233
|
+
let engineRoot;
|
|
5234
|
+
|
|
5129
5235
|
/** Set if game is paused
|
|
5130
5236
|
* @param {Boolean} isPaused
|
|
5131
5237
|
* @memberof Engine */
|
|
@@ -5159,8 +5265,9 @@ function engineAddPlugin(updateFunction, renderFunction)
|
|
|
5159
5265
|
* @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
|
|
5160
5266
|
* @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
|
|
5161
5267
|
* @param {Array} [imageSources=['tiles.png']] - Image to load
|
|
5268
|
+
* @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
|
|
5162
5269
|
* @memberof Engine */
|
|
5163
|
-
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[
|
|
5270
|
+
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
5164
5271
|
{
|
|
5165
5272
|
ASSERT(Array.isArray(imageSources), 'pass in images as array');
|
|
5166
5273
|
|
|
@@ -5202,6 +5309,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5202
5309
|
for (const o of engineObjects)
|
|
5203
5310
|
o.parent || o.updateTransforms();
|
|
5204
5311
|
inputUpdate();
|
|
5312
|
+
pluginUpdateList.forEach(f=>f());
|
|
5205
5313
|
debugUpdate();
|
|
5206
5314
|
gameUpdatePost();
|
|
5207
5315
|
inputUpdatePost();
|
|
@@ -5317,16 +5425,22 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5317
5425
|
}
|
|
5318
5426
|
|
|
5319
5427
|
// setup html
|
|
5320
|
-
const
|
|
5428
|
+
const styleRoot =
|
|
5321
5429
|
'margin:0;overflow:hidden;' + // fill the window
|
|
5430
|
+
'width:100vw;height:100vh;' + // fill the window
|
|
5431
|
+
'display:flex;' + // use flexbox
|
|
5432
|
+
'align-items:center;' + // horizontal center
|
|
5433
|
+
(canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
|
|
5434
|
+
'justify-content:center;' + // vertical center
|
|
5322
5435
|
'background:#000;' + // set background color
|
|
5323
5436
|
'user-select:none;' + // prevent hold to select
|
|
5324
5437
|
'-webkit-user-select:none;' + // compatibility for ios
|
|
5325
5438
|
(!touchInputEnable ? '' : // no touch css setttings
|
|
5326
5439
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
5327
5440
|
'-webkit-touch-callout:none');// compatibility for ios
|
|
5328
|
-
|
|
5329
|
-
|
|
5441
|
+
engineRoot = rootElement;
|
|
5442
|
+
engineRoot.style.cssText = styleRoot;
|
|
5443
|
+
engineRoot.appendChild(mainCanvas = document.createElement('canvas'));
|
|
5330
5444
|
mainContext = mainCanvas.getContext('2d');
|
|
5331
5445
|
|
|
5332
5446
|
// init stuff and start engine
|
|
@@ -5336,13 +5450,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5336
5450
|
glInit();
|
|
5337
5451
|
|
|
5338
5452
|
// create overlay canvas for hud to appear above gl canvas
|
|
5339
|
-
|
|
5453
|
+
engineRoot.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
5340
5454
|
overlayContext = overlayCanvas.getContext('2d');
|
|
5341
5455
|
|
|
5342
5456
|
// set canvas style
|
|
5343
|
-
const styleCanvas = 'position:absolute;
|
|
5344
|
-
|
|
5345
|
-
(glCanvas
|
|
5457
|
+
const styleCanvas = 'position:absolute'; // allow canvases to overlap
|
|
5458
|
+
mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
|
|
5459
|
+
if (glCanvas)
|
|
5460
|
+
glCanvas.style.cssText = styleCanvas;
|
|
5346
5461
|
updateCanvas();
|
|
5347
5462
|
|
|
5348
5463
|
// create promises for loading images
|
|
@@ -5359,19 +5474,32 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5359
5474
|
})
|
|
5360
5475
|
);
|
|
5361
5476
|
|
|
5362
|
-
|
|
5363
|
-
showSplashScreen && promises.push(new Promise(resolve =>
|
|
5477
|
+
if (!imageSources.length)
|
|
5364
5478
|
{
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
updateSplash();
|
|
5368
|
-
function updateSplash()
|
|
5479
|
+
// no images to load
|
|
5480
|
+
promises.push(new Promise(resolve =>
|
|
5369
5481
|
{
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5482
|
+
textureInfos[0] = new TextureInfo(new Image);
|
|
5483
|
+
resolve();
|
|
5484
|
+
}));
|
|
5485
|
+
}
|
|
5486
|
+
|
|
5487
|
+
if (showSplashScreen)
|
|
5488
|
+
{
|
|
5489
|
+
// draw splash screen
|
|
5490
|
+
promises.push(new Promise(resolve =>
|
|
5491
|
+
{
|
|
5492
|
+
let t = 0;
|
|
5493
|
+
console.log(`${engineName} Engine v${engineVersion}`);
|
|
5494
|
+
updateSplash();
|
|
5495
|
+
function updateSplash()
|
|
5496
|
+
{
|
|
5497
|
+
clearInput();
|
|
5498
|
+
drawEngineSplashScreen(t+=.01);
|
|
5499
|
+
t>1 ? resolve() : setTimeout(updateSplash, 16);
|
|
5500
|
+
}
|
|
5501
|
+
}));
|
|
5502
|
+
}
|
|
5375
5503
|
|
|
5376
5504
|
// load all of the images
|
|
5377
5505
|
Promise.all(promises).then(startEngine);
|