littlejsengine 1.9.5 → 1.9.7
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 +76 -58
- package/dist/littlejs.esm.js +349 -221
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +346 -221
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +343 -217
- 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 +64 -40
- package/src/engineAudio.js +67 -46
- package/src/engineDebug.js +3 -4
- package/src/engineDraw.js +13 -10
- package/src/engineExport.js +3 -0
- package/src/engineInput.js +80 -64
- package/src/engineObject.js +35 -9
- package/src/engineParticles.js +17 -13
- package/src/engineSettings.js +19 -3
- package/src/engineTileLayer.js +15 -4
- package/src/engineUtilities.js +13 -10
- 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);
|
|
@@ -755,7 +758,7 @@ class Color
|
|
|
755
758
|
const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
|
|
756
759
|
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
757
760
|
}
|
|
758
|
-
|
|
761
|
+
|
|
759
762
|
/** Set this color from a hex code
|
|
760
763
|
* @param {String} hex - html hex code
|
|
761
764
|
* @return {Color} */
|
|
@@ -811,11 +814,11 @@ class Timer
|
|
|
811
814
|
|
|
812
815
|
/** Returns true if set and has not elapsed
|
|
813
816
|
* @return {Boolean} */
|
|
814
|
-
active() { return time
|
|
817
|
+
active() { return time < this.time; }
|
|
815
818
|
|
|
816
819
|
/** Returns true if set and elapsed
|
|
817
820
|
* @return {Boolean} */
|
|
818
|
-
elapsed() { return time
|
|
821
|
+
elapsed() { return time >= this.time; }
|
|
819
822
|
|
|
820
823
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
821
824
|
* @return {Number} */
|
|
@@ -890,6 +893,12 @@ let fontDefault = 'arial';
|
|
|
890
893
|
* @memberof Settings */
|
|
891
894
|
let showSplashScreen = false;
|
|
892
895
|
|
|
896
|
+
/** Disables all rendering, audio, and input for servers
|
|
897
|
+
* @type {Boolean}
|
|
898
|
+
* @default
|
|
899
|
+
* @memberof Settings */
|
|
900
|
+
let headlessMode = false;
|
|
901
|
+
|
|
893
902
|
///////////////////////////////////////////////////////////////////////////////
|
|
894
903
|
// WebGL settings
|
|
895
904
|
|
|
@@ -918,7 +927,7 @@ let tileSizeDefault = vec2(16);
|
|
|
918
927
|
* @type {Number}
|
|
919
928
|
* @default
|
|
920
929
|
* @memberof Settings */
|
|
921
|
-
let tileFixBleedScale = .
|
|
930
|
+
let tileFixBleedScale = .5;
|
|
922
931
|
|
|
923
932
|
///////////////////////////////////////////////////////////////////////////////
|
|
924
933
|
// Object settings
|
|
@@ -1043,7 +1052,7 @@ let soundEnable = true;
|
|
|
1043
1052
|
* @type {Number}
|
|
1044
1053
|
* @default
|
|
1045
1054
|
* @memberof Settings */
|
|
1046
|
-
let soundVolume = .
|
|
1055
|
+
let soundVolume = .3;
|
|
1047
1056
|
|
|
1048
1057
|
/** Default range where sound no longer plays
|
|
1049
1058
|
* @type {Number}
|
|
@@ -1128,6 +1137,11 @@ function setFontDefault(font) { fontDefault = font; }
|
|
|
1128
1137
|
* @memberof Settings */
|
|
1129
1138
|
function setShowSplashScreen(show) { showSplashScreen = show; }
|
|
1130
1139
|
|
|
1140
|
+
/** Set to disalbe rendering, audio, and input for servers
|
|
1141
|
+
* @param {Boolean} headless
|
|
1142
|
+
* @memberof Settings */
|
|
1143
|
+
function setHeadlessMode(headless) { headlessMode = headless; }
|
|
1144
|
+
|
|
1131
1145
|
/** Set if webgl rendering is enabled
|
|
1132
1146
|
* @param {Boolean} enable
|
|
1133
1147
|
* @memberof Settings */
|
|
@@ -1241,7 +1255,12 @@ function setSoundEnable(enable) { soundEnable = enable; }
|
|
|
1241
1255
|
/** Set volume scale to apply to all sound, music and speech
|
|
1242
1256
|
* @param {Number} volume
|
|
1243
1257
|
* @memberof Settings */
|
|
1244
|
-
function setSoundVolume(volume)
|
|
1258
|
+
function setSoundVolume(volume)
|
|
1259
|
+
{
|
|
1260
|
+
soundVolume = volume;
|
|
1261
|
+
if (soundEnable && !headlessMode && audioGainNode)
|
|
1262
|
+
audioGainNode.gain.value = volume; // update gain immediatly
|
|
1263
|
+
}
|
|
1245
1264
|
|
|
1246
1265
|
/** Set default range where sound no longer plays
|
|
1247
1266
|
* @param {Number} range
|
|
@@ -1374,6 +1393,8 @@ class EngineObject
|
|
|
1374
1393
|
this.spawnTime = time;
|
|
1375
1394
|
/** @property {Array} - List of children of this object */
|
|
1376
1395
|
this.children = [];
|
|
1396
|
+
/** @property {Boolean} - Limit object speed using linear or circular math */
|
|
1397
|
+
this.clampSpeedLinear = true;
|
|
1377
1398
|
|
|
1378
1399
|
// parent child system
|
|
1379
1400
|
/** @property {EngineObject} - Parent of object if in local space */
|
|
@@ -1397,21 +1418,46 @@ class EngineObject
|
|
|
1397
1418
|
engineObjects.push(this);
|
|
1398
1419
|
}
|
|
1399
1420
|
|
|
1400
|
-
/** Update the object transform
|
|
1401
|
-
|
|
1421
|
+
/** Update the object transform, called automatically by engine even when paused */
|
|
1422
|
+
updateTransforms()
|
|
1402
1423
|
{
|
|
1403
1424
|
const parent = this.parent;
|
|
1404
1425
|
if (parent)
|
|
1405
1426
|
{
|
|
1406
1427
|
// copy parent pos/angle
|
|
1407
|
-
|
|
1408
|
-
this.
|
|
1409
|
-
|
|
1428
|
+
const mirror = parent.getMirrorSign();
|
|
1429
|
+
this.pos = this.localPos.multiply(vec2(mirror,1)).rotate(-parent.angle).add(parent.pos);
|
|
1430
|
+
this.angle = mirror*this.localAngle + parent.angle;
|
|
1410
1431
|
}
|
|
1411
1432
|
|
|
1433
|
+
// update children
|
|
1434
|
+
for (const child of this.children)
|
|
1435
|
+
child.updateTransforms();
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
/** Update the object physics, called automatically by engine once each frame */
|
|
1439
|
+
update()
|
|
1440
|
+
{
|
|
1441
|
+
// child objects do not have physics
|
|
1442
|
+
if (this.parent)
|
|
1443
|
+
return;
|
|
1444
|
+
|
|
1412
1445
|
// limit max speed to prevent missing collisions
|
|
1413
|
-
|
|
1414
|
-
|
|
1446
|
+
if (this.clampSpeedLinear)
|
|
1447
|
+
{
|
|
1448
|
+
this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
|
|
1449
|
+
this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
|
|
1450
|
+
}
|
|
1451
|
+
else
|
|
1452
|
+
{
|
|
1453
|
+
const length2 = this.velocity.lengthSquared();
|
|
1454
|
+
if (length2 > objectMaxSpeed*objectMaxSpeed)
|
|
1455
|
+
{
|
|
1456
|
+
const s = objectMaxSpeed / length2**.5;
|
|
1457
|
+
this.velocity.x *= s;
|
|
1458
|
+
this.velocity.y *= s;
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1415
1461
|
|
|
1416
1462
|
// apply physics
|
|
1417
1463
|
const oldPos = this.pos.copy();
|
|
@@ -1423,8 +1469,7 @@ class EngineObject
|
|
|
1423
1469
|
// physics sanity checks
|
|
1424
1470
|
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
1425
1471
|
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
1426
|
-
|
|
1427
|
-
if (!enablePhysicsSolver || !this.mass) // do not update collision for fixed objects
|
|
1472
|
+
if (!enablePhysicsSolver || !this.mass) // dont do collision for fixed objects
|
|
1428
1473
|
return;
|
|
1429
1474
|
|
|
1430
1475
|
const wasMovingDown = this.velocity.y < 0;
|
|
@@ -1754,6 +1799,9 @@ let drawCount;
|
|
|
1754
1799
|
*/
|
|
1755
1800
|
function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
1756
1801
|
{
|
|
1802
|
+
if (headlessMode)
|
|
1803
|
+
return new TileInfo;
|
|
1804
|
+
|
|
1757
1805
|
// if size is a number, make it a vector
|
|
1758
1806
|
if (typeof size === 'number')
|
|
1759
1807
|
{
|
|
@@ -1787,9 +1835,9 @@ class TileInfo
|
|
|
1787
1835
|
constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
1788
1836
|
{
|
|
1789
1837
|
/** @property {Vector2} - Top left corner of tile in pixels */
|
|
1790
|
-
this.pos = pos;
|
|
1838
|
+
this.pos = pos.copy();
|
|
1791
1839
|
/** @property {Vector2} - Size of tile in pixels */
|
|
1792
|
-
this.size = size;
|
|
1840
|
+
this.size = size.copy();
|
|
1793
1841
|
/** @property {Number} - Texture index to use */
|
|
1794
1842
|
this.textureIndex = textureIndex;
|
|
1795
1843
|
}
|
|
@@ -1881,7 +1929,7 @@ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
|
1881
1929
|
* @param {Color} [additiveColor=(0,0,0,0)] - Additive color to be applied
|
|
1882
1930
|
* @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
1883
1931
|
* @param {Boolean} [screenSpace=false] - If true the pos and size are in screen space
|
|
1884
|
-
* @param {CanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
1932
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
1885
1933
|
* @memberof Draw */
|
|
1886
1934
|
function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
1887
1935
|
angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace, context)
|
|
@@ -1954,7 +2002,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
1954
2002
|
* @param {Number} [angle]
|
|
1955
2003
|
* @param {Boolean} [useWebGL=glEnable]
|
|
1956
2004
|
* @param {Boolean} [screenSpace=false]
|
|
1957
|
-
* @param {CanvasRenderingContext2D} [context]
|
|
2005
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
1958
2006
|
* @memberof Draw */
|
|
1959
2007
|
function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
1960
2008
|
{
|
|
@@ -1968,7 +2016,7 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
1968
2016
|
* @param {Color} [color=(1,1,1,1)]
|
|
1969
2017
|
* @param {Boolean} [useWebGL=glEnable]
|
|
1970
2018
|
* @param {Boolean} [screenSpace=false]
|
|
1971
|
-
* @param {CanvasRenderingContext2D} [context]
|
|
2019
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
1972
2020
|
* @memberof Draw */
|
|
1973
2021
|
function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
|
|
1974
2022
|
{
|
|
@@ -1984,7 +2032,7 @@ function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, contex
|
|
|
1984
2032
|
* @param {Boolean} mirror
|
|
1985
2033
|
* @param {Function} drawFunction
|
|
1986
2034
|
* @param {Boolean} [screenSpace=false]
|
|
1987
|
-
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
2035
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
|
|
1988
2036
|
* @memberof Draw */
|
|
1989
2037
|
function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, context=mainContext)
|
|
1990
2038
|
{
|
|
@@ -2005,7 +2053,7 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
|
|
|
2005
2053
|
/** Enable normal or additive blend mode
|
|
2006
2054
|
* @param {Boolean} [additive]
|
|
2007
2055
|
* @param {Boolean} [useWebGL=glEnable]
|
|
2008
|
-
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
2056
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
|
|
2009
2057
|
* @memberof Draw */
|
|
2010
2058
|
function setBlendMode(additive, useWebGL=glEnable, context)
|
|
2011
2059
|
{
|
|
@@ -2030,7 +2078,7 @@ function setBlendMode(additive, useWebGL=glEnable, context)
|
|
|
2030
2078
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
2031
2079
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
2032
2080
|
* @param {String} [font=fontDefault]
|
|
2033
|
-
* @param {CanvasRenderingContext2D} [context=overlayContext]
|
|
2081
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
2034
2082
|
* @memberof Draw */
|
|
2035
2083
|
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context)
|
|
2036
2084
|
{
|
|
@@ -2047,7 +2095,7 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
2047
2095
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
2048
2096
|
* @param {CanvasTextAlign} [textAlign]
|
|
2049
2097
|
* @param {String} [font=fontDefault]
|
|
2050
|
-
* @param {CanvasRenderingContext2D} [context=overlayContext]
|
|
2098
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
2051
2099
|
* @memberof Draw */
|
|
2052
2100
|
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
|
|
2053
2101
|
{
|
|
@@ -2090,7 +2138,7 @@ class FontImage
|
|
|
2090
2138
|
* @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
|
|
2091
2139
|
* @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
|
|
2092
2140
|
* @param {Vector2} [paddingSize=(0,1)] - How much extra space to add between characters
|
|
2093
|
-
* @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
2141
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
2094
2142
|
*/
|
|
2095
2143
|
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), context=overlayContext)
|
|
2096
2144
|
{
|
|
@@ -2298,7 +2346,7 @@ function gamepadWasReleased(button, gamepad=0)
|
|
|
2298
2346
|
* @return {Vector2}
|
|
2299
2347
|
* @memberof Input */
|
|
2300
2348
|
function gamepadStick(stick, gamepad=0)
|
|
2301
|
-
{ return
|
|
2349
|
+
{ return gamepadStickData[gamepad] ? gamepadStickData[gamepad][stick] || vec2() : vec2(); }
|
|
2302
2350
|
|
|
2303
2351
|
///////////////////////////////////////////////////////////////////////////////
|
|
2304
2352
|
// Input update called by engine
|
|
@@ -2309,6 +2357,8 @@ let inputData = [[]];
|
|
|
2309
2357
|
|
|
2310
2358
|
function inputUpdate()
|
|
2311
2359
|
{
|
|
2360
|
+
if (headlessMode) return;
|
|
2361
|
+
|
|
2312
2362
|
// clear input when lost focus (prevent stuck keys)
|
|
2313
2363
|
isTouchDevice || document.hasFocus() || clearInput();
|
|
2314
2364
|
|
|
@@ -2321,6 +2371,8 @@ function inputUpdate()
|
|
|
2321
2371
|
|
|
2322
2372
|
function inputUpdatePost()
|
|
2323
2373
|
{
|
|
2374
|
+
if (headlessMode) return;
|
|
2375
|
+
|
|
2324
2376
|
// clear input to prepare for next frame
|
|
2325
2377
|
for (const deviceInputData of inputData)
|
|
2326
2378
|
for (const i in deviceInputData)
|
|
@@ -2329,9 +2381,12 @@ function inputUpdatePost()
|
|
|
2329
2381
|
}
|
|
2330
2382
|
|
|
2331
2383
|
///////////////////////////////////////////////////////////////////////////////
|
|
2332
|
-
//
|
|
2384
|
+
// Input event handlers
|
|
2333
2385
|
|
|
2386
|
+
function inputInit()
|
|
2334
2387
|
{
|
|
2388
|
+
if (headlessMode) return;
|
|
2389
|
+
|
|
2335
2390
|
onkeydown = (e)=>
|
|
2336
2391
|
{
|
|
2337
2392
|
if (debug && e.target != document.body) return;
|
|
@@ -2362,21 +2417,29 @@ function inputUpdatePost()
|
|
|
2362
2417
|
c == 'KeyA' ? 'ArrowLeft' :
|
|
2363
2418
|
c == 'KeyD' ? 'ArrowRight' : c : c;
|
|
2364
2419
|
}
|
|
2420
|
+
|
|
2421
|
+
// mouse event handlers
|
|
2422
|
+
onmousedown = (e)=>
|
|
2423
|
+
{
|
|
2424
|
+
isUsingGamepad = false;
|
|
2425
|
+
inputData[0][e.button] = 3;
|
|
2426
|
+
mousePosScreen = mouseToScreen(e);
|
|
2427
|
+
e.button && e.preventDefault();
|
|
2428
|
+
}
|
|
2429
|
+
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
2430
|
+
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
2431
|
+
onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
|
|
2432
|
+
oncontextmenu = (e)=> false; // prevent right click menu
|
|
2433
|
+
|
|
2434
|
+
// init touch input
|
|
2435
|
+
if (isTouchDevice)
|
|
2436
|
+
touchInputInit();
|
|
2365
2437
|
}
|
|
2366
2438
|
|
|
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
2439
|
// convert a mouse or touch event position to screen space
|
|
2377
2440
|
function mouseToScreen(mousePos)
|
|
2378
2441
|
{
|
|
2379
|
-
if (!mainCanvas)
|
|
2442
|
+
if (!mainCanvas || headlessMode)
|
|
2380
2443
|
return vec2(); // fix bug that can occur if user clicks before page loads
|
|
2381
2444
|
|
|
2382
2445
|
const rect = mainCanvas.getBoundingClientRect();
|
|
@@ -2388,7 +2451,7 @@ function mouseToScreen(mousePos)
|
|
|
2388
2451
|
// Gamepad input
|
|
2389
2452
|
|
|
2390
2453
|
// gamepad internal variables
|
|
2391
|
-
const
|
|
2454
|
+
const gamepadStickData = [];
|
|
2392
2455
|
|
|
2393
2456
|
// gamepads are updated by engine every frame automatically
|
|
2394
2457
|
function gamepadsUpdate()
|
|
@@ -2405,14 +2468,11 @@ function gamepadsUpdate()
|
|
|
2405
2468
|
// update touch gamepad if enabled
|
|
2406
2469
|
if (touchGamepadEnable && isTouchDevice)
|
|
2407
2470
|
{
|
|
2408
|
-
|
|
2409
|
-
if (!touchGamepadButtons)
|
|
2410
|
-
createTouchGamepad();
|
|
2411
|
-
|
|
2471
|
+
ASSERT(touchGamepadButtons, 'set touchGamepadEnable before calling init!');
|
|
2412
2472
|
if (touchGamepadTimer.isSet())
|
|
2413
2473
|
{
|
|
2414
2474
|
// read virtual analog stick
|
|
2415
|
-
const sticks =
|
|
2475
|
+
const sticks = gamepadStickData[0] || (gamepadStickData[0] = []);
|
|
2416
2476
|
sticks[0] = vec2();
|
|
2417
2477
|
if (touchGamepadAnalog)
|
|
2418
2478
|
sticks[0] = applyDeadZones(touchGamepadStick);
|
|
@@ -2429,7 +2489,8 @@ function gamepadsUpdate()
|
|
|
2429
2489
|
for (let i=10; i--;)
|
|
2430
2490
|
{
|
|
2431
2491
|
const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
|
|
2432
|
-
|
|
2492
|
+
const wasDown = gamepadIsDown(j,0);
|
|
2493
|
+
data[j] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
2433
2494
|
}
|
|
2434
2495
|
}
|
|
2435
2496
|
}
|
|
@@ -2449,7 +2510,7 @@ function gamepadsUpdate()
|
|
|
2449
2510
|
// get or create gamepad data
|
|
2450
2511
|
const gamepad = gamepads[i];
|
|
2451
2512
|
const data = inputData[i+1] || (inputData[i+1] = []);
|
|
2452
|
-
const sticks =
|
|
2513
|
+
const sticks = gamepadStickData[i] || (gamepadStickData[i] = []);
|
|
2453
2514
|
|
|
2454
2515
|
if (gamepad)
|
|
2455
2516
|
{
|
|
@@ -2488,28 +2549,44 @@ function gamepadsUpdate()
|
|
|
2488
2549
|
* @param {Number|Array} [pattern] - single value in ms or vibration interval array
|
|
2489
2550
|
* @memberof Input */
|
|
2490
2551
|
function vibrate(pattern=100)
|
|
2491
|
-
{ vibrateEnable && navigator && navigator.vibrate && navigator.vibrate(pattern); }
|
|
2552
|
+
{ vibrateEnable && !headlessMode && navigator && navigator.vibrate && navigator.vibrate(pattern); }
|
|
2492
2553
|
|
|
2493
2554
|
/** Cancel any ongoing vibration
|
|
2494
2555
|
* @memberof Input */
|
|
2495
2556
|
function vibrateStop() { vibrate(0); }
|
|
2496
2557
|
|
|
2497
2558
|
///////////////////////////////////////////////////////////////////////////////
|
|
2498
|
-
// Touch input
|
|
2559
|
+
// Touch input & virtual on screen gamepad
|
|
2499
2560
|
|
|
2500
2561
|
/** True if a touch device has been detected
|
|
2501
2562
|
* @memberof Input */
|
|
2502
|
-
const isTouchDevice = window.ontouchstart !== undefined;
|
|
2563
|
+
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
2564
|
+
|
|
2565
|
+
// touch gamepad internal variables
|
|
2566
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
2503
2567
|
|
|
2504
2568
|
// try to enable touch mouse
|
|
2505
|
-
|
|
2569
|
+
function touchInputInit()
|
|
2506
2570
|
{
|
|
2571
|
+
// add non passive touch event listeners
|
|
2572
|
+
let handleTouch = handleTouchDefault;
|
|
2573
|
+
if (touchGamepadEnable)
|
|
2574
|
+
{
|
|
2575
|
+
// touch input internal variables
|
|
2576
|
+
handleTouch = handleTouchGamepad;
|
|
2577
|
+
touchGamepadButtons = [];
|
|
2578
|
+
touchGamepadStick = vec2();
|
|
2579
|
+
}
|
|
2580
|
+
document.addEventListener('touchstart', (e) => handleTouch(e), { passive: false });
|
|
2581
|
+
document.addEventListener('touchmove', (e) => handleTouch(e), { passive: false });
|
|
2582
|
+
document.addEventListener('touchend', (e) => handleTouch(e), { passive: false });
|
|
2583
|
+
|
|
2507
2584
|
// override mouse events
|
|
2508
|
-
let wasTouching;
|
|
2509
2585
|
onmousedown = onmouseup = ()=> 0;
|
|
2510
2586
|
|
|
2511
2587
|
// handle all touch events the same way
|
|
2512
|
-
|
|
2588
|
+
let wasTouching;
|
|
2589
|
+
function handleTouchDefault(e)
|
|
2513
2590
|
{
|
|
2514
2591
|
// fix stalled audio requiring user interaction
|
|
2515
2592
|
if (soundEnable && audioContext && audioContext.state != 'running')
|
|
@@ -2538,27 +2615,14 @@ if (isTouchDevice)
|
|
|
2538
2615
|
// must return true so the document will get focus
|
|
2539
2616
|
return true;
|
|
2540
2617
|
}
|
|
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
2618
|
|
|
2556
|
-
|
|
2557
|
-
|
|
2619
|
+
// special handling for virtual gamepad mode
|
|
2620
|
+
function handleTouchGamepad(e)
|
|
2558
2621
|
{
|
|
2559
2622
|
// clear touch gamepad input
|
|
2560
2623
|
touchGamepadStick = vec2();
|
|
2561
2624
|
touchGamepadButtons = [];
|
|
2625
|
+
isUsingGamepad = true;
|
|
2562
2626
|
|
|
2563
2627
|
const touching = e.touches.length;
|
|
2564
2628
|
if (touching)
|
|
@@ -2599,9 +2663,8 @@ function createTouchGamepad()
|
|
|
2599
2663
|
}
|
|
2600
2664
|
}
|
|
2601
2665
|
|
|
2602
|
-
// call default touch handler
|
|
2603
|
-
|
|
2604
|
-
isUsingGamepad = true;
|
|
2666
|
+
// call default touch handler so normal touch events still work
|
|
2667
|
+
handleTouchDefault(e);
|
|
2605
2668
|
|
|
2606
2669
|
// must return true so the document will get focus
|
|
2607
2670
|
return true;
|
|
@@ -2620,32 +2683,33 @@ function touchGamepadRender()
|
|
|
2620
2683
|
return;
|
|
2621
2684
|
|
|
2622
2685
|
// setup the canvas
|
|
2623
|
-
overlayContext
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2686
|
+
const context = overlayContext;
|
|
2687
|
+
context.save();
|
|
2688
|
+
context.globalAlpha = alpha*touchGamepadAlpha;
|
|
2689
|
+
context.strokeStyle = '#fff';
|
|
2690
|
+
context.lineWidth = 3;
|
|
2627
2691
|
|
|
2628
2692
|
// draw left analog stick
|
|
2629
|
-
|
|
2630
|
-
|
|
2693
|
+
context.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
|
|
2694
|
+
context.beginPath();
|
|
2631
2695
|
|
|
2632
2696
|
const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2633
2697
|
if (touchGamepadAnalog) // draw circle shaped gamepad
|
|
2634
2698
|
{
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2699
|
+
context.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
|
|
2700
|
+
context.fill();
|
|
2701
|
+
context.stroke();
|
|
2638
2702
|
}
|
|
2639
2703
|
else // draw cross shaped gamepad
|
|
2640
2704
|
{
|
|
2641
2705
|
for(let i=10; i--;)
|
|
2642
2706
|
{
|
|
2643
2707
|
const angle = i*PI/4;
|
|
2644
|
-
|
|
2645
|
-
i%2 &&
|
|
2646
|
-
i==1 &&
|
|
2708
|
+
context.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
|
|
2709
|
+
i%2 && context.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
|
|
2710
|
+
i==1 && context.fill();
|
|
2647
2711
|
}
|
|
2648
|
-
|
|
2712
|
+
context.stroke();
|
|
2649
2713
|
}
|
|
2650
2714
|
|
|
2651
2715
|
// draw right face buttons
|
|
@@ -2653,15 +2717,15 @@ function touchGamepadRender()
|
|
|
2653
2717
|
for (let i=4; i--;)
|
|
2654
2718
|
{
|
|
2655
2719
|
const pos = rightCenter.add(vec2().setDirection(i, touchGamepadSize/2));
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2720
|
+
context.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
|
|
2721
|
+
context.beginPath();
|
|
2722
|
+
context.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
|
|
2723
|
+
context.fill();
|
|
2724
|
+
context.stroke();
|
|
2661
2725
|
}
|
|
2662
2726
|
|
|
2663
2727
|
// set canvas back to normal
|
|
2664
|
-
|
|
2728
|
+
context.restore();
|
|
2665
2729
|
}
|
|
2666
2730
|
/**
|
|
2667
2731
|
* LittleJS Audio System
|
|
@@ -2676,6 +2740,32 @@ function touchGamepadRender()
|
|
|
2676
2740
|
|
|
2677
2741
|
|
|
2678
2742
|
|
|
2743
|
+
/** Audio context used by the engine
|
|
2744
|
+
* @type {AudioContext}
|
|
2745
|
+
* @memberof Audio */
|
|
2746
|
+
let audioContext;
|
|
2747
|
+
|
|
2748
|
+
/** Master gain node for all audio to pass through
|
|
2749
|
+
* @type {GainNode}
|
|
2750
|
+
* @memberof Audio */
|
|
2751
|
+
let audioGainNode;
|
|
2752
|
+
|
|
2753
|
+
function audioInit()
|
|
2754
|
+
{
|
|
2755
|
+
if (!soundEnable || headlessMode) return;
|
|
2756
|
+
|
|
2757
|
+
// create audio context
|
|
2758
|
+
audioContext = new AudioContext;
|
|
2759
|
+
|
|
2760
|
+
// create and connect gain node
|
|
2761
|
+
// (createGain is more widely spported then GainNode construtor)
|
|
2762
|
+
audioGainNode = audioContext.createGain();
|
|
2763
|
+
audioGainNode.connect(audioContext.destination);
|
|
2764
|
+
setSoundVolume(soundVolume); // update gain volume
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2768
|
+
|
|
2679
2769
|
/**
|
|
2680
2770
|
* Sound Object - Stores a sound for later use and can be played positionally
|
|
2681
2771
|
*
|
|
@@ -2696,7 +2786,7 @@ class Sound
|
|
|
2696
2786
|
*/
|
|
2697
2787
|
constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
|
|
2698
2788
|
{
|
|
2699
|
-
if (!soundEnable) return;
|
|
2789
|
+
if (!soundEnable || headlessMode) return;
|
|
2700
2790
|
|
|
2701
2791
|
/** @property {Number} - World space max range of sound, will not play if camera is farther away */
|
|
2702
2792
|
this.range = range;
|
|
@@ -2710,8 +2800,8 @@ class Sound
|
|
|
2710
2800
|
if (zzfxSound)
|
|
2711
2801
|
{
|
|
2712
2802
|
// generate zzfx sound now for fast playback
|
|
2713
|
-
|
|
2714
|
-
zzfxSound[1]
|
|
2803
|
+
const defaultRandomness = .05;
|
|
2804
|
+
this.randomness = zzfxSound[1] || defaultRandomness;
|
|
2715
2805
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
2716
2806
|
this.sampleRate = zzfxR;
|
|
2717
2807
|
}
|
|
@@ -2727,7 +2817,8 @@ class Sound
|
|
|
2727
2817
|
*/
|
|
2728
2818
|
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false)
|
|
2729
2819
|
{
|
|
2730
|
-
if (!soundEnable ||
|
|
2820
|
+
if (!soundEnable || headlessMode) return;
|
|
2821
|
+
if (!this.sampleChannels) return;
|
|
2731
2822
|
|
|
2732
2823
|
let pan;
|
|
2733
2824
|
if (pos)
|
|
@@ -2804,14 +2895,14 @@ class SoundWave extends Sound
|
|
|
2804
2895
|
* @param {Number} [randomness] - How much to randomize frequency each time sound plays
|
|
2805
2896
|
* @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
|
|
2806
2897
|
* @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
|
|
2898
|
+
* @param {Function} [onloadCallback] - callback function to call when sound is loaded
|
|
2807
2899
|
*/
|
|
2808
|
-
constructor(filename, randomness=0, range, taper)
|
|
2900
|
+
constructor(filename, randomness=0, range, taper, onloadCallback)
|
|
2809
2901
|
{
|
|
2810
2902
|
super(undefined, range, taper);
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
if (!soundEnable) return;
|
|
2903
|
+
if (!soundEnable || headlessMode) return;
|
|
2814
2904
|
|
|
2905
|
+
this.randomness = randomness;
|
|
2815
2906
|
fetch(filename)
|
|
2816
2907
|
.then(response => response.arrayBuffer())
|
|
2817
2908
|
.then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))
|
|
@@ -2821,10 +2912,23 @@ class SoundWave extends Sound
|
|
|
2821
2912
|
for (let i = audioBuffer.numberOfChannels; i--;)
|
|
2822
2913
|
this.sampleChannels[i] = Array.from(audioBuffer.getChannelData(i));
|
|
2823
2914
|
this.sampleRate = audioBuffer.sampleRate;
|
|
2824
|
-
});
|
|
2915
|
+
}).then(() => onloadCallback && onloadCallback(this));
|
|
2825
2916
|
}
|
|
2826
2917
|
}
|
|
2827
2918
|
|
|
2919
|
+
/** Play an mp3, ogg, or wav audio from a local file or url
|
|
2920
|
+
* @param {String} filename - Location of sound file to play
|
|
2921
|
+
* @param {Number} [volume] - How much to scale volume by
|
|
2922
|
+
* @param {Boolean} [loop] - True if the music should loop
|
|
2923
|
+
* @return {SoundWave} - The sound object for this file
|
|
2924
|
+
* @memberof Audio */
|
|
2925
|
+
function playAudioFile(filename, volume=1, loop=false)
|
|
2926
|
+
{
|
|
2927
|
+
if (!soundEnable || headlessMode) return;
|
|
2928
|
+
|
|
2929
|
+
return new SoundWave(filename,0,0,0, s=>s.play(undefined, volume, 1, 1, loop));
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2828
2932
|
/**
|
|
2829
2933
|
* Music Object - Stores a zzfx music track for later use
|
|
2830
2934
|
*
|
|
@@ -2864,7 +2968,7 @@ class Music extends Sound
|
|
|
2864
2968
|
{
|
|
2865
2969
|
super(undefined);
|
|
2866
2970
|
|
|
2867
|
-
if (!soundEnable) return;
|
|
2971
|
+
if (!soundEnable || headlessMode) return;
|
|
2868
2972
|
this.randomness = 0;
|
|
2869
2973
|
this.sampleChannels = zzfxM(...zzfxMusic);
|
|
2870
2974
|
this.sampleRate = zzfxR;
|
|
@@ -2879,23 +2983,6 @@ class Music extends Sound
|
|
|
2879
2983
|
{ return super.play(undefined, volume, 1, 1, loop); }
|
|
2880
2984
|
}
|
|
2881
2985
|
|
|
2882
|
-
/** Play an mp3, ogg, or wav audio from a local file or url
|
|
2883
|
-
* @param {String} filename - Location of sound file to play
|
|
2884
|
-
* @param {Number} [volume] - How much to scale volume by
|
|
2885
|
-
* @param {Boolean} [loop] - True if the music should loop
|
|
2886
|
-
* @return {HTMLAudioElement} - The audio element for this sound
|
|
2887
|
-
* @memberof Audio */
|
|
2888
|
-
function playAudioFile(filename, volume=1, loop=false)
|
|
2889
|
-
{
|
|
2890
|
-
if (!soundEnable) return;
|
|
2891
|
-
|
|
2892
|
-
const audio = new Audio(filename);
|
|
2893
|
-
audio.volume = soundVolume * volume;
|
|
2894
|
-
audio.loop = loop;
|
|
2895
|
-
audio.play();
|
|
2896
|
-
return audio;
|
|
2897
|
-
}
|
|
2898
|
-
|
|
2899
2986
|
/** Speak text with passed in settings
|
|
2900
2987
|
* @param {String} text - The text to speak
|
|
2901
2988
|
* @param {String} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
|
|
@@ -2906,7 +2993,8 @@ function playAudioFile(filename, volume=1, loop=false)
|
|
|
2906
2993
|
* @memberof Audio */
|
|
2907
2994
|
function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
2908
2995
|
{
|
|
2909
|
-
if (!soundEnable ||
|
|
2996
|
+
if (!soundEnable || headlessMode) return;
|
|
2997
|
+
if (!speechSynthesis) return;
|
|
2910
2998
|
|
|
2911
2999
|
// common languages (not supported by all browsers)
|
|
2912
3000
|
// en - english, it - italian, fr - french, de - german, es - spanish
|
|
@@ -2936,14 +3024,8 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
2936
3024
|
|
|
2937
3025
|
///////////////////////////////////////////////////////////////////////////////
|
|
2938
3026
|
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
* @memberof Audio */
|
|
2942
|
-
let audioContext = new AudioContext;
|
|
2943
|
-
|
|
2944
|
-
/** Keep track if audio was suspended when last sound was played
|
|
2945
|
-
* @type {Boolean}
|
|
2946
|
-
* @memberof Audio */
|
|
3027
|
+
// internal tracking if audio was suspended when last sound was played
|
|
3028
|
+
// allows first suspended sound to play when audio is resumed
|
|
2947
3029
|
let audioSuspended = false;
|
|
2948
3030
|
|
|
2949
3031
|
/** Play cached audio samples with given settings
|
|
@@ -2957,7 +3039,7 @@ let audioSuspended = false;
|
|
|
2957
3039
|
* @memberof Audio */
|
|
2958
3040
|
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR)
|
|
2959
3041
|
{
|
|
2960
|
-
if (!soundEnable) return;
|
|
3042
|
+
if (!soundEnable || headlessMode) return;
|
|
2961
3043
|
|
|
2962
3044
|
// prevent sounds from building up if they can't be played
|
|
2963
3045
|
const audioWasSuspended = audioSuspended;
|
|
@@ -2981,10 +3063,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
2981
3063
|
source.playbackRate.value = rate;
|
|
2982
3064
|
source.loop = loop;
|
|
2983
3065
|
|
|
2984
|
-
//
|
|
3066
|
+
// set master gain volume
|
|
3067
|
+
setSoundVolume(soundVolume);
|
|
3068
|
+
|
|
3069
|
+
// create and connect gain node
|
|
2985
3070
|
const gainNode = audioContext.createGain();
|
|
2986
|
-
gainNode.gain.value =
|
|
2987
|
-
gainNode.connect(
|
|
3071
|
+
gainNode.gain.value = volume;
|
|
3072
|
+
gainNode.connect(audioGainNode);
|
|
2988
3073
|
|
|
2989
3074
|
// connect source to stereo panner and gain
|
|
2990
3075
|
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)})).connect(gainNode);
|
|
@@ -3003,7 +3088,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3003
3088
|
* @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
|
|
3004
3089
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
3005
3090
|
* @memberof Audio */
|
|
3006
|
-
function zzfx(...zzfxSound) { return
|
|
3091
|
+
function zzfx(...zzfxSound) { return new Sound(zzfxSound).play(); }
|
|
3007
3092
|
|
|
3008
3093
|
/** Sample rate used for all ZzFX sounds
|
|
3009
3094
|
* @default 44100
|
|
@@ -3012,7 +3097,7 @@ const zzfxR = 44100;
|
|
|
3012
3097
|
|
|
3013
3098
|
/** Generate samples for a ZzFX sound
|
|
3014
3099
|
* @param {Number} [volume] - Volume scale (percent)
|
|
3015
|
-
* @param {Number} [randomness] -
|
|
3100
|
+
* @param {Number} [randomness] - Unused in this fuction, handled by Sound class
|
|
3016
3101
|
* @param {Number} [frequency] - Frequency of sound (Hz)
|
|
3017
3102
|
* @param {Number} [attack] - Attack time, how fast sound starts (seconds)
|
|
3018
3103
|
* @param {Number} [sustain] - Sustain time, how long sound holds (seconds)
|
|
@@ -3038,17 +3123,18 @@ const zzfxR = 44100;
|
|
|
3038
3123
|
function zzfxG
|
|
3039
3124
|
(
|
|
3040
3125
|
// parameters
|
|
3041
|
-
volume = 1, randomness =
|
|
3126
|
+
volume = 1, randomness = 0, frequency = 220, attack = 0, sustain = 0,
|
|
3042
3127
|
release = .1, shape = 0, shapeCurve = 1, slide = 0, deltaSlide = 0,
|
|
3043
3128
|
pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, modulation = 0,
|
|
3044
3129
|
bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0, filter = 0
|
|
3045
3130
|
)
|
|
3046
3131
|
{
|
|
3132
|
+
// LJS Note: ZZFX modded so randomness is handled by Sound class
|
|
3133
|
+
|
|
3047
3134
|
// init parameters
|
|
3048
3135
|
let PI2 = PI*2, sampleRate = zzfxR,
|
|
3049
3136
|
startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
|
|
3050
|
-
startFrequency = frequency *=
|
|
3051
|
-
rand(1 + randomness, 1-randomness) * PI2 / sampleRate,
|
|
3137
|
+
startFrequency = frequency *= PI2 / sampleRate,
|
|
3052
3138
|
b = [], t = 0, tm = 0, i = 0, j = 1, r = 0, c = 0, s = 0, f, length,
|
|
3053
3139
|
|
|
3054
3140
|
// biquad LP/HP filter
|
|
@@ -3070,7 +3156,6 @@ function zzfxG
|
|
|
3070
3156
|
pitchJump *= PI2 / sampleRate;
|
|
3071
3157
|
pitchJumpTime *= sampleRate;
|
|
3072
3158
|
repeatTime = repeatTime * sampleRate | 0;
|
|
3073
|
-
volume *= soundVolume;
|
|
3074
3159
|
|
|
3075
3160
|
// generate waveform
|
|
3076
3161
|
for(length = attack + decay + sustain + release + delay | 0;
|
|
@@ -3416,7 +3501,7 @@ class TileLayer extends EngineObject
|
|
|
3416
3501
|
|
|
3417
3502
|
/** @property {HTMLCanvasElement} - The canvas used by this tile layer */
|
|
3418
3503
|
this.canvas = document.createElement('canvas');
|
|
3419
|
-
/** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
3504
|
+
/** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
3420
3505
|
this.context = this.canvas.getContext('2d');
|
|
3421
3506
|
/** @property {Vector2} - How much to scale this layer when rendered */
|
|
3422
3507
|
this.scale = scale;
|
|
@@ -3427,6 +3512,17 @@ class TileLayer extends EngineObject
|
|
|
3427
3512
|
this.data = [];
|
|
3428
3513
|
for (let j = this.size.area(); j--;)
|
|
3429
3514
|
this.data.push(new TileLayerData);
|
|
3515
|
+
|
|
3516
|
+
if (headlessMode)
|
|
3517
|
+
{
|
|
3518
|
+
// disable rendering
|
|
3519
|
+
this.redraw = () => {};
|
|
3520
|
+
this.render = () => {};
|
|
3521
|
+
this.redrawStart = () => {};
|
|
3522
|
+
this.redrawEnd = () => {};
|
|
3523
|
+
this.drawTileData = () => {};
|
|
3524
|
+
this.drawCanvas2D = () => {};
|
|
3525
|
+
}
|
|
3430
3526
|
}
|
|
3431
3527
|
|
|
3432
3528
|
/** Set data at a given position in the array
|
|
@@ -3457,7 +3553,7 @@ class TileLayer extends EngineObject
|
|
|
3457
3553
|
ASSERT(mainContext != this.context, 'must call redrawEnd() after drawing tiles');
|
|
3458
3554
|
|
|
3459
3555
|
// flush and copy gl canvas because tile canvas does not use webgl
|
|
3460
|
-
|
|
3556
|
+
!glOverlay && !this.isOverlay && glCopyToContext(mainContext);
|
|
3461
3557
|
|
|
3462
3558
|
// draw the entire cached level onto the canvas
|
|
3463
3559
|
const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
|
|
@@ -3507,14 +3603,14 @@ class TileLayer extends EngineObject
|
|
|
3507
3603
|
this.context.imageSmoothingEnabled = !canvasPixelated;
|
|
3508
3604
|
|
|
3509
3605
|
// setup gl rendering if enabled
|
|
3510
|
-
|
|
3606
|
+
glPreRender();
|
|
3511
3607
|
}
|
|
3512
3608
|
|
|
3513
3609
|
/** Call to end the redraw process */
|
|
3514
3610
|
redrawEnd()
|
|
3515
3611
|
{
|
|
3516
3612
|
ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
|
|
3517
|
-
|
|
3613
|
+
glCopyToContext(mainContext, true);
|
|
3518
3614
|
//debugSaveCanvas(this.canvas);
|
|
3519
3615
|
|
|
3520
3616
|
// set stuff back to normal
|
|
@@ -3839,20 +3935,21 @@ class ParticleEmitter extends EngineObject
|
|
|
3839
3935
|
class Particle extends EngineObject
|
|
3840
3936
|
{
|
|
3841
3937
|
/**
|
|
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 {
|
|
3938
|
+
* Create a particle with the passed in settings
|
|
3939
|
+
* Typically this is created automatically by a ParticleEmitter
|
|
3940
|
+
* @param {Vector2} position - World space position of the particle
|
|
3941
|
+
* @param {TileInfo} tileInfo - Tile info to render particles
|
|
3942
|
+
* @param {Number} angle - Angle to rotate the particle
|
|
3943
|
+
* @param {Color} colorStart - Color at start of life
|
|
3944
|
+
* @param {Color} colorEnd - Color at end of life
|
|
3945
|
+
* @param {Number} lifeTime - How long to live for
|
|
3946
|
+
* @param {Number} sizeStart - Size at start of life
|
|
3947
|
+
* @param {Number} sizeEnd - Size at end of life
|
|
3948
|
+
* @param {Number} fadeRate - How quick to fade in/out
|
|
3949
|
+
* @param {Boolean} additive - Does it use additive blend mode
|
|
3950
|
+
* @param {Number} trailScale - If a trail, how long to make it
|
|
3854
3951
|
* @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
|
|
3855
|
-
* @param {Function}
|
|
3952
|
+
* @param {Function} [destroyCallback] - Callback when particle dies
|
|
3856
3953
|
*/
|
|
3857
3954
|
constructor(position, tileInfo, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, fadeRate, additive, trailScale, localSpaceEmitter, destroyCallback
|
|
3858
3955
|
)
|
|
@@ -3879,6 +3976,9 @@ class Particle extends EngineObject
|
|
|
3879
3976
|
this.localSpaceEmitter = localSpaceEmitter;
|
|
3880
3977
|
/** @property {Function} - Called when particle dies */
|
|
3881
3978
|
this.destroyCallback = destroyCallback;
|
|
3979
|
+
|
|
3980
|
+
// particles use circular clamped speed
|
|
3981
|
+
this.clampSpeedLinear = false;
|
|
3882
3982
|
}
|
|
3883
3983
|
|
|
3884
3984
|
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
@@ -4261,6 +4361,8 @@ let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData,
|
|
|
4261
4361
|
// Initalize WebGL, called automatically by the engine
|
|
4262
4362
|
function glInit()
|
|
4263
4363
|
{
|
|
4364
|
+
if (!glEnable || headlessMode) return;
|
|
4365
|
+
|
|
4264
4366
|
// create the canvas and textures
|
|
4265
4367
|
glCanvas = document.createElement('canvas');
|
|
4266
4368
|
glContext = glCanvas.getContext('webgl2');
|
|
@@ -4273,11 +4375,11 @@ function glInit()
|
|
|
4273
4375
|
'#version 300 es\n' + // specify GLSL ES version
|
|
4274
4376
|
'precision highp float;'+ // use highp for better accuracy
|
|
4275
4377
|
'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;'+ //
|
|
4378
|
+
'in vec2 g;'+ // in: geometry
|
|
4379
|
+
'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
|
|
4380
|
+
'in float r;'+ // in: rotation
|
|
4381
|
+
'out vec2 v;'+ // out: uv
|
|
4382
|
+
'out vec4 d,e;'+ // out: color, additiveColor
|
|
4281
4383
|
'void main(){'+ // shader entry point
|
|
4282
4384
|
'vec2 s=(g-.5)*p.zw;'+ // get size offset
|
|
4283
4385
|
'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
|
|
@@ -4287,10 +4389,10 @@ function glInit()
|
|
|
4287
4389
|
,
|
|
4288
4390
|
'#version 300 es\n' + // specify GLSL ES version
|
|
4289
4391
|
'precision highp float;'+ // use highp for better accuracy
|
|
4290
|
-
'in vec2 v;'+ // uv
|
|
4291
|
-
'in vec4 d,e;'+ // color, additiveColor
|
|
4292
4392
|
'uniform sampler2D s;'+ // texture
|
|
4293
|
-
'
|
|
4393
|
+
'in vec2 v;'+ // in: uv
|
|
4394
|
+
'in vec4 d,e;'+ // in: color, additiveColor
|
|
4395
|
+
'out vec4 c;'+ // out: color
|
|
4294
4396
|
'void main(){'+ // shader entry point
|
|
4295
4397
|
'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
|
|
4296
4398
|
'}' // end of shader
|
|
@@ -4312,9 +4414,11 @@ function glInit()
|
|
|
4312
4414
|
// Setup render each frame, called automatically by engine
|
|
4313
4415
|
function glPreRender()
|
|
4314
4416
|
{
|
|
4417
|
+
if (!glEnable || headlessMode) return;
|
|
4418
|
+
|
|
4315
4419
|
// clear and set to same size as main canvas
|
|
4316
4420
|
glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
|
|
4317
|
-
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
4421
|
+
//glContext.clear(gl_COLOR_BUFFER_BIT); // auto cleared when size is set
|
|
4318
4422
|
|
|
4319
4423
|
// set up the shader
|
|
4320
4424
|
glContext.useProgram(glShader);
|
|
@@ -4348,12 +4452,12 @@ function glPreRender()
|
|
|
4348
4452
|
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
4349
4453
|
const p = vec2(-1).subtract(cameraPos.multiply(s));
|
|
4350
4454
|
glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), false,
|
|
4351
|
-
|
|
4455
|
+
[
|
|
4352
4456
|
s.x, 0, 0, 0,
|
|
4353
4457
|
0, s.y, 0, 0,
|
|
4354
4458
|
1, 1, 1, 1,
|
|
4355
4459
|
p.x, p.y, 0, 0
|
|
4356
|
-
]
|
|
4460
|
+
]
|
|
4357
4461
|
);
|
|
4358
4462
|
}
|
|
4359
4463
|
|
|
@@ -4364,7 +4468,7 @@ function glPreRender()
|
|
|
4364
4468
|
function glSetTexture(texture)
|
|
4365
4469
|
{
|
|
4366
4470
|
// must flush cache with the old texture to set a new one
|
|
4367
|
-
if (texture == glActiveTexture)
|
|
4471
|
+
if (headlessMode || texture == glActiveTexture)
|
|
4368
4472
|
return;
|
|
4369
4473
|
|
|
4370
4474
|
glFlush();
|
|
@@ -4424,7 +4528,6 @@ function glCreateTexture(image)
|
|
|
4424
4528
|
const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
|
|
4425
4529
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
|
|
4426
4530
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
|
|
4427
|
-
|
|
4428
4531
|
return texture;
|
|
4429
4532
|
}
|
|
4430
4533
|
|
|
@@ -4448,12 +4551,12 @@ function glFlush()
|
|
|
4448
4551
|
}
|
|
4449
4552
|
|
|
4450
4553
|
/** Draw any sprites still in the buffer, copy to main canvas and clear
|
|
4451
|
-
* @param {CanvasRenderingContext2D} context
|
|
4554
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
|
4452
4555
|
* @param {Boolean} [forceDraw]
|
|
4453
4556
|
* @memberof WebGL */
|
|
4454
4557
|
function glCopyToContext(context, forceDraw=false)
|
|
4455
4558
|
{
|
|
4456
|
-
if (!glInstanceCount && !forceDraw) return;
|
|
4559
|
+
if (!glEnable || !glInstanceCount && !forceDraw) return;
|
|
4457
4560
|
|
|
4458
4561
|
glFlush();
|
|
4459
4562
|
|
|
@@ -4510,7 +4613,7 @@ let glPostShader, glPostTexture, glPostIncludeOverlay;
|
|
|
4510
4613
|
function glInitPostProcess(shaderCode, includeOverlay=false)
|
|
4511
4614
|
{
|
|
4512
4615
|
ASSERT(!glPostShader, 'can only have 1 post effects shader');
|
|
4513
|
-
|
|
4616
|
+
if (headlessMode) return;
|
|
4514
4617
|
if (!shaderCode) // default shader pass through
|
|
4515
4618
|
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
|
|
4516
4619
|
|
|
@@ -4549,8 +4652,7 @@ function glInitPostProcess(shaderCode, includeOverlay=false)
|
|
|
4549
4652
|
// Render the post processing shader, called automatically by the engine
|
|
4550
4653
|
function glRenderPostProcess()
|
|
4551
4654
|
{
|
|
4552
|
-
if (!glPostShader)
|
|
4553
|
-
return;
|
|
4655
|
+
if (!glPostShader || headlessMode) return;
|
|
4554
4656
|
|
|
4555
4657
|
// prepare to render post process shader
|
|
4556
4658
|
if (glEnable)
|
|
@@ -4656,7 +4758,7 @@ const engineName = 'LittleJS';
|
|
|
4656
4758
|
* @type {String}
|
|
4657
4759
|
* @default
|
|
4658
4760
|
* @memberof Engine */
|
|
4659
|
-
const engineVersion = '1.9.
|
|
4761
|
+
const engineVersion = '1.9.7';
|
|
4660
4762
|
|
|
4661
4763
|
/** Frames per second to update
|
|
4662
4764
|
* @type {Number}
|
|
@@ -4733,7 +4835,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4733
4835
|
mainContext.imageSmoothingEnabled = !canvasPixelated;
|
|
4734
4836
|
|
|
4735
4837
|
// setup gl rendering if enabled
|
|
4736
|
-
|
|
4838
|
+
glPreRender();
|
|
4737
4839
|
}
|
|
4738
4840
|
|
|
4739
4841
|
// internal update loop for engine
|
|
@@ -4752,11 +4854,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4752
4854
|
frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
|
|
4753
4855
|
if (!debugSpeedUp)
|
|
4754
4856
|
frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp in case of slow framerate
|
|
4857
|
+
|
|
4755
4858
|
updateCanvas();
|
|
4756
4859
|
|
|
4757
4860
|
if (paused)
|
|
4758
4861
|
{
|
|
4759
|
-
//
|
|
4862
|
+
// update object transforms even when paused
|
|
4863
|
+
for (const o of engineObjects)
|
|
4864
|
+
o.parent || o.updateTransforms();
|
|
4760
4865
|
inputUpdate();
|
|
4761
4866
|
debugUpdate();
|
|
4762
4867
|
gameUpdatePost();
|
|
@@ -4768,7 +4873,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4768
4873
|
let deltaSmooth = 0;
|
|
4769
4874
|
if (frameTimeBufferMS < 0 && frameTimeBufferMS > -9)
|
|
4770
4875
|
{
|
|
4771
|
-
// force
|
|
4876
|
+
// force at least one update each frame since it is waiting for refresh
|
|
4772
4877
|
deltaSmooth = frameTimeBufferMS;
|
|
4773
4878
|
frameTimeBufferMS = 0;
|
|
4774
4879
|
}
|
|
@@ -4793,34 +4898,37 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4793
4898
|
// add the time smoothing back in
|
|
4794
4899
|
frameTimeBufferMS += deltaSmooth;
|
|
4795
4900
|
}
|
|
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)
|
|
4901
|
+
|
|
4902
|
+
if (!headlessMode)
|
|
4811
4903
|
{
|
|
4812
|
-
//
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4904
|
+
// render sort then render while removing destroyed objects
|
|
4905
|
+
enginePreRender();
|
|
4906
|
+
gameRender();
|
|
4907
|
+
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
4908
|
+
for (const o of engineObjects)
|
|
4909
|
+
o.destroyed || o.render();
|
|
4910
|
+
gameRenderPost();
|
|
4911
|
+
glRenderPostProcess();
|
|
4912
|
+
medalsRender();
|
|
4913
|
+
touchGamepadRender();
|
|
4914
|
+
debugRender();
|
|
4915
|
+
glCopyToContext(mainContext);
|
|
4916
|
+
|
|
4917
|
+
if (showWatermark)
|
|
4918
|
+
{
|
|
4919
|
+
// update fps
|
|
4920
|
+
overlayContext.textAlign = 'right';
|
|
4921
|
+
overlayContext.textBaseline = 'top';
|
|
4922
|
+
overlayContext.font = '1em monospace';
|
|
4923
|
+
overlayContext.fillStyle = '#000';
|
|
4924
|
+
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
4925
|
+
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
4926
|
+
+ (glEnable ? ' GL' : ' 2D') ;
|
|
4927
|
+
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
4928
|
+
overlayContext.fillStyle = '#fff';
|
|
4929
|
+
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
4930
|
+
drawCount = 0;
|
|
4931
|
+
}
|
|
4824
4932
|
}
|
|
4825
4933
|
|
|
4826
4934
|
requestAnimationFrame(engineUpdate);
|
|
@@ -4828,6 +4936,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4828
4936
|
|
|
4829
4937
|
function updateCanvas()
|
|
4830
4938
|
{
|
|
4939
|
+
if (headlessMode) return;
|
|
4940
|
+
|
|
4831
4941
|
if (canvasFixedSize.x)
|
|
4832
4942
|
{
|
|
4833
4943
|
// clear canvas and set fixed size
|
|
@@ -4855,8 +4965,20 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4855
4965
|
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
4856
4966
|
}
|
|
4857
4967
|
|
|
4968
|
+
function startEngine()
|
|
4969
|
+
{
|
|
4970
|
+
gameInit();
|
|
4971
|
+
engineUpdate();
|
|
4972
|
+
}
|
|
4973
|
+
|
|
4974
|
+
if (headlessMode)
|
|
4975
|
+
{
|
|
4976
|
+
startEngine();
|
|
4977
|
+
return;
|
|
4978
|
+
}
|
|
4979
|
+
|
|
4858
4980
|
// setup html
|
|
4859
|
-
|
|
4981
|
+
const styleBody =
|
|
4860
4982
|
'margin:0;overflow:hidden;' + // fill the window
|
|
4861
4983
|
'background:#000;' + // set background color
|
|
4862
4984
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
@@ -4868,8 +4990,10 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4868
4990
|
mainContext = mainCanvas.getContext('2d');
|
|
4869
4991
|
|
|
4870
4992
|
// init stuff and start engine
|
|
4993
|
+
inputInit();
|
|
4994
|
+
audioInit();
|
|
4871
4995
|
debugInit();
|
|
4872
|
-
|
|
4996
|
+
glInit();
|
|
4873
4997
|
|
|
4874
4998
|
// create overlay canvas for hud to appear above gl canvas
|
|
4875
4999
|
document.body.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
@@ -4910,12 +5034,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4910
5034
|
}));
|
|
4911
5035
|
|
|
4912
5036
|
// load all of the images
|
|
4913
|
-
Promise.all(promises).then(
|
|
4914
|
-
{
|
|
4915
|
-
// start the engine
|
|
4916
|
-
gameInit();
|
|
4917
|
-
engineUpdate();
|
|
4918
|
-
});
|
|
5037
|
+
Promise.all(promises).then(startEngine);
|
|
4919
5038
|
}
|
|
4920
5039
|
|
|
4921
5040
|
/** Update each engine object, remove destroyed objects, and update time
|
|
@@ -4936,7 +5055,14 @@ function engineObjectsUpdate()
|
|
|
4936
5055
|
}
|
|
4937
5056
|
}
|
|
4938
5057
|
for (const o of engineObjects)
|
|
4939
|
-
|
|
5058
|
+
{
|
|
5059
|
+
// update top level objects
|
|
5060
|
+
if (!o.parent)
|
|
5061
|
+
{
|
|
5062
|
+
updateObject(o);
|
|
5063
|
+
o.updateTransforms();
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
4940
5066
|
|
|
4941
5067
|
// remove destroyed objects
|
|
4942
5068
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|