littlejsengine 1.9.5 → 1.9.6
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/dist/littlejs.d.ts +57 -45
- package/dist/littlejs.esm.js +277 -192
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +275 -192
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +272 -188
- package/examples/breakout/index.html +3 -3
- package/examples/breakoutTutorial/index.html +2 -2
- package/examples/electron/index.html +2 -2
- package/examples/js13k/build.js +1 -0
- package/examples/js13k/index.html +13 -13
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +1 -1
- package/examples/platformer/index.html +8 -8
- package/examples/puzzle/index.html +2 -2
- package/examples/starter/index.html +13 -13
- package/examples/stress/index.html +1 -1
- package/examples/typescript/index.html +1 -1
- package/package.json +1 -1
- package/src/engine.js +63 -40
- package/src/engineAudio.js +20 -16
- package/src/engineDebug.js +3 -4
- package/src/engineDraw.js +13 -10
- package/src/engineExport.js +2 -0
- package/src/engineInput.js +80 -64
- package/src/engineObject.js +18 -7
- package/src/engineParticles.js +14 -13
- package/src/engineSettings.js +13 -2
- package/src/engineTileLayer.js +15 -4
- package/src/engineUtilities.js +16 -14
- package/src/engineWebGL.js +20 -18
- package/LittleJS.zip +0 -0
package/dist/littlejs.release.js
CHANGED
|
@@ -99,7 +99,7 @@ function clamp(value, min=0, max=1) { return value < min ? min : value > max ? m
|
|
|
99
99
|
* @return {Number}
|
|
100
100
|
* @memberof Utilities */
|
|
101
101
|
function percent(value, valueA, valueB)
|
|
102
|
-
{ return valueB
|
|
102
|
+
{ return (valueB-=valueA) ? clamp((value-valueA)/valueB) : 0; }
|
|
103
103
|
|
|
104
104
|
/** Linearly interpolates between values passed in using percent
|
|
105
105
|
* @param {Number} percent
|
|
@@ -306,7 +306,7 @@ class RandomGenerator
|
|
|
306
306
|
this.seed ^= this.seed << 13;
|
|
307
307
|
this.seed ^= this.seed >>> 17;
|
|
308
308
|
this.seed ^= this.seed << 5;
|
|
309
|
-
return valueB + (valueA - valueB) * abs(this.seed %
|
|
309
|
+
return valueB + (valueA - valueB) * abs(this.seed % 1e8) / 1e8;
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
/** Returns a floored seeded random value the two values passed in
|
|
@@ -317,7 +317,7 @@ class RandomGenerator
|
|
|
317
317
|
|
|
318
318
|
/** Randomly returns either -1 or 1 deterministically
|
|
319
319
|
* @return {Number} */
|
|
320
|
-
sign() { return this.
|
|
320
|
+
sign() { return this.float() > .5 ? 1 : -1; }
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -365,6 +365,7 @@ class Vector2
|
|
|
365
365
|
* @param {Number} [y] - Y axis location */
|
|
366
366
|
constructor(x=0, y=0)
|
|
367
367
|
{
|
|
368
|
+
ASSERT(typeof x === 'number' && typeof y === 'number');
|
|
368
369
|
/** @property {Number} - X axis location */
|
|
369
370
|
this.x = x;
|
|
370
371
|
/** @property {Number} - Y axis location */
|
|
@@ -691,12 +692,14 @@ class Color
|
|
|
691
692
|
* @return {Color} */
|
|
692
693
|
setHSLA(h=0, s=0, l=1, a=1)
|
|
693
694
|
{
|
|
695
|
+
h = mod(h,1);
|
|
696
|
+
s = clamp(s);
|
|
697
|
+
l = clamp(l);
|
|
694
698
|
const q = l < .5 ? l*(1+s) : l+s-l*s, p = 2*l-q,
|
|
695
699
|
f = (p, q, t)=>
|
|
696
|
-
(t = (
|
|
697
|
-
t < 1
|
|
698
|
-
t < 2
|
|
699
|
-
|
|
700
|
+
(t = mod(t,1))*6 < 1 ? p+(q-p)*6*t :
|
|
701
|
+
t*2 < 1 ? q :
|
|
702
|
+
t*3 < 2 ? p+(q-p)*(4-t*6) : p;
|
|
700
703
|
this.r = f(p, q, h + 1/3);
|
|
701
704
|
this.g = f(p, q, h);
|
|
702
705
|
this.b = f(p, q, h - 1/3);
|
|
@@ -747,13 +750,12 @@ class Color
|
|
|
747
750
|
).clamp();
|
|
748
751
|
}
|
|
749
752
|
|
|
750
|
-
/** Returns this color expressed as a
|
|
753
|
+
/** Returns this color expressed as a rgb color code
|
|
751
754
|
* @param {Boolean} [useAlpha] - if alpha should be included in result
|
|
752
755
|
* @return {String} */
|
|
753
|
-
toString(useAlpha = true)
|
|
754
|
-
{
|
|
755
|
-
|
|
756
|
-
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
756
|
+
toString(useAlpha = true)
|
|
757
|
+
{
|
|
758
|
+
return `rgb(${this.r*255},${this.g*255},${this.b*255},${useAlpha ? this.a : 0})`;
|
|
757
759
|
}
|
|
758
760
|
|
|
759
761
|
/** Set this color from a hex code
|
|
@@ -811,11 +813,11 @@ class Timer
|
|
|
811
813
|
|
|
812
814
|
/** Returns true if set and has not elapsed
|
|
813
815
|
* @return {Boolean} */
|
|
814
|
-
active() { return time
|
|
816
|
+
active() { return time < this.time; }
|
|
815
817
|
|
|
816
818
|
/** Returns true if set and elapsed
|
|
817
819
|
* @return {Boolean} */
|
|
818
|
-
elapsed() { return time
|
|
820
|
+
elapsed() { return time >= this.time; }
|
|
819
821
|
|
|
820
822
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
821
823
|
* @return {Number} */
|
|
@@ -890,6 +892,12 @@ let fontDefault = 'arial';
|
|
|
890
892
|
* @memberof Settings */
|
|
891
893
|
let showSplashScreen = false;
|
|
892
894
|
|
|
895
|
+
/** Disables all rendering, audio, and input for servers
|
|
896
|
+
* @type {Boolean}
|
|
897
|
+
* @default
|
|
898
|
+
* @memberof Settings */
|
|
899
|
+
let headlessMode = false;
|
|
900
|
+
|
|
893
901
|
///////////////////////////////////////////////////////////////////////////////
|
|
894
902
|
// WebGL settings
|
|
895
903
|
|
|
@@ -918,7 +926,7 @@ let tileSizeDefault = vec2(16);
|
|
|
918
926
|
* @type {Number}
|
|
919
927
|
* @default
|
|
920
928
|
* @memberof Settings */
|
|
921
|
-
let tileFixBleedScale = .
|
|
929
|
+
let tileFixBleedScale = .5;
|
|
922
930
|
|
|
923
931
|
///////////////////////////////////////////////////////////////////////////////
|
|
924
932
|
// Object settings
|
|
@@ -1043,7 +1051,7 @@ let soundEnable = true;
|
|
|
1043
1051
|
* @type {Number}
|
|
1044
1052
|
* @default
|
|
1045
1053
|
* @memberof Settings */
|
|
1046
|
-
let soundVolume = .
|
|
1054
|
+
let soundVolume = .3;
|
|
1047
1055
|
|
|
1048
1056
|
/** Default range where sound no longer plays
|
|
1049
1057
|
* @type {Number}
|
|
@@ -1128,6 +1136,11 @@ function setFontDefault(font) { fontDefault = font; }
|
|
|
1128
1136
|
* @memberof Settings */
|
|
1129
1137
|
function setShowSplashScreen(show) { showSplashScreen = show; }
|
|
1130
1138
|
|
|
1139
|
+
/** Set to disalbe rendering, audio, and input for servers
|
|
1140
|
+
* @param {Boolean} headless
|
|
1141
|
+
* @memberof Settings */
|
|
1142
|
+
function setHeadlessMode(headless) { headlessMode = headless; }
|
|
1143
|
+
|
|
1131
1144
|
/** Set if webgl rendering is enabled
|
|
1132
1145
|
* @param {Boolean} enable
|
|
1133
1146
|
* @memberof Settings */
|
|
@@ -1397,18 +1410,30 @@ class EngineObject
|
|
|
1397
1410
|
engineObjects.push(this);
|
|
1398
1411
|
}
|
|
1399
1412
|
|
|
1400
|
-
/** Update the object transform
|
|
1401
|
-
|
|
1413
|
+
/** Update the object transform, called automatically by engine even when paused */
|
|
1414
|
+
updateTransforms()
|
|
1402
1415
|
{
|
|
1403
1416
|
const parent = this.parent;
|
|
1404
1417
|
if (parent)
|
|
1405
1418
|
{
|
|
1406
1419
|
// copy parent pos/angle
|
|
1407
|
-
|
|
1408
|
-
this.
|
|
1409
|
-
|
|
1420
|
+
const mirror = parent.getMirrorSign();
|
|
1421
|
+
this.pos = this.localPos.multiply(vec2(mirror,1)).rotate(-parent.angle).add(parent.pos);
|
|
1422
|
+
this.angle = mirror*this.localAngle + parent.angle;
|
|
1410
1423
|
}
|
|
1411
1424
|
|
|
1425
|
+
// update children
|
|
1426
|
+
for (const child of this.children)
|
|
1427
|
+
child.updateTransforms();
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
/** Update the object physics, called automatically by engine once each frame */
|
|
1431
|
+
update()
|
|
1432
|
+
{
|
|
1433
|
+
// child objects do not have physics
|
|
1434
|
+
if (this.parent)
|
|
1435
|
+
return;
|
|
1436
|
+
|
|
1412
1437
|
// limit max speed to prevent missing collisions
|
|
1413
1438
|
this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
|
|
1414
1439
|
this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
|
|
@@ -1423,8 +1448,7 @@ class EngineObject
|
|
|
1423
1448
|
// physics sanity checks
|
|
1424
1449
|
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
1425
1450
|
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
1426
|
-
|
|
1427
|
-
if (!enablePhysicsSolver || !this.mass) // do not update collision for fixed objects
|
|
1451
|
+
if (!enablePhysicsSolver || !this.mass) // dont do collision for fixed objects
|
|
1428
1452
|
return;
|
|
1429
1453
|
|
|
1430
1454
|
const wasMovingDown = this.velocity.y < 0;
|
|
@@ -1754,6 +1778,9 @@ let drawCount;
|
|
|
1754
1778
|
*/
|
|
1755
1779
|
function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
1756
1780
|
{
|
|
1781
|
+
if (headlessMode)
|
|
1782
|
+
return new TileInfo;
|
|
1783
|
+
|
|
1757
1784
|
// if size is a number, make it a vector
|
|
1758
1785
|
if (typeof size === 'number')
|
|
1759
1786
|
{
|
|
@@ -1787,9 +1814,9 @@ class TileInfo
|
|
|
1787
1814
|
constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
1788
1815
|
{
|
|
1789
1816
|
/** @property {Vector2} - Top left corner of tile in pixels */
|
|
1790
|
-
this.pos = pos;
|
|
1817
|
+
this.pos = pos.copy();
|
|
1791
1818
|
/** @property {Vector2} - Size of tile in pixels */
|
|
1792
|
-
this.size = size;
|
|
1819
|
+
this.size = size.copy();
|
|
1793
1820
|
/** @property {Number} - Texture index to use */
|
|
1794
1821
|
this.textureIndex = textureIndex;
|
|
1795
1822
|
}
|
|
@@ -1881,7 +1908,7 @@ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
|
1881
1908
|
* @param {Color} [additiveColor=(0,0,0,0)] - Additive color to be applied
|
|
1882
1909
|
* @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
1883
1910
|
* @param {Boolean} [screenSpace=false] - If true the pos and size are in screen space
|
|
1884
|
-
* @param {CanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
1911
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
1885
1912
|
* @memberof Draw */
|
|
1886
1913
|
function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
1887
1914
|
angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace, context)
|
|
@@ -1954,7 +1981,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
1954
1981
|
* @param {Number} [angle]
|
|
1955
1982
|
* @param {Boolean} [useWebGL=glEnable]
|
|
1956
1983
|
* @param {Boolean} [screenSpace=false]
|
|
1957
|
-
* @param {CanvasRenderingContext2D} [context]
|
|
1984
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
1958
1985
|
* @memberof Draw */
|
|
1959
1986
|
function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
1960
1987
|
{
|
|
@@ -1968,7 +1995,7 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
1968
1995
|
* @param {Color} [color=(1,1,1,1)]
|
|
1969
1996
|
* @param {Boolean} [useWebGL=glEnable]
|
|
1970
1997
|
* @param {Boolean} [screenSpace=false]
|
|
1971
|
-
* @param {CanvasRenderingContext2D} [context]
|
|
1998
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
1972
1999
|
* @memberof Draw */
|
|
1973
2000
|
function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
|
|
1974
2001
|
{
|
|
@@ -1984,7 +2011,7 @@ function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, contex
|
|
|
1984
2011
|
* @param {Boolean} mirror
|
|
1985
2012
|
* @param {Function} drawFunction
|
|
1986
2013
|
* @param {Boolean} [screenSpace=false]
|
|
1987
|
-
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
2014
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
|
|
1988
2015
|
* @memberof Draw */
|
|
1989
2016
|
function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, context=mainContext)
|
|
1990
2017
|
{
|
|
@@ -2005,7 +2032,7 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
|
|
|
2005
2032
|
/** Enable normal or additive blend mode
|
|
2006
2033
|
* @param {Boolean} [additive]
|
|
2007
2034
|
* @param {Boolean} [useWebGL=glEnable]
|
|
2008
|
-
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
2035
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
|
|
2009
2036
|
* @memberof Draw */
|
|
2010
2037
|
function setBlendMode(additive, useWebGL=glEnable, context)
|
|
2011
2038
|
{
|
|
@@ -2030,7 +2057,7 @@ function setBlendMode(additive, useWebGL=glEnable, context)
|
|
|
2030
2057
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
2031
2058
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
2032
2059
|
* @param {String} [font=fontDefault]
|
|
2033
|
-
* @param {CanvasRenderingContext2D} [context=overlayContext]
|
|
2060
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
2034
2061
|
* @memberof Draw */
|
|
2035
2062
|
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context)
|
|
2036
2063
|
{
|
|
@@ -2047,7 +2074,7 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
2047
2074
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
2048
2075
|
* @param {CanvasTextAlign} [textAlign]
|
|
2049
2076
|
* @param {String} [font=fontDefault]
|
|
2050
|
-
* @param {CanvasRenderingContext2D} [context=overlayContext]
|
|
2077
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
2051
2078
|
* @memberof Draw */
|
|
2052
2079
|
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
|
|
2053
2080
|
{
|
|
@@ -2090,7 +2117,7 @@ class FontImage
|
|
|
2090
2117
|
* @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
|
|
2091
2118
|
* @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
|
|
2092
2119
|
* @param {Vector2} [paddingSize=(0,1)] - How much extra space to add between characters
|
|
2093
|
-
* @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
2120
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
2094
2121
|
*/
|
|
2095
2122
|
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), context=overlayContext)
|
|
2096
2123
|
{
|
|
@@ -2298,7 +2325,7 @@ function gamepadWasReleased(button, gamepad=0)
|
|
|
2298
2325
|
* @return {Vector2}
|
|
2299
2326
|
* @memberof Input */
|
|
2300
2327
|
function gamepadStick(stick, gamepad=0)
|
|
2301
|
-
{ return
|
|
2328
|
+
{ return gamepadStickData[gamepad] ? gamepadStickData[gamepad][stick] || vec2() : vec2(); }
|
|
2302
2329
|
|
|
2303
2330
|
///////////////////////////////////////////////////////////////////////////////
|
|
2304
2331
|
// Input update called by engine
|
|
@@ -2309,6 +2336,8 @@ let inputData = [[]];
|
|
|
2309
2336
|
|
|
2310
2337
|
function inputUpdate()
|
|
2311
2338
|
{
|
|
2339
|
+
if (headlessMode) return;
|
|
2340
|
+
|
|
2312
2341
|
// clear input when lost focus (prevent stuck keys)
|
|
2313
2342
|
isTouchDevice || document.hasFocus() || clearInput();
|
|
2314
2343
|
|
|
@@ -2321,6 +2350,8 @@ function inputUpdate()
|
|
|
2321
2350
|
|
|
2322
2351
|
function inputUpdatePost()
|
|
2323
2352
|
{
|
|
2353
|
+
if (headlessMode) return;
|
|
2354
|
+
|
|
2324
2355
|
// clear input to prepare for next frame
|
|
2325
2356
|
for (const deviceInputData of inputData)
|
|
2326
2357
|
for (const i in deviceInputData)
|
|
@@ -2329,9 +2360,12 @@ function inputUpdatePost()
|
|
|
2329
2360
|
}
|
|
2330
2361
|
|
|
2331
2362
|
///////////////////////////////////////////////////////////////////////////////
|
|
2332
|
-
//
|
|
2363
|
+
// Input event handlers
|
|
2333
2364
|
|
|
2365
|
+
function inputInit()
|
|
2334
2366
|
{
|
|
2367
|
+
if (headlessMode) return;
|
|
2368
|
+
|
|
2335
2369
|
onkeydown = (e)=>
|
|
2336
2370
|
{
|
|
2337
2371
|
if (debug && e.target != document.body) return;
|
|
@@ -2362,21 +2396,29 @@ function inputUpdatePost()
|
|
|
2362
2396
|
c == 'KeyA' ? 'ArrowLeft' :
|
|
2363
2397
|
c == 'KeyD' ? 'ArrowRight' : c : c;
|
|
2364
2398
|
}
|
|
2399
|
+
|
|
2400
|
+
// mouse event handlers
|
|
2401
|
+
onmousedown = (e)=>
|
|
2402
|
+
{
|
|
2403
|
+
isUsingGamepad = false;
|
|
2404
|
+
inputData[0][e.button] = 3;
|
|
2405
|
+
mousePosScreen = mouseToScreen(e);
|
|
2406
|
+
e.button && e.preventDefault();
|
|
2407
|
+
}
|
|
2408
|
+
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
2409
|
+
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
2410
|
+
onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
|
|
2411
|
+
oncontextmenu = (e)=> false; // prevent right click menu
|
|
2412
|
+
|
|
2413
|
+
// init touch input
|
|
2414
|
+
if (isTouchDevice)
|
|
2415
|
+
touchInputInit();
|
|
2365
2416
|
}
|
|
2366
2417
|
|
|
2367
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2368
|
-
// Mouse event handlers
|
|
2369
|
-
|
|
2370
|
-
onmousedown = (e)=> {isUsingGamepad = false; inputData[0][e.button] = 3; mousePosScreen = mouseToScreen(e); e.button && e.preventDefault();}
|
|
2371
|
-
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
2372
|
-
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
2373
|
-
onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
|
|
2374
|
-
oncontextmenu = (e)=> false; // prevent right click menu
|
|
2375
|
-
|
|
2376
2418
|
// convert a mouse or touch event position to screen space
|
|
2377
2419
|
function mouseToScreen(mousePos)
|
|
2378
2420
|
{
|
|
2379
|
-
if (!mainCanvas)
|
|
2421
|
+
if (!mainCanvas || headlessMode)
|
|
2380
2422
|
return vec2(); // fix bug that can occur if user clicks before page loads
|
|
2381
2423
|
|
|
2382
2424
|
const rect = mainCanvas.getBoundingClientRect();
|
|
@@ -2388,7 +2430,7 @@ function mouseToScreen(mousePos)
|
|
|
2388
2430
|
// Gamepad input
|
|
2389
2431
|
|
|
2390
2432
|
// gamepad internal variables
|
|
2391
|
-
const
|
|
2433
|
+
const gamepadStickData = [];
|
|
2392
2434
|
|
|
2393
2435
|
// gamepads are updated by engine every frame automatically
|
|
2394
2436
|
function gamepadsUpdate()
|
|
@@ -2405,14 +2447,11 @@ function gamepadsUpdate()
|
|
|
2405
2447
|
// update touch gamepad if enabled
|
|
2406
2448
|
if (touchGamepadEnable && isTouchDevice)
|
|
2407
2449
|
{
|
|
2408
|
-
|
|
2409
|
-
if (!touchGamepadButtons)
|
|
2410
|
-
createTouchGamepad();
|
|
2411
|
-
|
|
2450
|
+
ASSERT(touchGamepadButtons, 'set touchGamepadEnable before calling init!');
|
|
2412
2451
|
if (touchGamepadTimer.isSet())
|
|
2413
2452
|
{
|
|
2414
2453
|
// read virtual analog stick
|
|
2415
|
-
const sticks =
|
|
2454
|
+
const sticks = gamepadStickData[0] || (gamepadStickData[0] = []);
|
|
2416
2455
|
sticks[0] = vec2();
|
|
2417
2456
|
if (touchGamepadAnalog)
|
|
2418
2457
|
sticks[0] = applyDeadZones(touchGamepadStick);
|
|
@@ -2429,7 +2468,8 @@ function gamepadsUpdate()
|
|
|
2429
2468
|
for (let i=10; i--;)
|
|
2430
2469
|
{
|
|
2431
2470
|
const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
|
|
2432
|
-
|
|
2471
|
+
const wasDown = gamepadIsDown(j,0);
|
|
2472
|
+
data[j] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
2433
2473
|
}
|
|
2434
2474
|
}
|
|
2435
2475
|
}
|
|
@@ -2449,7 +2489,7 @@ function gamepadsUpdate()
|
|
|
2449
2489
|
// get or create gamepad data
|
|
2450
2490
|
const gamepad = gamepads[i];
|
|
2451
2491
|
const data = inputData[i+1] || (inputData[i+1] = []);
|
|
2452
|
-
const sticks =
|
|
2492
|
+
const sticks = gamepadStickData[i] || (gamepadStickData[i] = []);
|
|
2453
2493
|
|
|
2454
2494
|
if (gamepad)
|
|
2455
2495
|
{
|
|
@@ -2488,28 +2528,44 @@ function gamepadsUpdate()
|
|
|
2488
2528
|
* @param {Number|Array} [pattern] - single value in ms or vibration interval array
|
|
2489
2529
|
* @memberof Input */
|
|
2490
2530
|
function vibrate(pattern=100)
|
|
2491
|
-
{ vibrateEnable && navigator && navigator.vibrate && navigator.vibrate(pattern); }
|
|
2531
|
+
{ vibrateEnable && !headlessMode && navigator && navigator.vibrate && navigator.vibrate(pattern); }
|
|
2492
2532
|
|
|
2493
2533
|
/** Cancel any ongoing vibration
|
|
2494
2534
|
* @memberof Input */
|
|
2495
2535
|
function vibrateStop() { vibrate(0); }
|
|
2496
2536
|
|
|
2497
2537
|
///////////////////////////////////////////////////////////////////////////////
|
|
2498
|
-
// Touch input
|
|
2538
|
+
// Touch input & virtual on screen gamepad
|
|
2499
2539
|
|
|
2500
2540
|
/** True if a touch device has been detected
|
|
2501
2541
|
* @memberof Input */
|
|
2502
|
-
const isTouchDevice = window.ontouchstart !== undefined;
|
|
2542
|
+
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
2543
|
+
|
|
2544
|
+
// touch gamepad internal variables
|
|
2545
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
2503
2546
|
|
|
2504
2547
|
// try to enable touch mouse
|
|
2505
|
-
|
|
2548
|
+
function touchInputInit()
|
|
2506
2549
|
{
|
|
2550
|
+
// add non passive touch event listeners
|
|
2551
|
+
let handleTouch = handleTouchDefault;
|
|
2552
|
+
if (touchGamepadEnable)
|
|
2553
|
+
{
|
|
2554
|
+
// touch input internal variables
|
|
2555
|
+
handleTouch = handleTouchGamepad;
|
|
2556
|
+
touchGamepadButtons = [];
|
|
2557
|
+
touchGamepadStick = vec2();
|
|
2558
|
+
}
|
|
2559
|
+
document.addEventListener('touchstart', (e) => handleTouch(e), { passive: false });
|
|
2560
|
+
document.addEventListener('touchmove', (e) => handleTouch(e), { passive: false });
|
|
2561
|
+
document.addEventListener('touchend', (e) => handleTouch(e), { passive: false });
|
|
2562
|
+
|
|
2507
2563
|
// override mouse events
|
|
2508
|
-
let wasTouching;
|
|
2509
2564
|
onmousedown = onmouseup = ()=> 0;
|
|
2510
2565
|
|
|
2511
2566
|
// handle all touch events the same way
|
|
2512
|
-
|
|
2567
|
+
let wasTouching;
|
|
2568
|
+
function handleTouchDefault(e)
|
|
2513
2569
|
{
|
|
2514
2570
|
// fix stalled audio requiring user interaction
|
|
2515
2571
|
if (soundEnable && audioContext && audioContext.state != 'running')
|
|
@@ -2538,27 +2594,14 @@ if (isTouchDevice)
|
|
|
2538
2594
|
// must return true so the document will get focus
|
|
2539
2595
|
return true;
|
|
2540
2596
|
}
|
|
2541
|
-
}
|
|
2542
|
-
|
|
2543
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2544
|
-
// touch gamepad, virtual on screen gamepad emulator for touch devices
|
|
2545
|
-
|
|
2546
|
-
// touch input internal variables
|
|
2547
|
-
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
2548
|
-
|
|
2549
|
-
// create the touch gamepad, called automatically by the engine
|
|
2550
|
-
function createTouchGamepad()
|
|
2551
|
-
{
|
|
2552
|
-
// touch input internal variables
|
|
2553
|
-
touchGamepadButtons = [];
|
|
2554
|
-
touchGamepadStick = vec2();
|
|
2555
2597
|
|
|
2556
|
-
|
|
2557
|
-
|
|
2598
|
+
// special handling for virtual gamepad mode
|
|
2599
|
+
function handleTouchGamepad(e)
|
|
2558
2600
|
{
|
|
2559
2601
|
// clear touch gamepad input
|
|
2560
2602
|
touchGamepadStick = vec2();
|
|
2561
2603
|
touchGamepadButtons = [];
|
|
2604
|
+
isUsingGamepad = true;
|
|
2562
2605
|
|
|
2563
2606
|
const touching = e.touches.length;
|
|
2564
2607
|
if (touching)
|
|
@@ -2599,9 +2642,8 @@ function createTouchGamepad()
|
|
|
2599
2642
|
}
|
|
2600
2643
|
}
|
|
2601
2644
|
|
|
2602
|
-
// call default touch handler
|
|
2603
|
-
|
|
2604
|
-
isUsingGamepad = true;
|
|
2645
|
+
// call default touch handler so normal touch events still work
|
|
2646
|
+
handleTouchDefault(e);
|
|
2605
2647
|
|
|
2606
2648
|
// must return true so the document will get focus
|
|
2607
2649
|
return true;
|
|
@@ -2620,32 +2662,33 @@ function touchGamepadRender()
|
|
|
2620
2662
|
return;
|
|
2621
2663
|
|
|
2622
2664
|
// setup the canvas
|
|
2623
|
-
overlayContext
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2665
|
+
const context = overlayContext;
|
|
2666
|
+
context.save();
|
|
2667
|
+
context.globalAlpha = alpha*touchGamepadAlpha;
|
|
2668
|
+
context.strokeStyle = '#fff';
|
|
2669
|
+
context.lineWidth = 3;
|
|
2627
2670
|
|
|
2628
2671
|
// draw left analog stick
|
|
2629
|
-
|
|
2630
|
-
|
|
2672
|
+
context.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
|
|
2673
|
+
context.beginPath();
|
|
2631
2674
|
|
|
2632
2675
|
const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2633
2676
|
if (touchGamepadAnalog) // draw circle shaped gamepad
|
|
2634
2677
|
{
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2678
|
+
context.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
|
|
2679
|
+
context.fill();
|
|
2680
|
+
context.stroke();
|
|
2638
2681
|
}
|
|
2639
2682
|
else // draw cross shaped gamepad
|
|
2640
2683
|
{
|
|
2641
2684
|
for(let i=10; i--;)
|
|
2642
2685
|
{
|
|
2643
2686
|
const angle = i*PI/4;
|
|
2644
|
-
|
|
2645
|
-
i%2 &&
|
|
2646
|
-
i==1 &&
|
|
2687
|
+
context.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
|
|
2688
|
+
i%2 && context.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
|
|
2689
|
+
i==1 && context.fill();
|
|
2647
2690
|
}
|
|
2648
|
-
|
|
2691
|
+
context.stroke();
|
|
2649
2692
|
}
|
|
2650
2693
|
|
|
2651
2694
|
// draw right face buttons
|
|
@@ -2653,15 +2696,15 @@ function touchGamepadRender()
|
|
|
2653
2696
|
for (let i=4; i--;)
|
|
2654
2697
|
{
|
|
2655
2698
|
const pos = rightCenter.add(vec2().setDirection(i, touchGamepadSize/2));
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2699
|
+
context.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
|
|
2700
|
+
context.beginPath();
|
|
2701
|
+
context.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
|
|
2702
|
+
context.fill();
|
|
2703
|
+
context.stroke();
|
|
2661
2704
|
}
|
|
2662
2705
|
|
|
2663
2706
|
// set canvas back to normal
|
|
2664
|
-
|
|
2707
|
+
context.restore();
|
|
2665
2708
|
}
|
|
2666
2709
|
/**
|
|
2667
2710
|
* LittleJS Audio System
|
|
@@ -2696,7 +2739,7 @@ class Sound
|
|
|
2696
2739
|
*/
|
|
2697
2740
|
constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
|
|
2698
2741
|
{
|
|
2699
|
-
if (!soundEnable) return;
|
|
2742
|
+
if (!soundEnable || headlessMode) return;
|
|
2700
2743
|
|
|
2701
2744
|
/** @property {Number} - World space max range of sound, will not play if camera is farther away */
|
|
2702
2745
|
this.range = range;
|
|
@@ -2710,8 +2753,8 @@ class Sound
|
|
|
2710
2753
|
if (zzfxSound)
|
|
2711
2754
|
{
|
|
2712
2755
|
// generate zzfx sound now for fast playback
|
|
2713
|
-
|
|
2714
|
-
zzfxSound[1]
|
|
2756
|
+
const defaultRandomness = .05;
|
|
2757
|
+
this.randomness = zzfxSound[1] || defaultRandomness;
|
|
2715
2758
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
2716
2759
|
this.sampleRate = zzfxR;
|
|
2717
2760
|
}
|
|
@@ -2727,7 +2770,7 @@ class Sound
|
|
|
2727
2770
|
*/
|
|
2728
2771
|
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false)
|
|
2729
2772
|
{
|
|
2730
|
-
if (!soundEnable || !this.sampleChannels) return;
|
|
2773
|
+
if (!soundEnable || !this.sampleChannels || headlessMode) return;
|
|
2731
2774
|
|
|
2732
2775
|
let pan;
|
|
2733
2776
|
if (pos)
|
|
@@ -2810,7 +2853,9 @@ class SoundWave extends Sound
|
|
|
2810
2853
|
super(undefined, range, taper);
|
|
2811
2854
|
this.randomness = randomness;
|
|
2812
2855
|
|
|
2813
|
-
if (!soundEnable) return;
|
|
2856
|
+
if (!soundEnable || headlessMode) return;
|
|
2857
|
+
if (!audioContext)
|
|
2858
|
+
audioContext = new AudioContext; // create audio context
|
|
2814
2859
|
|
|
2815
2860
|
fetch(filename)
|
|
2816
2861
|
.then(response => response.arrayBuffer())
|
|
@@ -2864,7 +2909,7 @@ class Music extends Sound
|
|
|
2864
2909
|
{
|
|
2865
2910
|
super(undefined);
|
|
2866
2911
|
|
|
2867
|
-
if (!soundEnable) return;
|
|
2912
|
+
if (!soundEnable || headlessMode) return;
|
|
2868
2913
|
this.randomness = 0;
|
|
2869
2914
|
this.sampleChannels = zzfxM(...zzfxMusic);
|
|
2870
2915
|
this.sampleRate = zzfxR;
|
|
@@ -2887,7 +2932,7 @@ class Music extends Sound
|
|
|
2887
2932
|
* @memberof Audio */
|
|
2888
2933
|
function playAudioFile(filename, volume=1, loop=false)
|
|
2889
2934
|
{
|
|
2890
|
-
if (!soundEnable) return;
|
|
2935
|
+
if (!soundEnable || headlessMode) return;
|
|
2891
2936
|
|
|
2892
2937
|
const audio = new Audio(filename);
|
|
2893
2938
|
audio.volume = soundVolume * volume;
|
|
@@ -2906,7 +2951,7 @@ function playAudioFile(filename, volume=1, loop=false)
|
|
|
2906
2951
|
* @memberof Audio */
|
|
2907
2952
|
function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
2908
2953
|
{
|
|
2909
|
-
if (!soundEnable || !speechSynthesis) return;
|
|
2954
|
+
if (!soundEnable || !speechSynthesis || headlessMode) return;
|
|
2910
2955
|
|
|
2911
2956
|
// common languages (not supported by all browsers)
|
|
2912
2957
|
// en - english, it - italian, fr - french, de - german, es - spanish
|
|
@@ -2939,7 +2984,7 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
2939
2984
|
/** Audio context used by the engine
|
|
2940
2985
|
* @type {AudioContext}
|
|
2941
2986
|
* @memberof Audio */
|
|
2942
|
-
let audioContext
|
|
2987
|
+
let audioContext;
|
|
2943
2988
|
|
|
2944
2989
|
/** Keep track if audio was suspended when last sound was played
|
|
2945
2990
|
* @type {Boolean}
|
|
@@ -2957,7 +3002,9 @@ let audioSuspended = false;
|
|
|
2957
3002
|
* @memberof Audio */
|
|
2958
3003
|
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR)
|
|
2959
3004
|
{
|
|
2960
|
-
if (!soundEnable) return;
|
|
3005
|
+
if (!soundEnable || headlessMode) return;
|
|
3006
|
+
if (!audioContext)
|
|
3007
|
+
audioContext = new AudioContext; // create audio context
|
|
2961
3008
|
|
|
2962
3009
|
// prevent sounds from building up if they can't be played
|
|
2963
3010
|
const audioWasSuspended = audioSuspended;
|
|
@@ -3003,7 +3050,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3003
3050
|
* @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
|
|
3004
3051
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
3005
3052
|
* @memberof Audio */
|
|
3006
|
-
function zzfx(...zzfxSound) { return
|
|
3053
|
+
function zzfx(...zzfxSound) { return new Sound(zzfxSound).play(); }
|
|
3007
3054
|
|
|
3008
3055
|
/** Sample rate used for all ZzFX sounds
|
|
3009
3056
|
* @default 44100
|
|
@@ -3012,7 +3059,7 @@ const zzfxR = 44100;
|
|
|
3012
3059
|
|
|
3013
3060
|
/** Generate samples for a ZzFX sound
|
|
3014
3061
|
* @param {Number} [volume] - Volume scale (percent)
|
|
3015
|
-
* @param {Number} [randomness] -
|
|
3062
|
+
* @param {Number} [randomness] - Unused in this fuction, handled by Sound class
|
|
3016
3063
|
* @param {Number} [frequency] - Frequency of sound (Hz)
|
|
3017
3064
|
* @param {Number} [attack] - Attack time, how fast sound starts (seconds)
|
|
3018
3065
|
* @param {Number} [sustain] - Sustain time, how long sound holds (seconds)
|
|
@@ -3038,17 +3085,18 @@ const zzfxR = 44100;
|
|
|
3038
3085
|
function zzfxG
|
|
3039
3086
|
(
|
|
3040
3087
|
// parameters
|
|
3041
|
-
volume = 1, randomness =
|
|
3088
|
+
volume = 1, randomness = 0, frequency = 220, attack = 0, sustain = 0,
|
|
3042
3089
|
release = .1, shape = 0, shapeCurve = 1, slide = 0, deltaSlide = 0,
|
|
3043
3090
|
pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, modulation = 0,
|
|
3044
3091
|
bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0, filter = 0
|
|
3045
3092
|
)
|
|
3046
3093
|
{
|
|
3094
|
+
// LJS Note: ZZFX modded so randomness is handled by Sound class
|
|
3095
|
+
|
|
3047
3096
|
// init parameters
|
|
3048
3097
|
let PI2 = PI*2, sampleRate = zzfxR,
|
|
3049
3098
|
startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
|
|
3050
|
-
startFrequency = frequency *=
|
|
3051
|
-
rand(1 + randomness, 1-randomness) * PI2 / sampleRate,
|
|
3099
|
+
startFrequency = frequency *= PI2 / sampleRate,
|
|
3052
3100
|
b = [], t = 0, tm = 0, i = 0, j = 1, r = 0, c = 0, s = 0, f, length,
|
|
3053
3101
|
|
|
3054
3102
|
// biquad LP/HP filter
|
|
@@ -3070,7 +3118,6 @@ function zzfxG
|
|
|
3070
3118
|
pitchJump *= PI2 / sampleRate;
|
|
3071
3119
|
pitchJumpTime *= sampleRate;
|
|
3072
3120
|
repeatTime = repeatTime * sampleRate | 0;
|
|
3073
|
-
volume *= soundVolume;
|
|
3074
3121
|
|
|
3075
3122
|
// generate waveform
|
|
3076
3123
|
for(length = attack + decay + sustain + release + delay | 0;
|
|
@@ -3416,7 +3463,7 @@ class TileLayer extends EngineObject
|
|
|
3416
3463
|
|
|
3417
3464
|
/** @property {HTMLCanvasElement} - The canvas used by this tile layer */
|
|
3418
3465
|
this.canvas = document.createElement('canvas');
|
|
3419
|
-
/** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
3466
|
+
/** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
3420
3467
|
this.context = this.canvas.getContext('2d');
|
|
3421
3468
|
/** @property {Vector2} - How much to scale this layer when rendered */
|
|
3422
3469
|
this.scale = scale;
|
|
@@ -3427,6 +3474,17 @@ class TileLayer extends EngineObject
|
|
|
3427
3474
|
this.data = [];
|
|
3428
3475
|
for (let j = this.size.area(); j--;)
|
|
3429
3476
|
this.data.push(new TileLayerData);
|
|
3477
|
+
|
|
3478
|
+
if (headlessMode)
|
|
3479
|
+
{
|
|
3480
|
+
// disable rendering
|
|
3481
|
+
this.redraw = () => {};
|
|
3482
|
+
this.render = () => {};
|
|
3483
|
+
this.redrawStart = () => {};
|
|
3484
|
+
this.redrawEnd = () => {};
|
|
3485
|
+
this.drawTileData = () => {};
|
|
3486
|
+
this.drawCanvas2D = () => {};
|
|
3487
|
+
}
|
|
3430
3488
|
}
|
|
3431
3489
|
|
|
3432
3490
|
/** Set data at a given position in the array
|
|
@@ -3457,7 +3515,7 @@ class TileLayer extends EngineObject
|
|
|
3457
3515
|
ASSERT(mainContext != this.context, 'must call redrawEnd() after drawing tiles');
|
|
3458
3516
|
|
|
3459
3517
|
// flush and copy gl canvas because tile canvas does not use webgl
|
|
3460
|
-
|
|
3518
|
+
!glOverlay && !this.isOverlay && glCopyToContext(mainContext);
|
|
3461
3519
|
|
|
3462
3520
|
// draw the entire cached level onto the canvas
|
|
3463
3521
|
const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
|
|
@@ -3507,14 +3565,14 @@ class TileLayer extends EngineObject
|
|
|
3507
3565
|
this.context.imageSmoothingEnabled = !canvasPixelated;
|
|
3508
3566
|
|
|
3509
3567
|
// setup gl rendering if enabled
|
|
3510
|
-
|
|
3568
|
+
glPreRender();
|
|
3511
3569
|
}
|
|
3512
3570
|
|
|
3513
3571
|
/** Call to end the redraw process */
|
|
3514
3572
|
redrawEnd()
|
|
3515
3573
|
{
|
|
3516
3574
|
ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
|
|
3517
|
-
|
|
3575
|
+
glCopyToContext(mainContext, true);
|
|
3518
3576
|
//debugSaveCanvas(this.canvas);
|
|
3519
3577
|
|
|
3520
3578
|
// set stuff back to normal
|
|
@@ -3839,20 +3897,21 @@ class ParticleEmitter extends EngineObject
|
|
|
3839
3897
|
class Particle extends EngineObject
|
|
3840
3898
|
{
|
|
3841
3899
|
/**
|
|
3842
|
-
* Create a particle with the
|
|
3843
|
-
*
|
|
3844
|
-
* @param {
|
|
3845
|
-
* @param {
|
|
3846
|
-
* @param {
|
|
3847
|
-
* @param {Color}
|
|
3848
|
-
* @param {
|
|
3849
|
-
* @param {Number}
|
|
3850
|
-
* @param {Number}
|
|
3851
|
-
* @param {Number}
|
|
3852
|
-
* @param {
|
|
3853
|
-
* @param {
|
|
3900
|
+
* Create a particle with the passed in settings
|
|
3901
|
+
* Typically this is created automatically by a ParticleEmitter
|
|
3902
|
+
* @param {Vector2} position - World space position of the particle
|
|
3903
|
+
* @param {TileInfo} tileInfo - Tile info to render particles
|
|
3904
|
+
* @param {Number} angle - Angle to rotate the particle
|
|
3905
|
+
* @param {Color} colorStart - Color at start of life
|
|
3906
|
+
* @param {Color} colorEnd - Color at end of life
|
|
3907
|
+
* @param {Number} lifeTime - How long to live for
|
|
3908
|
+
* @param {Number} sizeStart - Size at start of life
|
|
3909
|
+
* @param {Number} sizeEnd - Size at end of life
|
|
3910
|
+
* @param {Number} fadeRate - How quick to fade in/out
|
|
3911
|
+
* @param {Boolean} additive - Does it use additive blend mode
|
|
3912
|
+
* @param {Number} trailScale - If a trail, how long to make it
|
|
3854
3913
|
* @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
|
|
3855
|
-
* @param {Function}
|
|
3914
|
+
* @param {Function} [destroyCallback] - Callback when particle dies
|
|
3856
3915
|
*/
|
|
3857
3916
|
constructor(position, tileInfo, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, fadeRate, additive, trailScale, localSpaceEmitter, destroyCallback
|
|
3858
3917
|
)
|
|
@@ -4261,6 +4320,8 @@ let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData,
|
|
|
4261
4320
|
// Initalize WebGL, called automatically by the engine
|
|
4262
4321
|
function glInit()
|
|
4263
4322
|
{
|
|
4323
|
+
if (!glEnable || headlessMode) return;
|
|
4324
|
+
|
|
4264
4325
|
// create the canvas and textures
|
|
4265
4326
|
glCanvas = document.createElement('canvas');
|
|
4266
4327
|
glContext = glCanvas.getContext('webgl2');
|
|
@@ -4273,11 +4334,11 @@ function glInit()
|
|
|
4273
4334
|
'#version 300 es\n' + // specify GLSL ES version
|
|
4274
4335
|
'precision highp float;'+ // use highp for better accuracy
|
|
4275
4336
|
'uniform mat4 m;'+ // transform matrix
|
|
4276
|
-
'in vec2 g;'+ // geometry
|
|
4277
|
-
'in vec4 p,u,c,a;'+ // position/size, uvs, color, additiveColor
|
|
4278
|
-
'in float r;'+ // rotation
|
|
4279
|
-
'out vec2 v;'+ //
|
|
4280
|
-
'out vec4 d,e;'+ //
|
|
4337
|
+
'in vec2 g;'+ // in: geometry
|
|
4338
|
+
'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
|
|
4339
|
+
'in float r;'+ // in: rotation
|
|
4340
|
+
'out vec2 v;'+ // out: uv
|
|
4341
|
+
'out vec4 d,e;'+ // out: color, additiveColor
|
|
4281
4342
|
'void main(){'+ // shader entry point
|
|
4282
4343
|
'vec2 s=(g-.5)*p.zw;'+ // get size offset
|
|
4283
4344
|
'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
|
|
@@ -4287,10 +4348,10 @@ function glInit()
|
|
|
4287
4348
|
,
|
|
4288
4349
|
'#version 300 es\n' + // specify GLSL ES version
|
|
4289
4350
|
'precision highp float;'+ // use highp for better accuracy
|
|
4290
|
-
'in vec2 v;'+ // uv
|
|
4291
|
-
'in vec4 d,e;'+ // color, additiveColor
|
|
4292
4351
|
'uniform sampler2D s;'+ // texture
|
|
4293
|
-
'
|
|
4352
|
+
'in vec2 v;'+ // in: uv
|
|
4353
|
+
'in vec4 d,e;'+ // in: color, additiveColor
|
|
4354
|
+
'out vec4 c;'+ // out: color
|
|
4294
4355
|
'void main(){'+ // shader entry point
|
|
4295
4356
|
'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
|
|
4296
4357
|
'}' // end of shader
|
|
@@ -4312,9 +4373,11 @@ function glInit()
|
|
|
4312
4373
|
// Setup render each frame, called automatically by engine
|
|
4313
4374
|
function glPreRender()
|
|
4314
4375
|
{
|
|
4376
|
+
if (!glEnable || headlessMode) return;
|
|
4377
|
+
|
|
4315
4378
|
// clear and set to same size as main canvas
|
|
4316
4379
|
glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
|
|
4317
|
-
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
4380
|
+
//glContext.clear(gl_COLOR_BUFFER_BIT); // auto cleared when size is set
|
|
4318
4381
|
|
|
4319
4382
|
// set up the shader
|
|
4320
4383
|
glContext.useProgram(glShader);
|
|
@@ -4348,12 +4411,12 @@ function glPreRender()
|
|
|
4348
4411
|
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
4349
4412
|
const p = vec2(-1).subtract(cameraPos.multiply(s));
|
|
4350
4413
|
glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), false,
|
|
4351
|
-
|
|
4414
|
+
[
|
|
4352
4415
|
s.x, 0, 0, 0,
|
|
4353
4416
|
0, s.y, 0, 0,
|
|
4354
4417
|
1, 1, 1, 1,
|
|
4355
4418
|
p.x, p.y, 0, 0
|
|
4356
|
-
]
|
|
4419
|
+
]
|
|
4357
4420
|
);
|
|
4358
4421
|
}
|
|
4359
4422
|
|
|
@@ -4364,7 +4427,7 @@ function glPreRender()
|
|
|
4364
4427
|
function glSetTexture(texture)
|
|
4365
4428
|
{
|
|
4366
4429
|
// must flush cache with the old texture to set a new one
|
|
4367
|
-
if (texture == glActiveTexture)
|
|
4430
|
+
if (headlessMode || texture == glActiveTexture)
|
|
4368
4431
|
return;
|
|
4369
4432
|
|
|
4370
4433
|
glFlush();
|
|
@@ -4424,7 +4487,6 @@ function glCreateTexture(image)
|
|
|
4424
4487
|
const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
|
|
4425
4488
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
|
|
4426
4489
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
|
|
4427
|
-
|
|
4428
4490
|
return texture;
|
|
4429
4491
|
}
|
|
4430
4492
|
|
|
@@ -4448,12 +4510,12 @@ function glFlush()
|
|
|
4448
4510
|
}
|
|
4449
4511
|
|
|
4450
4512
|
/** Draw any sprites still in the buffer, copy to main canvas and clear
|
|
4451
|
-
* @param {CanvasRenderingContext2D} context
|
|
4513
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
|
4452
4514
|
* @param {Boolean} [forceDraw]
|
|
4453
4515
|
* @memberof WebGL */
|
|
4454
4516
|
function glCopyToContext(context, forceDraw=false)
|
|
4455
4517
|
{
|
|
4456
|
-
if (!glInstanceCount && !forceDraw) return;
|
|
4518
|
+
if (!glEnable || !glInstanceCount && !forceDraw) return;
|
|
4457
4519
|
|
|
4458
4520
|
glFlush();
|
|
4459
4521
|
|
|
@@ -4510,7 +4572,7 @@ let glPostShader, glPostTexture, glPostIncludeOverlay;
|
|
|
4510
4572
|
function glInitPostProcess(shaderCode, includeOverlay=false)
|
|
4511
4573
|
{
|
|
4512
4574
|
ASSERT(!glPostShader, 'can only have 1 post effects shader');
|
|
4513
|
-
|
|
4575
|
+
if (headlessMode) return;
|
|
4514
4576
|
if (!shaderCode) // default shader pass through
|
|
4515
4577
|
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
|
|
4516
4578
|
|
|
@@ -4549,8 +4611,7 @@ function glInitPostProcess(shaderCode, includeOverlay=false)
|
|
|
4549
4611
|
// Render the post processing shader, called automatically by the engine
|
|
4550
4612
|
function glRenderPostProcess()
|
|
4551
4613
|
{
|
|
4552
|
-
if (!glPostShader)
|
|
4553
|
-
return;
|
|
4614
|
+
if (!glPostShader || headlessMode) return;
|
|
4554
4615
|
|
|
4555
4616
|
// prepare to render post process shader
|
|
4556
4617
|
if (glEnable)
|
|
@@ -4656,7 +4717,7 @@ const engineName = 'LittleJS';
|
|
|
4656
4717
|
* @type {String}
|
|
4657
4718
|
* @default
|
|
4658
4719
|
* @memberof Engine */
|
|
4659
|
-
const engineVersion = '1.9.
|
|
4720
|
+
const engineVersion = '1.9.6';
|
|
4660
4721
|
|
|
4661
4722
|
/** Frames per second to update
|
|
4662
4723
|
* @type {Number}
|
|
@@ -4733,7 +4794,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4733
4794
|
mainContext.imageSmoothingEnabled = !canvasPixelated;
|
|
4734
4795
|
|
|
4735
4796
|
// setup gl rendering if enabled
|
|
4736
|
-
|
|
4797
|
+
glPreRender();
|
|
4737
4798
|
}
|
|
4738
4799
|
|
|
4739
4800
|
// internal update loop for engine
|
|
@@ -4752,11 +4813,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4752
4813
|
frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
|
|
4753
4814
|
if (!debugSpeedUp)
|
|
4754
4815
|
frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp in case of slow framerate
|
|
4816
|
+
|
|
4755
4817
|
updateCanvas();
|
|
4756
4818
|
|
|
4757
4819
|
if (paused)
|
|
4758
4820
|
{
|
|
4759
|
-
//
|
|
4821
|
+
// update object transforms even when paused
|
|
4822
|
+
for (const o of engineObjects)
|
|
4823
|
+
o.parent || o.updateTransforms();
|
|
4760
4824
|
inputUpdate();
|
|
4761
4825
|
debugUpdate();
|
|
4762
4826
|
gameUpdatePost();
|
|
@@ -4768,7 +4832,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4768
4832
|
let deltaSmooth = 0;
|
|
4769
4833
|
if (frameTimeBufferMS < 0 && frameTimeBufferMS > -9)
|
|
4770
4834
|
{
|
|
4771
|
-
// force
|
|
4835
|
+
// force at least one update each frame since it is waiting for refresh
|
|
4772
4836
|
deltaSmooth = frameTimeBufferMS;
|
|
4773
4837
|
frameTimeBufferMS = 0;
|
|
4774
4838
|
}
|
|
@@ -4793,34 +4857,37 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4793
4857
|
// add the time smoothing back in
|
|
4794
4858
|
frameTimeBufferMS += deltaSmooth;
|
|
4795
4859
|
}
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
enginePreRender();
|
|
4799
|
-
gameRender();
|
|
4800
|
-
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
4801
|
-
for (const o of engineObjects)
|
|
4802
|
-
o.destroyed || o.render();
|
|
4803
|
-
gameRenderPost();
|
|
4804
|
-
glRenderPostProcess();
|
|
4805
|
-
medalsRender();
|
|
4806
|
-
touchGamepadRender();
|
|
4807
|
-
debugRender();
|
|
4808
|
-
glEnable && glCopyToContext(mainContext);
|
|
4809
|
-
|
|
4810
|
-
if (showWatermark)
|
|
4860
|
+
|
|
4861
|
+
if (!headlessMode)
|
|
4811
4862
|
{
|
|
4812
|
-
//
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4863
|
+
// render sort then render while removing destroyed objects
|
|
4864
|
+
enginePreRender();
|
|
4865
|
+
gameRender();
|
|
4866
|
+
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
4867
|
+
for (const o of engineObjects)
|
|
4868
|
+
o.destroyed || o.render();
|
|
4869
|
+
gameRenderPost();
|
|
4870
|
+
glRenderPostProcess();
|
|
4871
|
+
medalsRender();
|
|
4872
|
+
touchGamepadRender();
|
|
4873
|
+
debugRender();
|
|
4874
|
+
glCopyToContext(mainContext);
|
|
4875
|
+
|
|
4876
|
+
if (showWatermark)
|
|
4877
|
+
{
|
|
4878
|
+
// update fps
|
|
4879
|
+
overlayContext.textAlign = 'right';
|
|
4880
|
+
overlayContext.textBaseline = 'top';
|
|
4881
|
+
overlayContext.font = '1em monospace';
|
|
4882
|
+
overlayContext.fillStyle = '#000';
|
|
4883
|
+
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
4884
|
+
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
4885
|
+
+ (glEnable ? ' GL' : ' 2D') ;
|
|
4886
|
+
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
4887
|
+
overlayContext.fillStyle = '#fff';
|
|
4888
|
+
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
4889
|
+
drawCount = 0;
|
|
4890
|
+
}
|
|
4824
4891
|
}
|
|
4825
4892
|
|
|
4826
4893
|
requestAnimationFrame(engineUpdate);
|
|
@@ -4828,6 +4895,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4828
4895
|
|
|
4829
4896
|
function updateCanvas()
|
|
4830
4897
|
{
|
|
4898
|
+
if (headlessMode) return;
|
|
4899
|
+
|
|
4831
4900
|
if (canvasFixedSize.x)
|
|
4832
4901
|
{
|
|
4833
4902
|
// clear canvas and set fixed size
|
|
@@ -4855,8 +4924,20 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4855
4924
|
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
4856
4925
|
}
|
|
4857
4926
|
|
|
4927
|
+
function startEngine()
|
|
4928
|
+
{
|
|
4929
|
+
gameInit();
|
|
4930
|
+
engineUpdate();
|
|
4931
|
+
}
|
|
4932
|
+
|
|
4933
|
+
if (headlessMode)
|
|
4934
|
+
{
|
|
4935
|
+
startEngine();
|
|
4936
|
+
return;
|
|
4937
|
+
}
|
|
4938
|
+
|
|
4858
4939
|
// setup html
|
|
4859
|
-
|
|
4940
|
+
const styleBody =
|
|
4860
4941
|
'margin:0;overflow:hidden;' + // fill the window
|
|
4861
4942
|
'background:#000;' + // set background color
|
|
4862
4943
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
@@ -4868,8 +4949,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4868
4949
|
mainContext = mainCanvas.getContext('2d');
|
|
4869
4950
|
|
|
4870
4951
|
// init stuff and start engine
|
|
4952
|
+
inputInit();
|
|
4871
4953
|
debugInit();
|
|
4872
|
-
|
|
4954
|
+
glInit();
|
|
4873
4955
|
|
|
4874
4956
|
// create overlay canvas for hud to appear above gl canvas
|
|
4875
4957
|
document.body.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
@@ -4910,12 +4992,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4910
4992
|
}));
|
|
4911
4993
|
|
|
4912
4994
|
// load all of the images
|
|
4913
|
-
Promise.all(promises).then(
|
|
4914
|
-
{
|
|
4915
|
-
// start the engine
|
|
4916
|
-
gameInit();
|
|
4917
|
-
engineUpdate();
|
|
4918
|
-
});
|
|
4995
|
+
Promise.all(promises).then(startEngine);
|
|
4919
4996
|
}
|
|
4920
4997
|
|
|
4921
4998
|
/** Update each engine object, remove destroyed objects, and update time
|
|
@@ -4936,7 +5013,14 @@ function engineObjectsUpdate()
|
|
|
4936
5013
|
}
|
|
4937
5014
|
}
|
|
4938
5015
|
for (const o of engineObjects)
|
|
4939
|
-
|
|
5016
|
+
{
|
|
5017
|
+
// update top level objects
|
|
5018
|
+
if (!o.parent)
|
|
5019
|
+
{
|
|
5020
|
+
updateObject(o);
|
|
5021
|
+
o.updateTransforms();
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
4940
5024
|
|
|
4941
5025
|
// remove destroyed objects
|
|
4942
5026
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|