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.esm.js
CHANGED
|
@@ -153,7 +153,7 @@ function debugClear() { debugPrimitives = []; }
|
|
|
153
153
|
* @param {String} [filename]
|
|
154
154
|
* @param {String} [type]
|
|
155
155
|
* @memberof Debug */
|
|
156
|
-
function debugSaveCanvas(canvas, filename=
|
|
156
|
+
function debugSaveCanvas(canvas, filename='screenshot', type='image/png')
|
|
157
157
|
{ debugSaveDataURL(canvas.toDataURL(type), filename); }
|
|
158
158
|
|
|
159
159
|
/** Save a text file to disk
|
|
@@ -161,7 +161,7 @@ function debugSaveCanvas(canvas, filename=engineName, type='image/png')
|
|
|
161
161
|
* @param {String} [filename]
|
|
162
162
|
* @param {String} [type]
|
|
163
163
|
* @memberof Debug */
|
|
164
|
-
function debugSaveText(text, filename=
|
|
164
|
+
function debugSaveText(text, filename='text', type='text/plain')
|
|
165
165
|
{ debugSaveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); }
|
|
166
166
|
|
|
167
167
|
/** Save a data url to disk
|
|
@@ -181,8 +181,7 @@ function debugSaveDataURL(dataURL, filename)
|
|
|
181
181
|
function debugInit()
|
|
182
182
|
{
|
|
183
183
|
// create link for saving screenshots
|
|
184
|
-
|
|
185
|
-
downloadLink.style.display = 'none';
|
|
184
|
+
downloadLink = document.createElement('a');
|
|
186
185
|
}
|
|
187
186
|
|
|
188
187
|
function debugUpdate()
|
|
@@ -485,7 +484,7 @@ function clamp(value, min=0, max=1) { return value < min ? min : value > max ? m
|
|
|
485
484
|
* @return {Number}
|
|
486
485
|
* @memberof Utilities */
|
|
487
486
|
function percent(value, valueA, valueB)
|
|
488
|
-
{ return valueB
|
|
487
|
+
{ return (valueB-=valueA) ? clamp((value-valueA)/valueB) : 0; }
|
|
489
488
|
|
|
490
489
|
/** Linearly interpolates between values passed in using percent
|
|
491
490
|
* @param {Number} percent
|
|
@@ -692,7 +691,7 @@ class RandomGenerator
|
|
|
692
691
|
this.seed ^= this.seed << 13;
|
|
693
692
|
this.seed ^= this.seed >>> 17;
|
|
694
693
|
this.seed ^= this.seed << 5;
|
|
695
|
-
return valueB + (valueA - valueB) * abs(this.seed %
|
|
694
|
+
return valueB + (valueA - valueB) * abs(this.seed % 1e8) / 1e8;
|
|
696
695
|
}
|
|
697
696
|
|
|
698
697
|
/** Returns a floored seeded random value the two values passed in
|
|
@@ -703,7 +702,7 @@ class RandomGenerator
|
|
|
703
702
|
|
|
704
703
|
/** Randomly returns either -1 or 1 deterministically
|
|
705
704
|
* @return {Number} */
|
|
706
|
-
sign() { return this.
|
|
705
|
+
sign() { return this.float() > .5 ? 1 : -1; }
|
|
707
706
|
}
|
|
708
707
|
|
|
709
708
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -751,6 +750,7 @@ class Vector2
|
|
|
751
750
|
* @param {Number} [y] - Y axis location */
|
|
752
751
|
constructor(x=0, y=0)
|
|
753
752
|
{
|
|
753
|
+
ASSERT(typeof x === 'number' && typeof y === 'number');
|
|
754
754
|
/** @property {Number} - X axis location */
|
|
755
755
|
this.x = x;
|
|
756
756
|
/** @property {Number} - Y axis location */
|
|
@@ -1077,12 +1077,14 @@ class Color
|
|
|
1077
1077
|
* @return {Color} */
|
|
1078
1078
|
setHSLA(h=0, s=0, l=1, a=1)
|
|
1079
1079
|
{
|
|
1080
|
+
h = mod(h,1);
|
|
1081
|
+
s = clamp(s);
|
|
1082
|
+
l = clamp(l);
|
|
1080
1083
|
const q = l < .5 ? l*(1+s) : l+s-l*s, p = 2*l-q,
|
|
1081
1084
|
f = (p, q, t)=>
|
|
1082
|
-
(t = (
|
|
1083
|
-
t < 1
|
|
1084
|
-
t < 2
|
|
1085
|
-
|
|
1085
|
+
(t = mod(t,1))*6 < 1 ? p+(q-p)*6*t :
|
|
1086
|
+
t*2 < 1 ? q :
|
|
1087
|
+
t*3 < 2 ? p+(q-p)*(4-t*6) : p;
|
|
1086
1088
|
this.r = f(p, q, h + 1/3);
|
|
1087
1089
|
this.g = f(p, q, h);
|
|
1088
1090
|
this.b = f(p, q, h - 1/3);
|
|
@@ -1141,7 +1143,7 @@ class Color
|
|
|
1141
1143
|
const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
|
|
1142
1144
|
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
1143
1145
|
}
|
|
1144
|
-
|
|
1146
|
+
|
|
1145
1147
|
/** Set this color from a hex code
|
|
1146
1148
|
* @param {String} hex - html hex code
|
|
1147
1149
|
* @return {Color} */
|
|
@@ -1197,11 +1199,11 @@ class Timer
|
|
|
1197
1199
|
|
|
1198
1200
|
/** Returns true if set and has not elapsed
|
|
1199
1201
|
* @return {Boolean} */
|
|
1200
|
-
active() { return time
|
|
1202
|
+
active() { return time < this.time; }
|
|
1201
1203
|
|
|
1202
1204
|
/** Returns true if set and elapsed
|
|
1203
1205
|
* @return {Boolean} */
|
|
1204
|
-
elapsed() { return time
|
|
1206
|
+
elapsed() { return time >= this.time; }
|
|
1205
1207
|
|
|
1206
1208
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
1207
1209
|
* @return {Number} */
|
|
@@ -1276,6 +1278,12 @@ let fontDefault = 'arial';
|
|
|
1276
1278
|
* @memberof Settings */
|
|
1277
1279
|
let showSplashScreen = false;
|
|
1278
1280
|
|
|
1281
|
+
/** Disables all rendering, audio, and input for servers
|
|
1282
|
+
* @type {Boolean}
|
|
1283
|
+
* @default
|
|
1284
|
+
* @memberof Settings */
|
|
1285
|
+
let headlessMode = false;
|
|
1286
|
+
|
|
1279
1287
|
///////////////////////////////////////////////////////////////////////////////
|
|
1280
1288
|
// WebGL settings
|
|
1281
1289
|
|
|
@@ -1304,7 +1312,7 @@ let tileSizeDefault = vec2(16);
|
|
|
1304
1312
|
* @type {Number}
|
|
1305
1313
|
* @default
|
|
1306
1314
|
* @memberof Settings */
|
|
1307
|
-
let tileFixBleedScale = .
|
|
1315
|
+
let tileFixBleedScale = .5;
|
|
1308
1316
|
|
|
1309
1317
|
///////////////////////////////////////////////////////////////////////////////
|
|
1310
1318
|
// Object settings
|
|
@@ -1429,7 +1437,7 @@ let soundEnable = true;
|
|
|
1429
1437
|
* @type {Number}
|
|
1430
1438
|
* @default
|
|
1431
1439
|
* @memberof Settings */
|
|
1432
|
-
let soundVolume = .
|
|
1440
|
+
let soundVolume = .3;
|
|
1433
1441
|
|
|
1434
1442
|
/** Default range where sound no longer plays
|
|
1435
1443
|
* @type {Number}
|
|
@@ -1514,6 +1522,11 @@ function setFontDefault(font) { fontDefault = font; }
|
|
|
1514
1522
|
* @memberof Settings */
|
|
1515
1523
|
function setShowSplashScreen(show) { showSplashScreen = show; }
|
|
1516
1524
|
|
|
1525
|
+
/** Set to disalbe rendering, audio, and input for servers
|
|
1526
|
+
* @param {Boolean} headless
|
|
1527
|
+
* @memberof Settings */
|
|
1528
|
+
function setHeadlessMode(headless) { headlessMode = headless; }
|
|
1529
|
+
|
|
1517
1530
|
/** Set if webgl rendering is enabled
|
|
1518
1531
|
* @param {Boolean} enable
|
|
1519
1532
|
* @memberof Settings */
|
|
@@ -1627,7 +1640,12 @@ function setSoundEnable(enable) { soundEnable = enable; }
|
|
|
1627
1640
|
/** Set volume scale to apply to all sound, music and speech
|
|
1628
1641
|
* @param {Number} volume
|
|
1629
1642
|
* @memberof Settings */
|
|
1630
|
-
function setSoundVolume(volume)
|
|
1643
|
+
function setSoundVolume(volume)
|
|
1644
|
+
{
|
|
1645
|
+
soundVolume = volume;
|
|
1646
|
+
if (soundEnable && !headlessMode && audioGainNode)
|
|
1647
|
+
audioGainNode.gain.value = volume; // update gain immediatly
|
|
1648
|
+
}
|
|
1631
1649
|
|
|
1632
1650
|
/** Set default range where sound no longer plays
|
|
1633
1651
|
* @param {Number} range
|
|
@@ -1760,6 +1778,8 @@ class EngineObject
|
|
|
1760
1778
|
this.spawnTime = time;
|
|
1761
1779
|
/** @property {Array} - List of children of this object */
|
|
1762
1780
|
this.children = [];
|
|
1781
|
+
/** @property {Boolean} - Limit object speed using linear or circular math */
|
|
1782
|
+
this.clampSpeedLinear = true;
|
|
1763
1783
|
|
|
1764
1784
|
// parent child system
|
|
1765
1785
|
/** @property {EngineObject} - Parent of object if in local space */
|
|
@@ -1783,21 +1803,46 @@ class EngineObject
|
|
|
1783
1803
|
engineObjects.push(this);
|
|
1784
1804
|
}
|
|
1785
1805
|
|
|
1786
|
-
/** Update the object transform
|
|
1787
|
-
|
|
1806
|
+
/** Update the object transform, called automatically by engine even when paused */
|
|
1807
|
+
updateTransforms()
|
|
1788
1808
|
{
|
|
1789
1809
|
const parent = this.parent;
|
|
1790
1810
|
if (parent)
|
|
1791
1811
|
{
|
|
1792
1812
|
// copy parent pos/angle
|
|
1793
|
-
|
|
1794
|
-
this.
|
|
1795
|
-
|
|
1813
|
+
const mirror = parent.getMirrorSign();
|
|
1814
|
+
this.pos = this.localPos.multiply(vec2(mirror,1)).rotate(-parent.angle).add(parent.pos);
|
|
1815
|
+
this.angle = mirror*this.localAngle + parent.angle;
|
|
1796
1816
|
}
|
|
1797
1817
|
|
|
1818
|
+
// update children
|
|
1819
|
+
for (const child of this.children)
|
|
1820
|
+
child.updateTransforms();
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
/** Update the object physics, called automatically by engine once each frame */
|
|
1824
|
+
update()
|
|
1825
|
+
{
|
|
1826
|
+
// child objects do not have physics
|
|
1827
|
+
if (this.parent)
|
|
1828
|
+
return;
|
|
1829
|
+
|
|
1798
1830
|
// limit max speed to prevent missing collisions
|
|
1799
|
-
|
|
1800
|
-
|
|
1831
|
+
if (this.clampSpeedLinear)
|
|
1832
|
+
{
|
|
1833
|
+
this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
|
|
1834
|
+
this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
|
|
1835
|
+
}
|
|
1836
|
+
else
|
|
1837
|
+
{
|
|
1838
|
+
const length2 = this.velocity.lengthSquared();
|
|
1839
|
+
if (length2 > objectMaxSpeed*objectMaxSpeed)
|
|
1840
|
+
{
|
|
1841
|
+
const s = objectMaxSpeed / length2**.5;
|
|
1842
|
+
this.velocity.x *= s;
|
|
1843
|
+
this.velocity.y *= s;
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1801
1846
|
|
|
1802
1847
|
// apply physics
|
|
1803
1848
|
const oldPos = this.pos.copy();
|
|
@@ -1809,8 +1854,7 @@ class EngineObject
|
|
|
1809
1854
|
// physics sanity checks
|
|
1810
1855
|
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
1811
1856
|
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
1812
|
-
|
|
1813
|
-
if (!enablePhysicsSolver || !this.mass) // do not update collision for fixed objects
|
|
1857
|
+
if (!enablePhysicsSolver || !this.mass) // dont do collision for fixed objects
|
|
1814
1858
|
return;
|
|
1815
1859
|
|
|
1816
1860
|
const wasMovingDown = this.velocity.y < 0;
|
|
@@ -2140,6 +2184,9 @@ let drawCount;
|
|
|
2140
2184
|
*/
|
|
2141
2185
|
function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
2142
2186
|
{
|
|
2187
|
+
if (headlessMode)
|
|
2188
|
+
return new TileInfo;
|
|
2189
|
+
|
|
2143
2190
|
// if size is a number, make it a vector
|
|
2144
2191
|
if (typeof size === 'number')
|
|
2145
2192
|
{
|
|
@@ -2173,9 +2220,9 @@ class TileInfo
|
|
|
2173
2220
|
constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
2174
2221
|
{
|
|
2175
2222
|
/** @property {Vector2} - Top left corner of tile in pixels */
|
|
2176
|
-
this.pos = pos;
|
|
2223
|
+
this.pos = pos.copy();
|
|
2177
2224
|
/** @property {Vector2} - Size of tile in pixels */
|
|
2178
|
-
this.size = size;
|
|
2225
|
+
this.size = size.copy();
|
|
2179
2226
|
/** @property {Number} - Texture index to use */
|
|
2180
2227
|
this.textureIndex = textureIndex;
|
|
2181
2228
|
}
|
|
@@ -2267,7 +2314,7 @@ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
|
2267
2314
|
* @param {Color} [additiveColor=(0,0,0,0)] - Additive color to be applied
|
|
2268
2315
|
* @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
2269
2316
|
* @param {Boolean} [screenSpace=false] - If true the pos and size are in screen space
|
|
2270
|
-
* @param {CanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
2317
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
2271
2318
|
* @memberof Draw */
|
|
2272
2319
|
function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
2273
2320
|
angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace, context)
|
|
@@ -2340,7 +2387,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
2340
2387
|
* @param {Number} [angle]
|
|
2341
2388
|
* @param {Boolean} [useWebGL=glEnable]
|
|
2342
2389
|
* @param {Boolean} [screenSpace=false]
|
|
2343
|
-
* @param {CanvasRenderingContext2D} [context]
|
|
2390
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
2344
2391
|
* @memberof Draw */
|
|
2345
2392
|
function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
2346
2393
|
{
|
|
@@ -2354,7 +2401,7 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
2354
2401
|
* @param {Color} [color=(1,1,1,1)]
|
|
2355
2402
|
* @param {Boolean} [useWebGL=glEnable]
|
|
2356
2403
|
* @param {Boolean} [screenSpace=false]
|
|
2357
|
-
* @param {CanvasRenderingContext2D} [context]
|
|
2404
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
2358
2405
|
* @memberof Draw */
|
|
2359
2406
|
function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
|
|
2360
2407
|
{
|
|
@@ -2370,7 +2417,7 @@ function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, contex
|
|
|
2370
2417
|
* @param {Boolean} mirror
|
|
2371
2418
|
* @param {Function} drawFunction
|
|
2372
2419
|
* @param {Boolean} [screenSpace=false]
|
|
2373
|
-
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
2420
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
|
|
2374
2421
|
* @memberof Draw */
|
|
2375
2422
|
function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, context=mainContext)
|
|
2376
2423
|
{
|
|
@@ -2391,7 +2438,7 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
|
|
|
2391
2438
|
/** Enable normal or additive blend mode
|
|
2392
2439
|
* @param {Boolean} [additive]
|
|
2393
2440
|
* @param {Boolean} [useWebGL=glEnable]
|
|
2394
|
-
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
2441
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
|
|
2395
2442
|
* @memberof Draw */
|
|
2396
2443
|
function setBlendMode(additive, useWebGL=glEnable, context)
|
|
2397
2444
|
{
|
|
@@ -2416,7 +2463,7 @@ function setBlendMode(additive, useWebGL=glEnable, context)
|
|
|
2416
2463
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
2417
2464
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
2418
2465
|
* @param {String} [font=fontDefault]
|
|
2419
|
-
* @param {CanvasRenderingContext2D} [context=overlayContext]
|
|
2466
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
2420
2467
|
* @memberof Draw */
|
|
2421
2468
|
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context)
|
|
2422
2469
|
{
|
|
@@ -2433,7 +2480,7 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
2433
2480
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
2434
2481
|
* @param {CanvasTextAlign} [textAlign]
|
|
2435
2482
|
* @param {String} [font=fontDefault]
|
|
2436
|
-
* @param {CanvasRenderingContext2D} [context=overlayContext]
|
|
2483
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
2437
2484
|
* @memberof Draw */
|
|
2438
2485
|
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
|
|
2439
2486
|
{
|
|
@@ -2476,7 +2523,7 @@ class FontImage
|
|
|
2476
2523
|
* @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
|
|
2477
2524
|
* @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
|
|
2478
2525
|
* @param {Vector2} [paddingSize=(0,1)] - How much extra space to add between characters
|
|
2479
|
-
* @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
2526
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
2480
2527
|
*/
|
|
2481
2528
|
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), context=overlayContext)
|
|
2482
2529
|
{
|
|
@@ -2684,7 +2731,7 @@ function gamepadWasReleased(button, gamepad=0)
|
|
|
2684
2731
|
* @return {Vector2}
|
|
2685
2732
|
* @memberof Input */
|
|
2686
2733
|
function gamepadStick(stick, gamepad=0)
|
|
2687
|
-
{ return
|
|
2734
|
+
{ return gamepadStickData[gamepad] ? gamepadStickData[gamepad][stick] || vec2() : vec2(); }
|
|
2688
2735
|
|
|
2689
2736
|
///////////////////////////////////////////////////////////////////////////////
|
|
2690
2737
|
// Input update called by engine
|
|
@@ -2695,6 +2742,8 @@ let inputData = [[]];
|
|
|
2695
2742
|
|
|
2696
2743
|
function inputUpdate()
|
|
2697
2744
|
{
|
|
2745
|
+
if (headlessMode) return;
|
|
2746
|
+
|
|
2698
2747
|
// clear input when lost focus (prevent stuck keys)
|
|
2699
2748
|
isTouchDevice || document.hasFocus() || clearInput();
|
|
2700
2749
|
|
|
@@ -2707,6 +2756,8 @@ function inputUpdate()
|
|
|
2707
2756
|
|
|
2708
2757
|
function inputUpdatePost()
|
|
2709
2758
|
{
|
|
2759
|
+
if (headlessMode) return;
|
|
2760
|
+
|
|
2710
2761
|
// clear input to prepare for next frame
|
|
2711
2762
|
for (const deviceInputData of inputData)
|
|
2712
2763
|
for (const i in deviceInputData)
|
|
@@ -2715,9 +2766,12 @@ function inputUpdatePost()
|
|
|
2715
2766
|
}
|
|
2716
2767
|
|
|
2717
2768
|
///////////////////////////////////////////////////////////////////////////////
|
|
2718
|
-
//
|
|
2769
|
+
// Input event handlers
|
|
2719
2770
|
|
|
2771
|
+
function inputInit()
|
|
2720
2772
|
{
|
|
2773
|
+
if (headlessMode) return;
|
|
2774
|
+
|
|
2721
2775
|
onkeydown = (e)=>
|
|
2722
2776
|
{
|
|
2723
2777
|
if (debug && e.target != document.body) return;
|
|
@@ -2748,21 +2802,29 @@ function inputUpdatePost()
|
|
|
2748
2802
|
c == 'KeyA' ? 'ArrowLeft' :
|
|
2749
2803
|
c == 'KeyD' ? 'ArrowRight' : c : c;
|
|
2750
2804
|
}
|
|
2805
|
+
|
|
2806
|
+
// mouse event handlers
|
|
2807
|
+
onmousedown = (e)=>
|
|
2808
|
+
{
|
|
2809
|
+
isUsingGamepad = false;
|
|
2810
|
+
inputData[0][e.button] = 3;
|
|
2811
|
+
mousePosScreen = mouseToScreen(e);
|
|
2812
|
+
e.button && e.preventDefault();
|
|
2813
|
+
}
|
|
2814
|
+
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
2815
|
+
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
2816
|
+
onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
|
|
2817
|
+
oncontextmenu = (e)=> false; // prevent right click menu
|
|
2818
|
+
|
|
2819
|
+
// init touch input
|
|
2820
|
+
if (isTouchDevice)
|
|
2821
|
+
touchInputInit();
|
|
2751
2822
|
}
|
|
2752
2823
|
|
|
2753
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2754
|
-
// Mouse event handlers
|
|
2755
|
-
|
|
2756
|
-
onmousedown = (e)=> {isUsingGamepad = false; inputData[0][e.button] = 3; mousePosScreen = mouseToScreen(e); e.button && e.preventDefault();}
|
|
2757
|
-
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
2758
|
-
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
2759
|
-
onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
|
|
2760
|
-
oncontextmenu = (e)=> false; // prevent right click menu
|
|
2761
|
-
|
|
2762
2824
|
// convert a mouse or touch event position to screen space
|
|
2763
2825
|
function mouseToScreen(mousePos)
|
|
2764
2826
|
{
|
|
2765
|
-
if (!mainCanvas)
|
|
2827
|
+
if (!mainCanvas || headlessMode)
|
|
2766
2828
|
return vec2(); // fix bug that can occur if user clicks before page loads
|
|
2767
2829
|
|
|
2768
2830
|
const rect = mainCanvas.getBoundingClientRect();
|
|
@@ -2774,7 +2836,7 @@ function mouseToScreen(mousePos)
|
|
|
2774
2836
|
// Gamepad input
|
|
2775
2837
|
|
|
2776
2838
|
// gamepad internal variables
|
|
2777
|
-
const
|
|
2839
|
+
const gamepadStickData = [];
|
|
2778
2840
|
|
|
2779
2841
|
// gamepads are updated by engine every frame automatically
|
|
2780
2842
|
function gamepadsUpdate()
|
|
@@ -2791,14 +2853,11 @@ function gamepadsUpdate()
|
|
|
2791
2853
|
// update touch gamepad if enabled
|
|
2792
2854
|
if (touchGamepadEnable && isTouchDevice)
|
|
2793
2855
|
{
|
|
2794
|
-
|
|
2795
|
-
if (!touchGamepadButtons)
|
|
2796
|
-
createTouchGamepad();
|
|
2797
|
-
|
|
2856
|
+
ASSERT(touchGamepadButtons, 'set touchGamepadEnable before calling init!');
|
|
2798
2857
|
if (touchGamepadTimer.isSet())
|
|
2799
2858
|
{
|
|
2800
2859
|
// read virtual analog stick
|
|
2801
|
-
const sticks =
|
|
2860
|
+
const sticks = gamepadStickData[0] || (gamepadStickData[0] = []);
|
|
2802
2861
|
sticks[0] = vec2();
|
|
2803
2862
|
if (touchGamepadAnalog)
|
|
2804
2863
|
sticks[0] = applyDeadZones(touchGamepadStick);
|
|
@@ -2815,7 +2874,8 @@ function gamepadsUpdate()
|
|
|
2815
2874
|
for (let i=10; i--;)
|
|
2816
2875
|
{
|
|
2817
2876
|
const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
|
|
2818
|
-
|
|
2877
|
+
const wasDown = gamepadIsDown(j,0);
|
|
2878
|
+
data[j] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
2819
2879
|
}
|
|
2820
2880
|
}
|
|
2821
2881
|
}
|
|
@@ -2835,7 +2895,7 @@ function gamepadsUpdate()
|
|
|
2835
2895
|
// get or create gamepad data
|
|
2836
2896
|
const gamepad = gamepads[i];
|
|
2837
2897
|
const data = inputData[i+1] || (inputData[i+1] = []);
|
|
2838
|
-
const sticks =
|
|
2898
|
+
const sticks = gamepadStickData[i] || (gamepadStickData[i] = []);
|
|
2839
2899
|
|
|
2840
2900
|
if (gamepad)
|
|
2841
2901
|
{
|
|
@@ -2874,28 +2934,44 @@ function gamepadsUpdate()
|
|
|
2874
2934
|
* @param {Number|Array} [pattern] - single value in ms or vibration interval array
|
|
2875
2935
|
* @memberof Input */
|
|
2876
2936
|
function vibrate(pattern=100)
|
|
2877
|
-
{ vibrateEnable && navigator && navigator.vibrate && navigator.vibrate(pattern); }
|
|
2937
|
+
{ vibrateEnable && !headlessMode && navigator && navigator.vibrate && navigator.vibrate(pattern); }
|
|
2878
2938
|
|
|
2879
2939
|
/** Cancel any ongoing vibration
|
|
2880
2940
|
* @memberof Input */
|
|
2881
2941
|
function vibrateStop() { vibrate(0); }
|
|
2882
2942
|
|
|
2883
2943
|
///////////////////////////////////////////////////////////////////////////////
|
|
2884
|
-
// Touch input
|
|
2944
|
+
// Touch input & virtual on screen gamepad
|
|
2885
2945
|
|
|
2886
2946
|
/** True if a touch device has been detected
|
|
2887
2947
|
* @memberof Input */
|
|
2888
|
-
const isTouchDevice = window.ontouchstart !== undefined;
|
|
2948
|
+
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
2949
|
+
|
|
2950
|
+
// touch gamepad internal variables
|
|
2951
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
2889
2952
|
|
|
2890
2953
|
// try to enable touch mouse
|
|
2891
|
-
|
|
2954
|
+
function touchInputInit()
|
|
2892
2955
|
{
|
|
2956
|
+
// add non passive touch event listeners
|
|
2957
|
+
let handleTouch = handleTouchDefault;
|
|
2958
|
+
if (touchGamepadEnable)
|
|
2959
|
+
{
|
|
2960
|
+
// touch input internal variables
|
|
2961
|
+
handleTouch = handleTouchGamepad;
|
|
2962
|
+
touchGamepadButtons = [];
|
|
2963
|
+
touchGamepadStick = vec2();
|
|
2964
|
+
}
|
|
2965
|
+
document.addEventListener('touchstart', (e) => handleTouch(e), { passive: false });
|
|
2966
|
+
document.addEventListener('touchmove', (e) => handleTouch(e), { passive: false });
|
|
2967
|
+
document.addEventListener('touchend', (e) => handleTouch(e), { passive: false });
|
|
2968
|
+
|
|
2893
2969
|
// override mouse events
|
|
2894
|
-
let wasTouching;
|
|
2895
2970
|
onmousedown = onmouseup = ()=> 0;
|
|
2896
2971
|
|
|
2897
2972
|
// handle all touch events the same way
|
|
2898
|
-
|
|
2973
|
+
let wasTouching;
|
|
2974
|
+
function handleTouchDefault(e)
|
|
2899
2975
|
{
|
|
2900
2976
|
// fix stalled audio requiring user interaction
|
|
2901
2977
|
if (soundEnable && audioContext && audioContext.state != 'running')
|
|
@@ -2924,27 +3000,14 @@ if (isTouchDevice)
|
|
|
2924
3000
|
// must return true so the document will get focus
|
|
2925
3001
|
return true;
|
|
2926
3002
|
}
|
|
2927
|
-
}
|
|
2928
|
-
|
|
2929
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2930
|
-
// touch gamepad, virtual on screen gamepad emulator for touch devices
|
|
2931
|
-
|
|
2932
|
-
// touch input internal variables
|
|
2933
|
-
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
2934
3003
|
|
|
2935
|
-
//
|
|
2936
|
-
function
|
|
2937
|
-
{
|
|
2938
|
-
// touch input internal variables
|
|
2939
|
-
touchGamepadButtons = [];
|
|
2940
|
-
touchGamepadStick = vec2();
|
|
2941
|
-
|
|
2942
|
-
const touchHandler = ontouchstart;
|
|
2943
|
-
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
3004
|
+
// special handling for virtual gamepad mode
|
|
3005
|
+
function handleTouchGamepad(e)
|
|
2944
3006
|
{
|
|
2945
3007
|
// clear touch gamepad input
|
|
2946
3008
|
touchGamepadStick = vec2();
|
|
2947
3009
|
touchGamepadButtons = [];
|
|
3010
|
+
isUsingGamepad = true;
|
|
2948
3011
|
|
|
2949
3012
|
const touching = e.touches.length;
|
|
2950
3013
|
if (touching)
|
|
@@ -2985,9 +3048,8 @@ function createTouchGamepad()
|
|
|
2985
3048
|
}
|
|
2986
3049
|
}
|
|
2987
3050
|
|
|
2988
|
-
// call default touch handler
|
|
2989
|
-
|
|
2990
|
-
isUsingGamepad = true;
|
|
3051
|
+
// call default touch handler so normal touch events still work
|
|
3052
|
+
handleTouchDefault(e);
|
|
2991
3053
|
|
|
2992
3054
|
// must return true so the document will get focus
|
|
2993
3055
|
return true;
|
|
@@ -3006,32 +3068,33 @@ function touchGamepadRender()
|
|
|
3006
3068
|
return;
|
|
3007
3069
|
|
|
3008
3070
|
// setup the canvas
|
|
3009
|
-
overlayContext
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3071
|
+
const context = overlayContext;
|
|
3072
|
+
context.save();
|
|
3073
|
+
context.globalAlpha = alpha*touchGamepadAlpha;
|
|
3074
|
+
context.strokeStyle = '#fff';
|
|
3075
|
+
context.lineWidth = 3;
|
|
3013
3076
|
|
|
3014
3077
|
// draw left analog stick
|
|
3015
|
-
|
|
3016
|
-
|
|
3078
|
+
context.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
|
|
3079
|
+
context.beginPath();
|
|
3017
3080
|
|
|
3018
3081
|
const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
3019
3082
|
if (touchGamepadAnalog) // draw circle shaped gamepad
|
|
3020
3083
|
{
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3084
|
+
context.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
|
|
3085
|
+
context.fill();
|
|
3086
|
+
context.stroke();
|
|
3024
3087
|
}
|
|
3025
3088
|
else // draw cross shaped gamepad
|
|
3026
3089
|
{
|
|
3027
3090
|
for(let i=10; i--;)
|
|
3028
3091
|
{
|
|
3029
3092
|
const angle = i*PI/4;
|
|
3030
|
-
|
|
3031
|
-
i%2 &&
|
|
3032
|
-
i==1 &&
|
|
3093
|
+
context.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
|
|
3094
|
+
i%2 && context.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
|
|
3095
|
+
i==1 && context.fill();
|
|
3033
3096
|
}
|
|
3034
|
-
|
|
3097
|
+
context.stroke();
|
|
3035
3098
|
}
|
|
3036
3099
|
|
|
3037
3100
|
// draw right face buttons
|
|
@@ -3039,15 +3102,15 @@ function touchGamepadRender()
|
|
|
3039
3102
|
for (let i=4; i--;)
|
|
3040
3103
|
{
|
|
3041
3104
|
const pos = rightCenter.add(vec2().setDirection(i, touchGamepadSize/2));
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3105
|
+
context.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
|
|
3106
|
+
context.beginPath();
|
|
3107
|
+
context.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
|
|
3108
|
+
context.fill();
|
|
3109
|
+
context.stroke();
|
|
3047
3110
|
}
|
|
3048
3111
|
|
|
3049
3112
|
// set canvas back to normal
|
|
3050
|
-
|
|
3113
|
+
context.restore();
|
|
3051
3114
|
}
|
|
3052
3115
|
/**
|
|
3053
3116
|
* LittleJS Audio System
|
|
@@ -3062,6 +3125,32 @@ function touchGamepadRender()
|
|
|
3062
3125
|
|
|
3063
3126
|
|
|
3064
3127
|
|
|
3128
|
+
/** Audio context used by the engine
|
|
3129
|
+
* @type {AudioContext}
|
|
3130
|
+
* @memberof Audio */
|
|
3131
|
+
let audioContext;
|
|
3132
|
+
|
|
3133
|
+
/** Master gain node for all audio to pass through
|
|
3134
|
+
* @type {GainNode}
|
|
3135
|
+
* @memberof Audio */
|
|
3136
|
+
let audioGainNode;
|
|
3137
|
+
|
|
3138
|
+
function audioInit()
|
|
3139
|
+
{
|
|
3140
|
+
if (!soundEnable || headlessMode) return;
|
|
3141
|
+
|
|
3142
|
+
// create audio context
|
|
3143
|
+
audioContext = new AudioContext;
|
|
3144
|
+
|
|
3145
|
+
// create and connect gain node
|
|
3146
|
+
// (createGain is more widely spported then GainNode construtor)
|
|
3147
|
+
audioGainNode = audioContext.createGain();
|
|
3148
|
+
audioGainNode.connect(audioContext.destination);
|
|
3149
|
+
setSoundVolume(soundVolume); // update gain volume
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3153
|
+
|
|
3065
3154
|
/**
|
|
3066
3155
|
* Sound Object - Stores a sound for later use and can be played positionally
|
|
3067
3156
|
*
|
|
@@ -3082,7 +3171,7 @@ class Sound
|
|
|
3082
3171
|
*/
|
|
3083
3172
|
constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
|
|
3084
3173
|
{
|
|
3085
|
-
if (!soundEnable) return;
|
|
3174
|
+
if (!soundEnable || headlessMode) return;
|
|
3086
3175
|
|
|
3087
3176
|
/** @property {Number} - World space max range of sound, will not play if camera is farther away */
|
|
3088
3177
|
this.range = range;
|
|
@@ -3096,8 +3185,8 @@ class Sound
|
|
|
3096
3185
|
if (zzfxSound)
|
|
3097
3186
|
{
|
|
3098
3187
|
// generate zzfx sound now for fast playback
|
|
3099
|
-
|
|
3100
|
-
zzfxSound[1]
|
|
3188
|
+
const defaultRandomness = .05;
|
|
3189
|
+
this.randomness = zzfxSound[1] || defaultRandomness;
|
|
3101
3190
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
3102
3191
|
this.sampleRate = zzfxR;
|
|
3103
3192
|
}
|
|
@@ -3113,7 +3202,8 @@ class Sound
|
|
|
3113
3202
|
*/
|
|
3114
3203
|
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false)
|
|
3115
3204
|
{
|
|
3116
|
-
if (!soundEnable ||
|
|
3205
|
+
if (!soundEnable || headlessMode) return;
|
|
3206
|
+
if (!this.sampleChannels) return;
|
|
3117
3207
|
|
|
3118
3208
|
let pan;
|
|
3119
3209
|
if (pos)
|
|
@@ -3190,14 +3280,14 @@ class SoundWave extends Sound
|
|
|
3190
3280
|
* @param {Number} [randomness] - How much to randomize frequency each time sound plays
|
|
3191
3281
|
* @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
|
|
3192
3282
|
* @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
|
|
3283
|
+
* @param {Function} [onloadCallback] - callback function to call when sound is loaded
|
|
3193
3284
|
*/
|
|
3194
|
-
constructor(filename, randomness=0, range, taper)
|
|
3285
|
+
constructor(filename, randomness=0, range, taper, onloadCallback)
|
|
3195
3286
|
{
|
|
3196
3287
|
super(undefined, range, taper);
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
if (!soundEnable) return;
|
|
3288
|
+
if (!soundEnable || headlessMode) return;
|
|
3200
3289
|
|
|
3290
|
+
this.randomness = randomness;
|
|
3201
3291
|
fetch(filename)
|
|
3202
3292
|
.then(response => response.arrayBuffer())
|
|
3203
3293
|
.then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))
|
|
@@ -3207,10 +3297,23 @@ class SoundWave extends Sound
|
|
|
3207
3297
|
for (let i = audioBuffer.numberOfChannels; i--;)
|
|
3208
3298
|
this.sampleChannels[i] = Array.from(audioBuffer.getChannelData(i));
|
|
3209
3299
|
this.sampleRate = audioBuffer.sampleRate;
|
|
3210
|
-
});
|
|
3300
|
+
}).then(() => onloadCallback && onloadCallback(this));
|
|
3211
3301
|
}
|
|
3212
3302
|
}
|
|
3213
3303
|
|
|
3304
|
+
/** Play an mp3, ogg, or wav audio from a local file or url
|
|
3305
|
+
* @param {String} filename - Location of sound file to play
|
|
3306
|
+
* @param {Number} [volume] - How much to scale volume by
|
|
3307
|
+
* @param {Boolean} [loop] - True if the music should loop
|
|
3308
|
+
* @return {SoundWave} - The sound object for this file
|
|
3309
|
+
* @memberof Audio */
|
|
3310
|
+
function playAudioFile(filename, volume=1, loop=false)
|
|
3311
|
+
{
|
|
3312
|
+
if (!soundEnable || headlessMode) return;
|
|
3313
|
+
|
|
3314
|
+
return new SoundWave(filename,0,0,0, s=>s.play(undefined, volume, 1, 1, loop));
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3214
3317
|
/**
|
|
3215
3318
|
* Music Object - Stores a zzfx music track for later use
|
|
3216
3319
|
*
|
|
@@ -3250,7 +3353,7 @@ class Music extends Sound
|
|
|
3250
3353
|
{
|
|
3251
3354
|
super(undefined);
|
|
3252
3355
|
|
|
3253
|
-
if (!soundEnable) return;
|
|
3356
|
+
if (!soundEnable || headlessMode) return;
|
|
3254
3357
|
this.randomness = 0;
|
|
3255
3358
|
this.sampleChannels = zzfxM(...zzfxMusic);
|
|
3256
3359
|
this.sampleRate = zzfxR;
|
|
@@ -3265,23 +3368,6 @@ class Music extends Sound
|
|
|
3265
3368
|
{ return super.play(undefined, volume, 1, 1, loop); }
|
|
3266
3369
|
}
|
|
3267
3370
|
|
|
3268
|
-
/** Play an mp3, ogg, or wav audio from a local file or url
|
|
3269
|
-
* @param {String} filename - Location of sound file to play
|
|
3270
|
-
* @param {Number} [volume] - How much to scale volume by
|
|
3271
|
-
* @param {Boolean} [loop] - True if the music should loop
|
|
3272
|
-
* @return {HTMLAudioElement} - The audio element for this sound
|
|
3273
|
-
* @memberof Audio */
|
|
3274
|
-
function playAudioFile(filename, volume=1, loop=false)
|
|
3275
|
-
{
|
|
3276
|
-
if (!soundEnable) return;
|
|
3277
|
-
|
|
3278
|
-
const audio = new Audio(filename);
|
|
3279
|
-
audio.volume = soundVolume * volume;
|
|
3280
|
-
audio.loop = loop;
|
|
3281
|
-
audio.play();
|
|
3282
|
-
return audio;
|
|
3283
|
-
}
|
|
3284
|
-
|
|
3285
3371
|
/** Speak text with passed in settings
|
|
3286
3372
|
* @param {String} text - The text to speak
|
|
3287
3373
|
* @param {String} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
|
|
@@ -3292,7 +3378,8 @@ function playAudioFile(filename, volume=1, loop=false)
|
|
|
3292
3378
|
* @memberof Audio */
|
|
3293
3379
|
function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
3294
3380
|
{
|
|
3295
|
-
if (!soundEnable ||
|
|
3381
|
+
if (!soundEnable || headlessMode) return;
|
|
3382
|
+
if (!speechSynthesis) return;
|
|
3296
3383
|
|
|
3297
3384
|
// common languages (not supported by all browsers)
|
|
3298
3385
|
// en - english, it - italian, fr - french, de - german, es - spanish
|
|
@@ -3322,14 +3409,8 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
3322
3409
|
|
|
3323
3410
|
///////////////////////////////////////////////////////////////////////////////
|
|
3324
3411
|
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
* @memberof Audio */
|
|
3328
|
-
let audioContext = new AudioContext;
|
|
3329
|
-
|
|
3330
|
-
/** Keep track if audio was suspended when last sound was played
|
|
3331
|
-
* @type {Boolean}
|
|
3332
|
-
* @memberof Audio */
|
|
3412
|
+
// internal tracking if audio was suspended when last sound was played
|
|
3413
|
+
// allows first suspended sound to play when audio is resumed
|
|
3333
3414
|
let audioSuspended = false;
|
|
3334
3415
|
|
|
3335
3416
|
/** Play cached audio samples with given settings
|
|
@@ -3343,7 +3424,7 @@ let audioSuspended = false;
|
|
|
3343
3424
|
* @memberof Audio */
|
|
3344
3425
|
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR)
|
|
3345
3426
|
{
|
|
3346
|
-
if (!soundEnable) return;
|
|
3427
|
+
if (!soundEnable || headlessMode) return;
|
|
3347
3428
|
|
|
3348
3429
|
// prevent sounds from building up if they can't be played
|
|
3349
3430
|
const audioWasSuspended = audioSuspended;
|
|
@@ -3367,10 +3448,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3367
3448
|
source.playbackRate.value = rate;
|
|
3368
3449
|
source.loop = loop;
|
|
3369
3450
|
|
|
3370
|
-
//
|
|
3451
|
+
// set master gain volume
|
|
3452
|
+
setSoundVolume(soundVolume);
|
|
3453
|
+
|
|
3454
|
+
// create and connect gain node
|
|
3371
3455
|
const gainNode = audioContext.createGain();
|
|
3372
|
-
gainNode.gain.value =
|
|
3373
|
-
gainNode.connect(
|
|
3456
|
+
gainNode.gain.value = volume;
|
|
3457
|
+
gainNode.connect(audioGainNode);
|
|
3374
3458
|
|
|
3375
3459
|
// connect source to stereo panner and gain
|
|
3376
3460
|
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)})).connect(gainNode);
|
|
@@ -3389,7 +3473,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3389
3473
|
* @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
|
|
3390
3474
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
3391
3475
|
* @memberof Audio */
|
|
3392
|
-
function zzfx(...zzfxSound) { return
|
|
3476
|
+
function zzfx(...zzfxSound) { return new Sound(zzfxSound).play(); }
|
|
3393
3477
|
|
|
3394
3478
|
/** Sample rate used for all ZzFX sounds
|
|
3395
3479
|
* @default 44100
|
|
@@ -3398,7 +3482,7 @@ const zzfxR = 44100;
|
|
|
3398
3482
|
|
|
3399
3483
|
/** Generate samples for a ZzFX sound
|
|
3400
3484
|
* @param {Number} [volume] - Volume scale (percent)
|
|
3401
|
-
* @param {Number} [randomness] -
|
|
3485
|
+
* @param {Number} [randomness] - Unused in this fuction, handled by Sound class
|
|
3402
3486
|
* @param {Number} [frequency] - Frequency of sound (Hz)
|
|
3403
3487
|
* @param {Number} [attack] - Attack time, how fast sound starts (seconds)
|
|
3404
3488
|
* @param {Number} [sustain] - Sustain time, how long sound holds (seconds)
|
|
@@ -3424,17 +3508,18 @@ const zzfxR = 44100;
|
|
|
3424
3508
|
function zzfxG
|
|
3425
3509
|
(
|
|
3426
3510
|
// parameters
|
|
3427
|
-
volume = 1, randomness =
|
|
3511
|
+
volume = 1, randomness = 0, frequency = 220, attack = 0, sustain = 0,
|
|
3428
3512
|
release = .1, shape = 0, shapeCurve = 1, slide = 0, deltaSlide = 0,
|
|
3429
3513
|
pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, modulation = 0,
|
|
3430
3514
|
bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0, filter = 0
|
|
3431
3515
|
)
|
|
3432
3516
|
{
|
|
3517
|
+
// LJS Note: ZZFX modded so randomness is handled by Sound class
|
|
3518
|
+
|
|
3433
3519
|
// init parameters
|
|
3434
3520
|
let PI2 = PI*2, sampleRate = zzfxR,
|
|
3435
3521
|
startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
|
|
3436
|
-
startFrequency = frequency *=
|
|
3437
|
-
rand(1 + randomness, 1-randomness) * PI2 / sampleRate,
|
|
3522
|
+
startFrequency = frequency *= PI2 / sampleRate,
|
|
3438
3523
|
b = [], t = 0, tm = 0, i = 0, j = 1, r = 0, c = 0, s = 0, f, length,
|
|
3439
3524
|
|
|
3440
3525
|
// biquad LP/HP filter
|
|
@@ -3456,7 +3541,6 @@ function zzfxG
|
|
|
3456
3541
|
pitchJump *= PI2 / sampleRate;
|
|
3457
3542
|
pitchJumpTime *= sampleRate;
|
|
3458
3543
|
repeatTime = repeatTime * sampleRate | 0;
|
|
3459
|
-
volume *= soundVolume;
|
|
3460
3544
|
|
|
3461
3545
|
// generate waveform
|
|
3462
3546
|
for(length = attack + decay + sustain + release + delay | 0;
|
|
@@ -3802,7 +3886,7 @@ class TileLayer extends EngineObject
|
|
|
3802
3886
|
|
|
3803
3887
|
/** @property {HTMLCanvasElement} - The canvas used by this tile layer */
|
|
3804
3888
|
this.canvas = document.createElement('canvas');
|
|
3805
|
-
/** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
3889
|
+
/** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
3806
3890
|
this.context = this.canvas.getContext('2d');
|
|
3807
3891
|
/** @property {Vector2} - How much to scale this layer when rendered */
|
|
3808
3892
|
this.scale = scale;
|
|
@@ -3813,6 +3897,17 @@ class TileLayer extends EngineObject
|
|
|
3813
3897
|
this.data = [];
|
|
3814
3898
|
for (let j = this.size.area(); j--;)
|
|
3815
3899
|
this.data.push(new TileLayerData);
|
|
3900
|
+
|
|
3901
|
+
if (headlessMode)
|
|
3902
|
+
{
|
|
3903
|
+
// disable rendering
|
|
3904
|
+
this.redraw = () => {};
|
|
3905
|
+
this.render = () => {};
|
|
3906
|
+
this.redrawStart = () => {};
|
|
3907
|
+
this.redrawEnd = () => {};
|
|
3908
|
+
this.drawTileData = () => {};
|
|
3909
|
+
this.drawCanvas2D = () => {};
|
|
3910
|
+
}
|
|
3816
3911
|
}
|
|
3817
3912
|
|
|
3818
3913
|
/** Set data at a given position in the array
|
|
@@ -3843,7 +3938,7 @@ class TileLayer extends EngineObject
|
|
|
3843
3938
|
ASSERT(mainContext != this.context, 'must call redrawEnd() after drawing tiles');
|
|
3844
3939
|
|
|
3845
3940
|
// flush and copy gl canvas because tile canvas does not use webgl
|
|
3846
|
-
|
|
3941
|
+
!glOverlay && !this.isOverlay && glCopyToContext(mainContext);
|
|
3847
3942
|
|
|
3848
3943
|
// draw the entire cached level onto the canvas
|
|
3849
3944
|
const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
|
|
@@ -3893,14 +3988,14 @@ class TileLayer extends EngineObject
|
|
|
3893
3988
|
this.context.imageSmoothingEnabled = !canvasPixelated;
|
|
3894
3989
|
|
|
3895
3990
|
// setup gl rendering if enabled
|
|
3896
|
-
|
|
3991
|
+
glPreRender();
|
|
3897
3992
|
}
|
|
3898
3993
|
|
|
3899
3994
|
/** Call to end the redraw process */
|
|
3900
3995
|
redrawEnd()
|
|
3901
3996
|
{
|
|
3902
3997
|
ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
|
|
3903
|
-
|
|
3998
|
+
glCopyToContext(mainContext, true);
|
|
3904
3999
|
//debugSaveCanvas(this.canvas);
|
|
3905
4000
|
|
|
3906
4001
|
// set stuff back to normal
|
|
@@ -4225,20 +4320,21 @@ class ParticleEmitter extends EngineObject
|
|
|
4225
4320
|
class Particle extends EngineObject
|
|
4226
4321
|
{
|
|
4227
4322
|
/**
|
|
4228
|
-
* Create a particle with the
|
|
4229
|
-
*
|
|
4230
|
-
* @param {
|
|
4231
|
-
* @param {
|
|
4232
|
-
* @param {
|
|
4233
|
-
* @param {Color}
|
|
4234
|
-
* @param {
|
|
4235
|
-
* @param {Number}
|
|
4236
|
-
* @param {Number}
|
|
4237
|
-
* @param {Number}
|
|
4238
|
-
* @param {
|
|
4239
|
-
* @param {
|
|
4323
|
+
* Create a particle with the passed in settings
|
|
4324
|
+
* Typically this is created automatically by a ParticleEmitter
|
|
4325
|
+
* @param {Vector2} position - World space position of the particle
|
|
4326
|
+
* @param {TileInfo} tileInfo - Tile info to render particles
|
|
4327
|
+
* @param {Number} angle - Angle to rotate the particle
|
|
4328
|
+
* @param {Color} colorStart - Color at start of life
|
|
4329
|
+
* @param {Color} colorEnd - Color at end of life
|
|
4330
|
+
* @param {Number} lifeTime - How long to live for
|
|
4331
|
+
* @param {Number} sizeStart - Size at start of life
|
|
4332
|
+
* @param {Number} sizeEnd - Size at end of life
|
|
4333
|
+
* @param {Number} fadeRate - How quick to fade in/out
|
|
4334
|
+
* @param {Boolean} additive - Does it use additive blend mode
|
|
4335
|
+
* @param {Number} trailScale - If a trail, how long to make it
|
|
4240
4336
|
* @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
|
|
4241
|
-
* @param {Function}
|
|
4337
|
+
* @param {Function} [destroyCallback] - Callback when particle dies
|
|
4242
4338
|
*/
|
|
4243
4339
|
constructor(position, tileInfo, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, fadeRate, additive, trailScale, localSpaceEmitter, destroyCallback
|
|
4244
4340
|
)
|
|
@@ -4265,6 +4361,9 @@ class Particle extends EngineObject
|
|
|
4265
4361
|
this.localSpaceEmitter = localSpaceEmitter;
|
|
4266
4362
|
/** @property {Function} - Called when particle dies */
|
|
4267
4363
|
this.destroyCallback = destroyCallback;
|
|
4364
|
+
|
|
4365
|
+
// particles use circular clamped speed
|
|
4366
|
+
this.clampSpeedLinear = false;
|
|
4268
4367
|
}
|
|
4269
4368
|
|
|
4270
4369
|
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
@@ -4647,6 +4746,8 @@ let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData,
|
|
|
4647
4746
|
// Initalize WebGL, called automatically by the engine
|
|
4648
4747
|
function glInit()
|
|
4649
4748
|
{
|
|
4749
|
+
if (!glEnable || headlessMode) return;
|
|
4750
|
+
|
|
4650
4751
|
// create the canvas and textures
|
|
4651
4752
|
glCanvas = document.createElement('canvas');
|
|
4652
4753
|
glContext = glCanvas.getContext('webgl2');
|
|
@@ -4659,11 +4760,11 @@ function glInit()
|
|
|
4659
4760
|
'#version 300 es\n' + // specify GLSL ES version
|
|
4660
4761
|
'precision highp float;'+ // use highp for better accuracy
|
|
4661
4762
|
'uniform mat4 m;'+ // transform matrix
|
|
4662
|
-
'in vec2 g;'+ // geometry
|
|
4663
|
-
'in vec4 p,u,c,a;'+ // position/size, uvs, color, additiveColor
|
|
4664
|
-
'in float r;'+ // rotation
|
|
4665
|
-
'out vec2 v;'+ //
|
|
4666
|
-
'out vec4 d,e;'+ //
|
|
4763
|
+
'in vec2 g;'+ // in: geometry
|
|
4764
|
+
'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
|
|
4765
|
+
'in float r;'+ // in: rotation
|
|
4766
|
+
'out vec2 v;'+ // out: uv
|
|
4767
|
+
'out vec4 d,e;'+ // out: color, additiveColor
|
|
4667
4768
|
'void main(){'+ // shader entry point
|
|
4668
4769
|
'vec2 s=(g-.5)*p.zw;'+ // get size offset
|
|
4669
4770
|
'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
|
|
@@ -4673,10 +4774,10 @@ function glInit()
|
|
|
4673
4774
|
,
|
|
4674
4775
|
'#version 300 es\n' + // specify GLSL ES version
|
|
4675
4776
|
'precision highp float;'+ // use highp for better accuracy
|
|
4676
|
-
'in vec2 v;'+ // uv
|
|
4677
|
-
'in vec4 d,e;'+ // color, additiveColor
|
|
4678
4777
|
'uniform sampler2D s;'+ // texture
|
|
4679
|
-
'
|
|
4778
|
+
'in vec2 v;'+ // in: uv
|
|
4779
|
+
'in vec4 d,e;'+ // in: color, additiveColor
|
|
4780
|
+
'out vec4 c;'+ // out: color
|
|
4680
4781
|
'void main(){'+ // shader entry point
|
|
4681
4782
|
'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
|
|
4682
4783
|
'}' // end of shader
|
|
@@ -4698,9 +4799,11 @@ function glInit()
|
|
|
4698
4799
|
// Setup render each frame, called automatically by engine
|
|
4699
4800
|
function glPreRender()
|
|
4700
4801
|
{
|
|
4802
|
+
if (!glEnable || headlessMode) return;
|
|
4803
|
+
|
|
4701
4804
|
// clear and set to same size as main canvas
|
|
4702
4805
|
glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
|
|
4703
|
-
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
4806
|
+
//glContext.clear(gl_COLOR_BUFFER_BIT); // auto cleared when size is set
|
|
4704
4807
|
|
|
4705
4808
|
// set up the shader
|
|
4706
4809
|
glContext.useProgram(glShader);
|
|
@@ -4734,12 +4837,12 @@ function glPreRender()
|
|
|
4734
4837
|
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
4735
4838
|
const p = vec2(-1).subtract(cameraPos.multiply(s));
|
|
4736
4839
|
glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), false,
|
|
4737
|
-
|
|
4840
|
+
[
|
|
4738
4841
|
s.x, 0, 0, 0,
|
|
4739
4842
|
0, s.y, 0, 0,
|
|
4740
4843
|
1, 1, 1, 1,
|
|
4741
4844
|
p.x, p.y, 0, 0
|
|
4742
|
-
]
|
|
4845
|
+
]
|
|
4743
4846
|
);
|
|
4744
4847
|
}
|
|
4745
4848
|
|
|
@@ -4750,7 +4853,7 @@ function glPreRender()
|
|
|
4750
4853
|
function glSetTexture(texture)
|
|
4751
4854
|
{
|
|
4752
4855
|
// must flush cache with the old texture to set a new one
|
|
4753
|
-
if (texture == glActiveTexture)
|
|
4856
|
+
if (headlessMode || texture == glActiveTexture)
|
|
4754
4857
|
return;
|
|
4755
4858
|
|
|
4756
4859
|
glFlush();
|
|
@@ -4810,7 +4913,6 @@ function glCreateTexture(image)
|
|
|
4810
4913
|
const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
|
|
4811
4914
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
|
|
4812
4915
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
|
|
4813
|
-
|
|
4814
4916
|
return texture;
|
|
4815
4917
|
}
|
|
4816
4918
|
|
|
@@ -4834,12 +4936,12 @@ function glFlush()
|
|
|
4834
4936
|
}
|
|
4835
4937
|
|
|
4836
4938
|
/** Draw any sprites still in the buffer, copy to main canvas and clear
|
|
4837
|
-
* @param {CanvasRenderingContext2D} context
|
|
4939
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
|
4838
4940
|
* @param {Boolean} [forceDraw]
|
|
4839
4941
|
* @memberof WebGL */
|
|
4840
4942
|
function glCopyToContext(context, forceDraw=false)
|
|
4841
4943
|
{
|
|
4842
|
-
if (!glInstanceCount && !forceDraw) return;
|
|
4944
|
+
if (!glEnable || !glInstanceCount && !forceDraw) return;
|
|
4843
4945
|
|
|
4844
4946
|
glFlush();
|
|
4845
4947
|
|
|
@@ -4896,7 +4998,7 @@ let glPostShader, glPostTexture, glPostIncludeOverlay;
|
|
|
4896
4998
|
function glInitPostProcess(shaderCode, includeOverlay=false)
|
|
4897
4999
|
{
|
|
4898
5000
|
ASSERT(!glPostShader, 'can only have 1 post effects shader');
|
|
4899
|
-
|
|
5001
|
+
if (headlessMode) return;
|
|
4900
5002
|
if (!shaderCode) // default shader pass through
|
|
4901
5003
|
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
|
|
4902
5004
|
|
|
@@ -4935,8 +5037,7 @@ function glInitPostProcess(shaderCode, includeOverlay=false)
|
|
|
4935
5037
|
// Render the post processing shader, called automatically by the engine
|
|
4936
5038
|
function glRenderPostProcess()
|
|
4937
5039
|
{
|
|
4938
|
-
if (!glPostShader)
|
|
4939
|
-
return;
|
|
5040
|
+
if (!glPostShader || headlessMode) return;
|
|
4940
5041
|
|
|
4941
5042
|
// prepare to render post process shader
|
|
4942
5043
|
if (glEnable)
|
|
@@ -5042,7 +5143,7 @@ const engineName = 'LittleJS';
|
|
|
5042
5143
|
* @type {String}
|
|
5043
5144
|
* @default
|
|
5044
5145
|
* @memberof Engine */
|
|
5045
|
-
const engineVersion = '1.9.
|
|
5146
|
+
const engineVersion = '1.9.7';
|
|
5046
5147
|
|
|
5047
5148
|
/** Frames per second to update
|
|
5048
5149
|
* @type {Number}
|
|
@@ -5119,7 +5220,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5119
5220
|
mainContext.imageSmoothingEnabled = !canvasPixelated;
|
|
5120
5221
|
|
|
5121
5222
|
// setup gl rendering if enabled
|
|
5122
|
-
|
|
5223
|
+
glPreRender();
|
|
5123
5224
|
}
|
|
5124
5225
|
|
|
5125
5226
|
// internal update loop for engine
|
|
@@ -5138,11 +5239,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5138
5239
|
frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
|
|
5139
5240
|
if (!debugSpeedUp)
|
|
5140
5241
|
frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp in case of slow framerate
|
|
5242
|
+
|
|
5141
5243
|
updateCanvas();
|
|
5142
5244
|
|
|
5143
5245
|
if (paused)
|
|
5144
5246
|
{
|
|
5145
|
-
//
|
|
5247
|
+
// update object transforms even when paused
|
|
5248
|
+
for (const o of engineObjects)
|
|
5249
|
+
o.parent || o.updateTransforms();
|
|
5146
5250
|
inputUpdate();
|
|
5147
5251
|
debugUpdate();
|
|
5148
5252
|
gameUpdatePost();
|
|
@@ -5154,7 +5258,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5154
5258
|
let deltaSmooth = 0;
|
|
5155
5259
|
if (frameTimeBufferMS < 0 && frameTimeBufferMS > -9)
|
|
5156
5260
|
{
|
|
5157
|
-
// force
|
|
5261
|
+
// force at least one update each frame since it is waiting for refresh
|
|
5158
5262
|
deltaSmooth = frameTimeBufferMS;
|
|
5159
5263
|
frameTimeBufferMS = 0;
|
|
5160
5264
|
}
|
|
@@ -5179,34 +5283,37 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5179
5283
|
// add the time smoothing back in
|
|
5180
5284
|
frameTimeBufferMS += deltaSmooth;
|
|
5181
5285
|
}
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
enginePreRender();
|
|
5185
|
-
gameRender();
|
|
5186
|
-
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
5187
|
-
for (const o of engineObjects)
|
|
5188
|
-
o.destroyed || o.render();
|
|
5189
|
-
gameRenderPost();
|
|
5190
|
-
glRenderPostProcess();
|
|
5191
|
-
medalsRender();
|
|
5192
|
-
touchGamepadRender();
|
|
5193
|
-
debugRender();
|
|
5194
|
-
glEnable && glCopyToContext(mainContext);
|
|
5195
|
-
|
|
5196
|
-
if (showWatermark)
|
|
5286
|
+
|
|
5287
|
+
if (!headlessMode)
|
|
5197
5288
|
{
|
|
5198
|
-
//
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5289
|
+
// render sort then render while removing destroyed objects
|
|
5290
|
+
enginePreRender();
|
|
5291
|
+
gameRender();
|
|
5292
|
+
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
5293
|
+
for (const o of engineObjects)
|
|
5294
|
+
o.destroyed || o.render();
|
|
5295
|
+
gameRenderPost();
|
|
5296
|
+
glRenderPostProcess();
|
|
5297
|
+
medalsRender();
|
|
5298
|
+
touchGamepadRender();
|
|
5299
|
+
debugRender();
|
|
5300
|
+
glCopyToContext(mainContext);
|
|
5301
|
+
|
|
5302
|
+
if (showWatermark)
|
|
5303
|
+
{
|
|
5304
|
+
// update fps
|
|
5305
|
+
overlayContext.textAlign = 'right';
|
|
5306
|
+
overlayContext.textBaseline = 'top';
|
|
5307
|
+
overlayContext.font = '1em monospace';
|
|
5308
|
+
overlayContext.fillStyle = '#000';
|
|
5309
|
+
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
5310
|
+
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
5311
|
+
+ (glEnable ? ' GL' : ' 2D') ;
|
|
5312
|
+
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
5313
|
+
overlayContext.fillStyle = '#fff';
|
|
5314
|
+
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
5315
|
+
drawCount = 0;
|
|
5316
|
+
}
|
|
5210
5317
|
}
|
|
5211
5318
|
|
|
5212
5319
|
requestAnimationFrame(engineUpdate);
|
|
@@ -5214,6 +5321,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5214
5321
|
|
|
5215
5322
|
function updateCanvas()
|
|
5216
5323
|
{
|
|
5324
|
+
if (headlessMode) return;
|
|
5325
|
+
|
|
5217
5326
|
if (canvasFixedSize.x)
|
|
5218
5327
|
{
|
|
5219
5328
|
// clear canvas and set fixed size
|
|
@@ -5241,8 +5350,20 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5241
5350
|
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
5242
5351
|
}
|
|
5243
5352
|
|
|
5353
|
+
function startEngine()
|
|
5354
|
+
{
|
|
5355
|
+
gameInit();
|
|
5356
|
+
engineUpdate();
|
|
5357
|
+
}
|
|
5358
|
+
|
|
5359
|
+
if (headlessMode)
|
|
5360
|
+
{
|
|
5361
|
+
startEngine();
|
|
5362
|
+
return;
|
|
5363
|
+
}
|
|
5364
|
+
|
|
5244
5365
|
// setup html
|
|
5245
|
-
|
|
5366
|
+
const styleBody =
|
|
5246
5367
|
'margin:0;overflow:hidden;' + // fill the window
|
|
5247
5368
|
'background:#000;' + // set background color
|
|
5248
5369
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
@@ -5254,8 +5375,10 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5254
5375
|
mainContext = mainCanvas.getContext('2d');
|
|
5255
5376
|
|
|
5256
5377
|
// init stuff and start engine
|
|
5378
|
+
inputInit();
|
|
5379
|
+
audioInit();
|
|
5257
5380
|
debugInit();
|
|
5258
|
-
|
|
5381
|
+
glInit();
|
|
5259
5382
|
|
|
5260
5383
|
// create overlay canvas for hud to appear above gl canvas
|
|
5261
5384
|
document.body.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
@@ -5296,12 +5419,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5296
5419
|
}));
|
|
5297
5420
|
|
|
5298
5421
|
// load all of the images
|
|
5299
|
-
Promise.all(promises).then(
|
|
5300
|
-
{
|
|
5301
|
-
// start the engine
|
|
5302
|
-
gameInit();
|
|
5303
|
-
engineUpdate();
|
|
5304
|
-
});
|
|
5422
|
+
Promise.all(promises).then(startEngine);
|
|
5305
5423
|
}
|
|
5306
5424
|
|
|
5307
5425
|
/** Update each engine object, remove destroyed objects, and update time
|
|
@@ -5322,7 +5440,14 @@ function engineObjectsUpdate()
|
|
|
5322
5440
|
}
|
|
5323
5441
|
}
|
|
5324
5442
|
for (const o of engineObjects)
|
|
5325
|
-
|
|
5443
|
+
{
|
|
5444
|
+
// update top level objects
|
|
5445
|
+
if (!o.parent)
|
|
5446
|
+
{
|
|
5447
|
+
updateObject(o);
|
|
5448
|
+
o.updateTransforms();
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5326
5451
|
|
|
5327
5452
|
// remove destroyed objects
|
|
5328
5453
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
@@ -5608,6 +5733,7 @@ export {
|
|
|
5608
5733
|
canvasPixelated,
|
|
5609
5734
|
fontDefault,
|
|
5610
5735
|
showSplashScreen,
|
|
5736
|
+
headlessMode,
|
|
5611
5737
|
tileSizeDefault,
|
|
5612
5738
|
tileFixBleedScale,
|
|
5613
5739
|
enablePhysicsSolver,
|
|
@@ -5646,6 +5772,7 @@ export {
|
|
|
5646
5772
|
setCanvasPixelated,
|
|
5647
5773
|
setFontDefault,
|
|
5648
5774
|
setShowSplashScreen,
|
|
5775
|
+
setHeadlessMode,
|
|
5649
5776
|
setGlEnable,
|
|
5650
5777
|
setGlOverlay,
|
|
5651
5778
|
setTileSizeDefault,
|
|
@@ -5739,6 +5866,7 @@ export {
|
|
|
5739
5866
|
FontImage,
|
|
5740
5867
|
isFullscreen,
|
|
5741
5868
|
toggleFullscreen,
|
|
5869
|
+
getCameraSize,
|
|
5742
5870
|
|
|
5743
5871
|
// WebGL
|
|
5744
5872
|
glCanvas,
|