littlejsengine 1.9.4 → 1.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/littlejs.d.ts +73 -63
- package/dist/littlejs.esm.js +405 -245
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +403 -245
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +399 -240
- 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/gameEffects.js +2 -3
- 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/reference.md +11 -11
- package/src/engine.js +127 -71
- package/src/engineAudio.js +20 -16
- package/src/engineBuild.js +4 -4
- package/src/engineDebug.js +4 -5
- package/src/engineDraw.js +13 -10
- package/src/engineExport.js +2 -0
- package/src/engineInput.js +80 -64
- package/src/engineObject.js +27 -18
- package/src/engineParticles.js +14 -13
- package/src/engineSettings.js +13 -2
- package/src/engineTileLayer.js +20 -6
- package/src/engineUtilities.js +65 -22
- package/src/engineWebGL.js +20 -18
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()
|
|
@@ -357,7 +356,7 @@ function debugRender()
|
|
|
357
356
|
overlayContext.restore();
|
|
358
357
|
});
|
|
359
358
|
|
|
360
|
-
// remove expired
|
|
359
|
+
// remove expired primitives
|
|
361
360
|
debugPrimitives = debugPrimitives.filter(r=>r.time<0);
|
|
362
361
|
}
|
|
363
362
|
|
|
@@ -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
|
|
@@ -542,16 +541,57 @@ function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
|
|
|
542
541
|
function nearestPowerOfTwo(value) { return 2**Math.ceil(Math.log2(value)); }
|
|
543
542
|
|
|
544
543
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
545
|
-
* @param {Vector2}
|
|
546
|
-
* @param {Vector2} sizeA
|
|
547
|
-
* @param {Vector2}
|
|
548
|
-
* @param {Vector2} [sizeB=(0,0)]
|
|
549
|
-
* @return {Boolean}
|
|
544
|
+
* @param {Vector2} posA - Center of box A
|
|
545
|
+
* @param {Vector2} sizeA - Size of box A
|
|
546
|
+
* @param {Vector2} posB - Center of box B
|
|
547
|
+
* @param {Vector2} [sizeB=(0,0)] - Size of box B, a point if undefined
|
|
548
|
+
* @return {Boolean} - True if overlapping
|
|
550
549
|
* @memberof Utilities */
|
|
551
|
-
function isOverlapping(
|
|
550
|
+
function isOverlapping(posA, sizeA, posB, sizeB=vec2())
|
|
552
551
|
{
|
|
553
|
-
return abs(
|
|
554
|
-
&& abs(
|
|
552
|
+
return abs(posA.x - posB.x)*2 < sizeA.x + sizeB.x
|
|
553
|
+
&& abs(posA.y - posB.y)*2 < sizeA.y + sizeB.y;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/** Returns true if a line segment is intersecting an axis aligned box
|
|
557
|
+
* @param {Vector2} start - Start of raycast
|
|
558
|
+
* @param {Vector2} end - End of raycast
|
|
559
|
+
* @param {Vector2} pos - Center of box
|
|
560
|
+
* @param {Vector2} size - Size of box
|
|
561
|
+
* @return {Boolean} - True if intersecting
|
|
562
|
+
* @memberof Utilities */
|
|
563
|
+
function isIntersecting(start, end, pos, size)
|
|
564
|
+
{
|
|
565
|
+
// Liang-Barsky algorithm
|
|
566
|
+
const boxMin = pos.subtract(size.scale(.5));
|
|
567
|
+
const boxMax = boxMin.add(size);
|
|
568
|
+
const delta = end.subtract(start);
|
|
569
|
+
const a = start.subtract(boxMin);
|
|
570
|
+
const b = start.subtract(boxMax);
|
|
571
|
+
const p = [-delta.x, delta.x, -delta.y, delta.y];
|
|
572
|
+
const q = [a.x, -b.x, a.y, -b.y];
|
|
573
|
+
let tMin = 0, tMax = 1;
|
|
574
|
+
for (let i = 4; i--;)
|
|
575
|
+
{
|
|
576
|
+
if (p[i])
|
|
577
|
+
{
|
|
578
|
+
const t = q[i] / p[i];
|
|
579
|
+
if (p[i] < 0)
|
|
580
|
+
{
|
|
581
|
+
if (t > tMax) return false;
|
|
582
|
+
tMin = max(t, tMin);
|
|
583
|
+
}
|
|
584
|
+
else
|
|
585
|
+
{
|
|
586
|
+
if (t < tMin) return false;
|
|
587
|
+
tMax = min(t, tMax);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
else if (q[i] < 0)
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return true;
|
|
555
595
|
}
|
|
556
596
|
|
|
557
597
|
/** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
|
|
@@ -651,7 +691,7 @@ class RandomGenerator
|
|
|
651
691
|
this.seed ^= this.seed << 13;
|
|
652
692
|
this.seed ^= this.seed >>> 17;
|
|
653
693
|
this.seed ^= this.seed << 5;
|
|
654
|
-
return valueB + (valueA - valueB) * abs(this.seed %
|
|
694
|
+
return valueB + (valueA - valueB) * abs(this.seed % 1e8) / 1e8;
|
|
655
695
|
}
|
|
656
696
|
|
|
657
697
|
/** Returns a floored seeded random value the two values passed in
|
|
@@ -662,7 +702,7 @@ class RandomGenerator
|
|
|
662
702
|
|
|
663
703
|
/** Randomly returns either -1 or 1 deterministically
|
|
664
704
|
* @return {Number} */
|
|
665
|
-
sign() { return this.
|
|
705
|
+
sign() { return this.float() > .5 ? 1 : -1; }
|
|
666
706
|
}
|
|
667
707
|
|
|
668
708
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -710,6 +750,7 @@ class Vector2
|
|
|
710
750
|
* @param {Number} [y] - Y axis location */
|
|
711
751
|
constructor(x=0, y=0)
|
|
712
752
|
{
|
|
753
|
+
ASSERT(typeof x === 'number' && typeof y === 'number');
|
|
713
754
|
/** @property {Number} - X axis location */
|
|
714
755
|
this.x = x;
|
|
715
756
|
/** @property {Number} - Y axis location */
|
|
@@ -1036,12 +1077,14 @@ class Color
|
|
|
1036
1077
|
* @return {Color} */
|
|
1037
1078
|
setHSLA(h=0, s=0, l=1, a=1)
|
|
1038
1079
|
{
|
|
1080
|
+
h = mod(h,1);
|
|
1081
|
+
s = clamp(s);
|
|
1082
|
+
l = clamp(l);
|
|
1039
1083
|
const q = l < .5 ? l*(1+s) : l+s-l*s, p = 2*l-q,
|
|
1040
1084
|
f = (p, q, t)=>
|
|
1041
|
-
(t = (
|
|
1042
|
-
t < 1
|
|
1043
|
-
t < 2
|
|
1044
|
-
|
|
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;
|
|
1045
1088
|
this.r = f(p, q, h + 1/3);
|
|
1046
1089
|
this.g = f(p, q, h);
|
|
1047
1090
|
this.b = f(p, q, h - 1/3);
|
|
@@ -1092,13 +1135,12 @@ class Color
|
|
|
1092
1135
|
).clamp();
|
|
1093
1136
|
}
|
|
1094
1137
|
|
|
1095
|
-
/** Returns this color expressed as a
|
|
1138
|
+
/** Returns this color expressed as a rgb color code
|
|
1096
1139
|
* @param {Boolean} [useAlpha] - if alpha should be included in result
|
|
1097
1140
|
* @return {String} */
|
|
1098
|
-
toString(useAlpha = true)
|
|
1099
|
-
{
|
|
1100
|
-
|
|
1101
|
-
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
1141
|
+
toString(useAlpha = true)
|
|
1142
|
+
{
|
|
1143
|
+
return `rgb(${this.r*255},${this.g*255},${this.b*255},${useAlpha ? this.a : 0})`;
|
|
1102
1144
|
}
|
|
1103
1145
|
|
|
1104
1146
|
/** Set this color from a hex code
|
|
@@ -1156,11 +1198,11 @@ class Timer
|
|
|
1156
1198
|
|
|
1157
1199
|
/** Returns true if set and has not elapsed
|
|
1158
1200
|
* @return {Boolean} */
|
|
1159
|
-
active() { return time
|
|
1201
|
+
active() { return time < this.time; }
|
|
1160
1202
|
|
|
1161
1203
|
/** Returns true if set and elapsed
|
|
1162
1204
|
* @return {Boolean} */
|
|
1163
|
-
elapsed() { return time
|
|
1205
|
+
elapsed() { return time >= this.time; }
|
|
1164
1206
|
|
|
1165
1207
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
1166
1208
|
* @return {Number} */
|
|
@@ -1235,6 +1277,12 @@ let fontDefault = 'arial';
|
|
|
1235
1277
|
* @memberof Settings */
|
|
1236
1278
|
let showSplashScreen = false;
|
|
1237
1279
|
|
|
1280
|
+
/** Disables all rendering, audio, and input for servers
|
|
1281
|
+
* @type {Boolean}
|
|
1282
|
+
* @default
|
|
1283
|
+
* @memberof Settings */
|
|
1284
|
+
let headlessMode = false;
|
|
1285
|
+
|
|
1238
1286
|
///////////////////////////////////////////////////////////////////////////////
|
|
1239
1287
|
// WebGL settings
|
|
1240
1288
|
|
|
@@ -1263,7 +1311,7 @@ let tileSizeDefault = vec2(16);
|
|
|
1263
1311
|
* @type {Number}
|
|
1264
1312
|
* @default
|
|
1265
1313
|
* @memberof Settings */
|
|
1266
|
-
let tileFixBleedScale = .
|
|
1314
|
+
let tileFixBleedScale = .5;
|
|
1267
1315
|
|
|
1268
1316
|
///////////////////////////////////////////////////////////////////////////////
|
|
1269
1317
|
// Object settings
|
|
@@ -1388,7 +1436,7 @@ let soundEnable = true;
|
|
|
1388
1436
|
* @type {Number}
|
|
1389
1437
|
* @default
|
|
1390
1438
|
* @memberof Settings */
|
|
1391
|
-
let soundVolume = .
|
|
1439
|
+
let soundVolume = .3;
|
|
1392
1440
|
|
|
1393
1441
|
/** Default range where sound no longer plays
|
|
1394
1442
|
* @type {Number}
|
|
@@ -1473,6 +1521,11 @@ function setFontDefault(font) { fontDefault = font; }
|
|
|
1473
1521
|
* @memberof Settings */
|
|
1474
1522
|
function setShowSplashScreen(show) { showSplashScreen = show; }
|
|
1475
1523
|
|
|
1524
|
+
/** Set to disalbe rendering, audio, and input for servers
|
|
1525
|
+
* @param {Boolean} headless
|
|
1526
|
+
* @memberof Settings */
|
|
1527
|
+
function setHeadlessMode(headless) { headlessMode = headless; }
|
|
1528
|
+
|
|
1476
1529
|
/** Set if webgl rendering is enabled
|
|
1477
1530
|
* @param {Boolean} enable
|
|
1478
1531
|
* @memberof Settings */
|
|
@@ -1735,23 +1788,37 @@ class EngineObject
|
|
|
1735
1788
|
this.collideSolidObjects = false;
|
|
1736
1789
|
/** @property {Boolean} - Object collides with and blocks other objects */
|
|
1737
1790
|
this.isSolid = false;
|
|
1791
|
+
/** @property {Boolean} - Object collides with raycasts */
|
|
1792
|
+
this.collideRaycast = false;
|
|
1738
1793
|
|
|
1739
1794
|
// add to list of objects
|
|
1740
1795
|
engineObjects.push(this);
|
|
1741
1796
|
}
|
|
1742
1797
|
|
|
1743
|
-
/** Update the object transform
|
|
1744
|
-
|
|
1798
|
+
/** Update the object transform, called automatically by engine even when paused */
|
|
1799
|
+
updateTransforms()
|
|
1745
1800
|
{
|
|
1746
1801
|
const parent = this.parent;
|
|
1747
1802
|
if (parent)
|
|
1748
1803
|
{
|
|
1749
1804
|
// copy parent pos/angle
|
|
1750
|
-
|
|
1751
|
-
this.
|
|
1752
|
-
|
|
1805
|
+
const mirror = parent.getMirrorSign();
|
|
1806
|
+
this.pos = this.localPos.multiply(vec2(mirror,1)).rotate(-parent.angle).add(parent.pos);
|
|
1807
|
+
this.angle = mirror*this.localAngle + parent.angle;
|
|
1753
1808
|
}
|
|
1754
1809
|
|
|
1810
|
+
// update children
|
|
1811
|
+
for (const child of this.children)
|
|
1812
|
+
child.updateTransforms();
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
/** Update the object physics, called automatically by engine once each frame */
|
|
1816
|
+
update()
|
|
1817
|
+
{
|
|
1818
|
+
// child objects do not have physics
|
|
1819
|
+
if (this.parent)
|
|
1820
|
+
return;
|
|
1821
|
+
|
|
1755
1822
|
// limit max speed to prevent missing collisions
|
|
1756
1823
|
this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
|
|
1757
1824
|
this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
|
|
@@ -1766,8 +1833,7 @@ class EngineObject
|
|
|
1766
1833
|
// physics sanity checks
|
|
1767
1834
|
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
1768
1835
|
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
1769
|
-
|
|
1770
|
-
if (!enablePhysicsSolver || !this.mass) // do not update collision for fixed objects
|
|
1836
|
+
if (!enablePhysicsSolver || !this.mass) // dont do collision for fixed objects
|
|
1771
1837
|
return;
|
|
1772
1838
|
|
|
1773
1839
|
const wasMovingDown = this.velocity.y < 0;
|
|
@@ -1939,13 +2005,7 @@ class EngineObject
|
|
|
1939
2005
|
* @param {Number} tileData - the value of the tile at the position
|
|
1940
2006
|
* @param {Vector2} pos - tile where the collision occured
|
|
1941
2007
|
* @return {Boolean} - true if the collision should be resolved */
|
|
1942
|
-
collideWithTile(tileData, pos)
|
|
1943
|
-
|
|
1944
|
-
/** Called to check if a tile raycast hit
|
|
1945
|
-
* @param {Number} tileData - the value of the tile at the position
|
|
1946
|
-
* @param {Vector2} pos - tile where the raycast is
|
|
1947
|
-
* @return {Boolean} - true if the raycast should hit */
|
|
1948
|
-
collideWithTileRaycast(tileData, pos) { return tileData > 0; }
|
|
2008
|
+
collideWithTile(tileData, pos) { return tileData > 0; }
|
|
1949
2009
|
|
|
1950
2010
|
/** Called to check if a object collision should be resolved
|
|
1951
2011
|
* @param {EngineObject} object - the object to test against
|
|
@@ -1992,16 +2052,18 @@ class EngineObject
|
|
|
1992
2052
|
}
|
|
1993
2053
|
|
|
1994
2054
|
/** Set how this object collides
|
|
1995
|
-
* @param {Boolean} [collideSolidObjects] - Does it collide with solid objects
|
|
1996
|
-
* @param {Boolean} [isSolid] - Does it collide with and block other objects (expensive in large numbers)
|
|
1997
|
-
* @param {Boolean} [collideTiles] - Does it collide with the tile collision
|
|
1998
|
-
|
|
2055
|
+
* @param {Boolean} [collideSolidObjects] - Does it collide with solid objects?
|
|
2056
|
+
* @param {Boolean} [isSolid] - Does it collide with and block other objects? (expensive in large numbers)
|
|
2057
|
+
* @param {Boolean} [collideTiles] - Does it collide with the tile collision?
|
|
2058
|
+
* @param {Boolean} [collideRaycast] - Does it collide with raycasts? */
|
|
2059
|
+
setCollision(collideSolidObjects=true, isSolid=true, collideTiles=true, collideRaycast=true)
|
|
1999
2060
|
{
|
|
2000
2061
|
ASSERT(collideSolidObjects || !isSolid, 'solid objects must be set to collide');
|
|
2001
2062
|
|
|
2002
2063
|
this.collideSolidObjects = collideSolidObjects;
|
|
2003
2064
|
this.isSolid = isSolid;
|
|
2004
2065
|
this.collideTiles = collideTiles;
|
|
2066
|
+
this.collideRaycast = collideRaycast;
|
|
2005
2067
|
}
|
|
2006
2068
|
|
|
2007
2069
|
/** Returns string containg info about this object for debugging
|
|
@@ -2101,6 +2163,9 @@ let drawCount;
|
|
|
2101
2163
|
*/
|
|
2102
2164
|
function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
2103
2165
|
{
|
|
2166
|
+
if (headlessMode)
|
|
2167
|
+
return new TileInfo;
|
|
2168
|
+
|
|
2104
2169
|
// if size is a number, make it a vector
|
|
2105
2170
|
if (typeof size === 'number')
|
|
2106
2171
|
{
|
|
@@ -2134,9 +2199,9 @@ class TileInfo
|
|
|
2134
2199
|
constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
2135
2200
|
{
|
|
2136
2201
|
/** @property {Vector2} - Top left corner of tile in pixels */
|
|
2137
|
-
this.pos = pos;
|
|
2202
|
+
this.pos = pos.copy();
|
|
2138
2203
|
/** @property {Vector2} - Size of tile in pixels */
|
|
2139
|
-
this.size = size;
|
|
2204
|
+
this.size = size.copy();
|
|
2140
2205
|
/** @property {Number} - Texture index to use */
|
|
2141
2206
|
this.textureIndex = textureIndex;
|
|
2142
2207
|
}
|
|
@@ -2228,7 +2293,7 @@ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
|
2228
2293
|
* @param {Color} [additiveColor=(0,0,0,0)] - Additive color to be applied
|
|
2229
2294
|
* @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
2230
2295
|
* @param {Boolean} [screenSpace=false] - If true the pos and size are in screen space
|
|
2231
|
-
* @param {CanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
2296
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
2232
2297
|
* @memberof Draw */
|
|
2233
2298
|
function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
2234
2299
|
angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace, context)
|
|
@@ -2301,7 +2366,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
2301
2366
|
* @param {Number} [angle]
|
|
2302
2367
|
* @param {Boolean} [useWebGL=glEnable]
|
|
2303
2368
|
* @param {Boolean} [screenSpace=false]
|
|
2304
|
-
* @param {CanvasRenderingContext2D} [context]
|
|
2369
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
2305
2370
|
* @memberof Draw */
|
|
2306
2371
|
function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
2307
2372
|
{
|
|
@@ -2315,7 +2380,7 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
2315
2380
|
* @param {Color} [color=(1,1,1,1)]
|
|
2316
2381
|
* @param {Boolean} [useWebGL=glEnable]
|
|
2317
2382
|
* @param {Boolean} [screenSpace=false]
|
|
2318
|
-
* @param {CanvasRenderingContext2D} [context]
|
|
2383
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
2319
2384
|
* @memberof Draw */
|
|
2320
2385
|
function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
|
|
2321
2386
|
{
|
|
@@ -2331,7 +2396,7 @@ function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, contex
|
|
|
2331
2396
|
* @param {Boolean} mirror
|
|
2332
2397
|
* @param {Function} drawFunction
|
|
2333
2398
|
* @param {Boolean} [screenSpace=false]
|
|
2334
|
-
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
2399
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
|
|
2335
2400
|
* @memberof Draw */
|
|
2336
2401
|
function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, context=mainContext)
|
|
2337
2402
|
{
|
|
@@ -2352,7 +2417,7 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
|
|
|
2352
2417
|
/** Enable normal or additive blend mode
|
|
2353
2418
|
* @param {Boolean} [additive]
|
|
2354
2419
|
* @param {Boolean} [useWebGL=glEnable]
|
|
2355
|
-
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
2420
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
|
|
2356
2421
|
* @memberof Draw */
|
|
2357
2422
|
function setBlendMode(additive, useWebGL=glEnable, context)
|
|
2358
2423
|
{
|
|
@@ -2377,7 +2442,7 @@ function setBlendMode(additive, useWebGL=glEnable, context)
|
|
|
2377
2442
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
2378
2443
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
2379
2444
|
* @param {String} [font=fontDefault]
|
|
2380
|
-
* @param {CanvasRenderingContext2D} [context=overlayContext]
|
|
2445
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
2381
2446
|
* @memberof Draw */
|
|
2382
2447
|
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context)
|
|
2383
2448
|
{
|
|
@@ -2394,7 +2459,7 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
2394
2459
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
2395
2460
|
* @param {CanvasTextAlign} [textAlign]
|
|
2396
2461
|
* @param {String} [font=fontDefault]
|
|
2397
|
-
* @param {CanvasRenderingContext2D} [context=overlayContext]
|
|
2462
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
2398
2463
|
* @memberof Draw */
|
|
2399
2464
|
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
|
|
2400
2465
|
{
|
|
@@ -2437,7 +2502,7 @@ class FontImage
|
|
|
2437
2502
|
* @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
|
|
2438
2503
|
* @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
|
|
2439
2504
|
* @param {Vector2} [paddingSize=(0,1)] - How much extra space to add between characters
|
|
2440
|
-
* @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
2505
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
2441
2506
|
*/
|
|
2442
2507
|
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), context=overlayContext)
|
|
2443
2508
|
{
|
|
@@ -2645,7 +2710,7 @@ function gamepadWasReleased(button, gamepad=0)
|
|
|
2645
2710
|
* @return {Vector2}
|
|
2646
2711
|
* @memberof Input */
|
|
2647
2712
|
function gamepadStick(stick, gamepad=0)
|
|
2648
|
-
{ return
|
|
2713
|
+
{ return gamepadStickData[gamepad] ? gamepadStickData[gamepad][stick] || vec2() : vec2(); }
|
|
2649
2714
|
|
|
2650
2715
|
///////////////////////////////////////////////////////////////////////////////
|
|
2651
2716
|
// Input update called by engine
|
|
@@ -2656,6 +2721,8 @@ let inputData = [[]];
|
|
|
2656
2721
|
|
|
2657
2722
|
function inputUpdate()
|
|
2658
2723
|
{
|
|
2724
|
+
if (headlessMode) return;
|
|
2725
|
+
|
|
2659
2726
|
// clear input when lost focus (prevent stuck keys)
|
|
2660
2727
|
isTouchDevice || document.hasFocus() || clearInput();
|
|
2661
2728
|
|
|
@@ -2668,6 +2735,8 @@ function inputUpdate()
|
|
|
2668
2735
|
|
|
2669
2736
|
function inputUpdatePost()
|
|
2670
2737
|
{
|
|
2738
|
+
if (headlessMode) return;
|
|
2739
|
+
|
|
2671
2740
|
// clear input to prepare for next frame
|
|
2672
2741
|
for (const deviceInputData of inputData)
|
|
2673
2742
|
for (const i in deviceInputData)
|
|
@@ -2676,9 +2745,12 @@ function inputUpdatePost()
|
|
|
2676
2745
|
}
|
|
2677
2746
|
|
|
2678
2747
|
///////////////////////////////////////////////////////////////////////////////
|
|
2679
|
-
//
|
|
2748
|
+
// Input event handlers
|
|
2680
2749
|
|
|
2750
|
+
function inputInit()
|
|
2681
2751
|
{
|
|
2752
|
+
if (headlessMode) return;
|
|
2753
|
+
|
|
2682
2754
|
onkeydown = (e)=>
|
|
2683
2755
|
{
|
|
2684
2756
|
if (debug && e.target != document.body) return;
|
|
@@ -2709,21 +2781,29 @@ function inputUpdatePost()
|
|
|
2709
2781
|
c == 'KeyA' ? 'ArrowLeft' :
|
|
2710
2782
|
c == 'KeyD' ? 'ArrowRight' : c : c;
|
|
2711
2783
|
}
|
|
2784
|
+
|
|
2785
|
+
// mouse event handlers
|
|
2786
|
+
onmousedown = (e)=>
|
|
2787
|
+
{
|
|
2788
|
+
isUsingGamepad = false;
|
|
2789
|
+
inputData[0][e.button] = 3;
|
|
2790
|
+
mousePosScreen = mouseToScreen(e);
|
|
2791
|
+
e.button && e.preventDefault();
|
|
2792
|
+
}
|
|
2793
|
+
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
2794
|
+
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
2795
|
+
onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
|
|
2796
|
+
oncontextmenu = (e)=> false; // prevent right click menu
|
|
2797
|
+
|
|
2798
|
+
// init touch input
|
|
2799
|
+
if (isTouchDevice)
|
|
2800
|
+
touchInputInit();
|
|
2712
2801
|
}
|
|
2713
2802
|
|
|
2714
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2715
|
-
// Mouse event handlers
|
|
2716
|
-
|
|
2717
|
-
onmousedown = (e)=> {isUsingGamepad = false; inputData[0][e.button] = 3; mousePosScreen = mouseToScreen(e); e.button && e.preventDefault();}
|
|
2718
|
-
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
2719
|
-
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
2720
|
-
onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
|
|
2721
|
-
oncontextmenu = (e)=> false; // prevent right click menu
|
|
2722
|
-
|
|
2723
2803
|
// convert a mouse or touch event position to screen space
|
|
2724
2804
|
function mouseToScreen(mousePos)
|
|
2725
2805
|
{
|
|
2726
|
-
if (!mainCanvas)
|
|
2806
|
+
if (!mainCanvas || headlessMode)
|
|
2727
2807
|
return vec2(); // fix bug that can occur if user clicks before page loads
|
|
2728
2808
|
|
|
2729
2809
|
const rect = mainCanvas.getBoundingClientRect();
|
|
@@ -2735,7 +2815,7 @@ function mouseToScreen(mousePos)
|
|
|
2735
2815
|
// Gamepad input
|
|
2736
2816
|
|
|
2737
2817
|
// gamepad internal variables
|
|
2738
|
-
const
|
|
2818
|
+
const gamepadStickData = [];
|
|
2739
2819
|
|
|
2740
2820
|
// gamepads are updated by engine every frame automatically
|
|
2741
2821
|
function gamepadsUpdate()
|
|
@@ -2752,14 +2832,11 @@ function gamepadsUpdate()
|
|
|
2752
2832
|
// update touch gamepad if enabled
|
|
2753
2833
|
if (touchGamepadEnable && isTouchDevice)
|
|
2754
2834
|
{
|
|
2755
|
-
|
|
2756
|
-
if (!touchGamepadButtons)
|
|
2757
|
-
createTouchGamepad();
|
|
2758
|
-
|
|
2835
|
+
ASSERT(touchGamepadButtons, 'set touchGamepadEnable before calling init!');
|
|
2759
2836
|
if (touchGamepadTimer.isSet())
|
|
2760
2837
|
{
|
|
2761
2838
|
// read virtual analog stick
|
|
2762
|
-
const sticks =
|
|
2839
|
+
const sticks = gamepadStickData[0] || (gamepadStickData[0] = []);
|
|
2763
2840
|
sticks[0] = vec2();
|
|
2764
2841
|
if (touchGamepadAnalog)
|
|
2765
2842
|
sticks[0] = applyDeadZones(touchGamepadStick);
|
|
@@ -2776,7 +2853,8 @@ function gamepadsUpdate()
|
|
|
2776
2853
|
for (let i=10; i--;)
|
|
2777
2854
|
{
|
|
2778
2855
|
const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
|
|
2779
|
-
|
|
2856
|
+
const wasDown = gamepadIsDown(j,0);
|
|
2857
|
+
data[j] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
2780
2858
|
}
|
|
2781
2859
|
}
|
|
2782
2860
|
}
|
|
@@ -2796,7 +2874,7 @@ function gamepadsUpdate()
|
|
|
2796
2874
|
// get or create gamepad data
|
|
2797
2875
|
const gamepad = gamepads[i];
|
|
2798
2876
|
const data = inputData[i+1] || (inputData[i+1] = []);
|
|
2799
|
-
const sticks =
|
|
2877
|
+
const sticks = gamepadStickData[i] || (gamepadStickData[i] = []);
|
|
2800
2878
|
|
|
2801
2879
|
if (gamepad)
|
|
2802
2880
|
{
|
|
@@ -2835,28 +2913,44 @@ function gamepadsUpdate()
|
|
|
2835
2913
|
* @param {Number|Array} [pattern] - single value in ms or vibration interval array
|
|
2836
2914
|
* @memberof Input */
|
|
2837
2915
|
function vibrate(pattern=100)
|
|
2838
|
-
{ vibrateEnable && navigator && navigator.vibrate && navigator.vibrate(pattern); }
|
|
2916
|
+
{ vibrateEnable && !headlessMode && navigator && navigator.vibrate && navigator.vibrate(pattern); }
|
|
2839
2917
|
|
|
2840
2918
|
/** Cancel any ongoing vibration
|
|
2841
2919
|
* @memberof Input */
|
|
2842
2920
|
function vibrateStop() { vibrate(0); }
|
|
2843
2921
|
|
|
2844
2922
|
///////////////////////////////////////////////////////////////////////////////
|
|
2845
|
-
// Touch input
|
|
2923
|
+
// Touch input & virtual on screen gamepad
|
|
2846
2924
|
|
|
2847
2925
|
/** True if a touch device has been detected
|
|
2848
2926
|
* @memberof Input */
|
|
2849
|
-
const isTouchDevice = window.ontouchstart !== undefined;
|
|
2927
|
+
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
2928
|
+
|
|
2929
|
+
// touch gamepad internal variables
|
|
2930
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
2850
2931
|
|
|
2851
2932
|
// try to enable touch mouse
|
|
2852
|
-
|
|
2933
|
+
function touchInputInit()
|
|
2853
2934
|
{
|
|
2935
|
+
// add non passive touch event listeners
|
|
2936
|
+
let handleTouch = handleTouchDefault;
|
|
2937
|
+
if (touchGamepadEnable)
|
|
2938
|
+
{
|
|
2939
|
+
// touch input internal variables
|
|
2940
|
+
handleTouch = handleTouchGamepad;
|
|
2941
|
+
touchGamepadButtons = [];
|
|
2942
|
+
touchGamepadStick = vec2();
|
|
2943
|
+
}
|
|
2944
|
+
document.addEventListener('touchstart', (e) => handleTouch(e), { passive: false });
|
|
2945
|
+
document.addEventListener('touchmove', (e) => handleTouch(e), { passive: false });
|
|
2946
|
+
document.addEventListener('touchend', (e) => handleTouch(e), { passive: false });
|
|
2947
|
+
|
|
2854
2948
|
// override mouse events
|
|
2855
|
-
let wasTouching;
|
|
2856
2949
|
onmousedown = onmouseup = ()=> 0;
|
|
2857
2950
|
|
|
2858
2951
|
// handle all touch events the same way
|
|
2859
|
-
|
|
2952
|
+
let wasTouching;
|
|
2953
|
+
function handleTouchDefault(e)
|
|
2860
2954
|
{
|
|
2861
2955
|
// fix stalled audio requiring user interaction
|
|
2862
2956
|
if (soundEnable && audioContext && audioContext.state != 'running')
|
|
@@ -2885,27 +2979,14 @@ if (isTouchDevice)
|
|
|
2885
2979
|
// must return true so the document will get focus
|
|
2886
2980
|
return true;
|
|
2887
2981
|
}
|
|
2888
|
-
}
|
|
2889
|
-
|
|
2890
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2891
|
-
// touch gamepad, virtual on screen gamepad emulator for touch devices
|
|
2892
|
-
|
|
2893
|
-
// touch input internal variables
|
|
2894
|
-
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
2895
|
-
|
|
2896
|
-
// create the touch gamepad, called automatically by the engine
|
|
2897
|
-
function createTouchGamepad()
|
|
2898
|
-
{
|
|
2899
|
-
// touch input internal variables
|
|
2900
|
-
touchGamepadButtons = [];
|
|
2901
|
-
touchGamepadStick = vec2();
|
|
2902
2982
|
|
|
2903
|
-
|
|
2904
|
-
|
|
2983
|
+
// special handling for virtual gamepad mode
|
|
2984
|
+
function handleTouchGamepad(e)
|
|
2905
2985
|
{
|
|
2906
2986
|
// clear touch gamepad input
|
|
2907
2987
|
touchGamepadStick = vec2();
|
|
2908
2988
|
touchGamepadButtons = [];
|
|
2989
|
+
isUsingGamepad = true;
|
|
2909
2990
|
|
|
2910
2991
|
const touching = e.touches.length;
|
|
2911
2992
|
if (touching)
|
|
@@ -2946,9 +3027,8 @@ function createTouchGamepad()
|
|
|
2946
3027
|
}
|
|
2947
3028
|
}
|
|
2948
3029
|
|
|
2949
|
-
// call default touch handler
|
|
2950
|
-
|
|
2951
|
-
isUsingGamepad = true;
|
|
3030
|
+
// call default touch handler so normal touch events still work
|
|
3031
|
+
handleTouchDefault(e);
|
|
2952
3032
|
|
|
2953
3033
|
// must return true so the document will get focus
|
|
2954
3034
|
return true;
|
|
@@ -2967,32 +3047,33 @@ function touchGamepadRender()
|
|
|
2967
3047
|
return;
|
|
2968
3048
|
|
|
2969
3049
|
// setup the canvas
|
|
2970
|
-
overlayContext
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
3050
|
+
const context = overlayContext;
|
|
3051
|
+
context.save();
|
|
3052
|
+
context.globalAlpha = alpha*touchGamepadAlpha;
|
|
3053
|
+
context.strokeStyle = '#fff';
|
|
3054
|
+
context.lineWidth = 3;
|
|
2974
3055
|
|
|
2975
3056
|
// draw left analog stick
|
|
2976
|
-
|
|
2977
|
-
|
|
3057
|
+
context.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
|
|
3058
|
+
context.beginPath();
|
|
2978
3059
|
|
|
2979
3060
|
const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2980
3061
|
if (touchGamepadAnalog) // draw circle shaped gamepad
|
|
2981
3062
|
{
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
3063
|
+
context.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
|
|
3064
|
+
context.fill();
|
|
3065
|
+
context.stroke();
|
|
2985
3066
|
}
|
|
2986
3067
|
else // draw cross shaped gamepad
|
|
2987
3068
|
{
|
|
2988
3069
|
for(let i=10; i--;)
|
|
2989
3070
|
{
|
|
2990
3071
|
const angle = i*PI/4;
|
|
2991
|
-
|
|
2992
|
-
i%2 &&
|
|
2993
|
-
i==1 &&
|
|
3072
|
+
context.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
|
|
3073
|
+
i%2 && context.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
|
|
3074
|
+
i==1 && context.fill();
|
|
2994
3075
|
}
|
|
2995
|
-
|
|
3076
|
+
context.stroke();
|
|
2996
3077
|
}
|
|
2997
3078
|
|
|
2998
3079
|
// draw right face buttons
|
|
@@ -3000,15 +3081,15 @@ function touchGamepadRender()
|
|
|
3000
3081
|
for (let i=4; i--;)
|
|
3001
3082
|
{
|
|
3002
3083
|
const pos = rightCenter.add(vec2().setDirection(i, touchGamepadSize/2));
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3084
|
+
context.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
|
|
3085
|
+
context.beginPath();
|
|
3086
|
+
context.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
|
|
3087
|
+
context.fill();
|
|
3088
|
+
context.stroke();
|
|
3008
3089
|
}
|
|
3009
3090
|
|
|
3010
3091
|
// set canvas back to normal
|
|
3011
|
-
|
|
3092
|
+
context.restore();
|
|
3012
3093
|
}
|
|
3013
3094
|
/**
|
|
3014
3095
|
* LittleJS Audio System
|
|
@@ -3043,7 +3124,7 @@ class Sound
|
|
|
3043
3124
|
*/
|
|
3044
3125
|
constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
|
|
3045
3126
|
{
|
|
3046
|
-
if (!soundEnable) return;
|
|
3127
|
+
if (!soundEnable || headlessMode) return;
|
|
3047
3128
|
|
|
3048
3129
|
/** @property {Number} - World space max range of sound, will not play if camera is farther away */
|
|
3049
3130
|
this.range = range;
|
|
@@ -3057,8 +3138,8 @@ class Sound
|
|
|
3057
3138
|
if (zzfxSound)
|
|
3058
3139
|
{
|
|
3059
3140
|
// generate zzfx sound now for fast playback
|
|
3060
|
-
|
|
3061
|
-
zzfxSound[1]
|
|
3141
|
+
const defaultRandomness = .05;
|
|
3142
|
+
this.randomness = zzfxSound[1] || defaultRandomness;
|
|
3062
3143
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
3063
3144
|
this.sampleRate = zzfxR;
|
|
3064
3145
|
}
|
|
@@ -3074,7 +3155,7 @@ class Sound
|
|
|
3074
3155
|
*/
|
|
3075
3156
|
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false)
|
|
3076
3157
|
{
|
|
3077
|
-
if (!soundEnable || !this.sampleChannels) return;
|
|
3158
|
+
if (!soundEnable || !this.sampleChannels || headlessMode) return;
|
|
3078
3159
|
|
|
3079
3160
|
let pan;
|
|
3080
3161
|
if (pos)
|
|
@@ -3157,7 +3238,9 @@ class SoundWave extends Sound
|
|
|
3157
3238
|
super(undefined, range, taper);
|
|
3158
3239
|
this.randomness = randomness;
|
|
3159
3240
|
|
|
3160
|
-
if (!soundEnable) return;
|
|
3241
|
+
if (!soundEnable || headlessMode) return;
|
|
3242
|
+
if (!audioContext)
|
|
3243
|
+
audioContext = new AudioContext; // create audio context
|
|
3161
3244
|
|
|
3162
3245
|
fetch(filename)
|
|
3163
3246
|
.then(response => response.arrayBuffer())
|
|
@@ -3211,7 +3294,7 @@ class Music extends Sound
|
|
|
3211
3294
|
{
|
|
3212
3295
|
super(undefined);
|
|
3213
3296
|
|
|
3214
|
-
if (!soundEnable) return;
|
|
3297
|
+
if (!soundEnable || headlessMode) return;
|
|
3215
3298
|
this.randomness = 0;
|
|
3216
3299
|
this.sampleChannels = zzfxM(...zzfxMusic);
|
|
3217
3300
|
this.sampleRate = zzfxR;
|
|
@@ -3234,7 +3317,7 @@ class Music extends Sound
|
|
|
3234
3317
|
* @memberof Audio */
|
|
3235
3318
|
function playAudioFile(filename, volume=1, loop=false)
|
|
3236
3319
|
{
|
|
3237
|
-
if (!soundEnable) return;
|
|
3320
|
+
if (!soundEnable || headlessMode) return;
|
|
3238
3321
|
|
|
3239
3322
|
const audio = new Audio(filename);
|
|
3240
3323
|
audio.volume = soundVolume * volume;
|
|
@@ -3253,7 +3336,7 @@ function playAudioFile(filename, volume=1, loop=false)
|
|
|
3253
3336
|
* @memberof Audio */
|
|
3254
3337
|
function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
3255
3338
|
{
|
|
3256
|
-
if (!soundEnable || !speechSynthesis) return;
|
|
3339
|
+
if (!soundEnable || !speechSynthesis || headlessMode) return;
|
|
3257
3340
|
|
|
3258
3341
|
// common languages (not supported by all browsers)
|
|
3259
3342
|
// en - english, it - italian, fr - french, de - german, es - spanish
|
|
@@ -3286,7 +3369,7 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
3286
3369
|
/** Audio context used by the engine
|
|
3287
3370
|
* @type {AudioContext}
|
|
3288
3371
|
* @memberof Audio */
|
|
3289
|
-
let audioContext
|
|
3372
|
+
let audioContext;
|
|
3290
3373
|
|
|
3291
3374
|
/** Keep track if audio was suspended when last sound was played
|
|
3292
3375
|
* @type {Boolean}
|
|
@@ -3304,7 +3387,9 @@ let audioSuspended = false;
|
|
|
3304
3387
|
* @memberof Audio */
|
|
3305
3388
|
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR)
|
|
3306
3389
|
{
|
|
3307
|
-
if (!soundEnable) return;
|
|
3390
|
+
if (!soundEnable || headlessMode) return;
|
|
3391
|
+
if (!audioContext)
|
|
3392
|
+
audioContext = new AudioContext; // create audio context
|
|
3308
3393
|
|
|
3309
3394
|
// prevent sounds from building up if they can't be played
|
|
3310
3395
|
const audioWasSuspended = audioSuspended;
|
|
@@ -3350,7 +3435,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3350
3435
|
* @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
|
|
3351
3436
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
3352
3437
|
* @memberof Audio */
|
|
3353
|
-
function zzfx(...zzfxSound) { return
|
|
3438
|
+
function zzfx(...zzfxSound) { return new Sound(zzfxSound).play(); }
|
|
3354
3439
|
|
|
3355
3440
|
/** Sample rate used for all ZzFX sounds
|
|
3356
3441
|
* @default 44100
|
|
@@ -3359,7 +3444,7 @@ const zzfxR = 44100;
|
|
|
3359
3444
|
|
|
3360
3445
|
/** Generate samples for a ZzFX sound
|
|
3361
3446
|
* @param {Number} [volume] - Volume scale (percent)
|
|
3362
|
-
* @param {Number} [randomness] -
|
|
3447
|
+
* @param {Number} [randomness] - Unused in this fuction, handled by Sound class
|
|
3363
3448
|
* @param {Number} [frequency] - Frequency of sound (Hz)
|
|
3364
3449
|
* @param {Number} [attack] - Attack time, how fast sound starts (seconds)
|
|
3365
3450
|
* @param {Number} [sustain] - Sustain time, how long sound holds (seconds)
|
|
@@ -3385,17 +3470,18 @@ const zzfxR = 44100;
|
|
|
3385
3470
|
function zzfxG
|
|
3386
3471
|
(
|
|
3387
3472
|
// parameters
|
|
3388
|
-
volume = 1, randomness =
|
|
3473
|
+
volume = 1, randomness = 0, frequency = 220, attack = 0, sustain = 0,
|
|
3389
3474
|
release = .1, shape = 0, shapeCurve = 1, slide = 0, deltaSlide = 0,
|
|
3390
3475
|
pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, modulation = 0,
|
|
3391
3476
|
bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0, filter = 0
|
|
3392
3477
|
)
|
|
3393
3478
|
{
|
|
3479
|
+
// LJS Note: ZZFX modded so randomness is handled by Sound class
|
|
3480
|
+
|
|
3394
3481
|
// init parameters
|
|
3395
3482
|
let PI2 = PI*2, sampleRate = zzfxR,
|
|
3396
3483
|
startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
|
|
3397
|
-
startFrequency = frequency *=
|
|
3398
|
-
rand(1 + randomness, 1-randomness) * PI2 / sampleRate,
|
|
3484
|
+
startFrequency = frequency *= PI2 / sampleRate,
|
|
3399
3485
|
b = [], t = 0, tm = 0, i = 0, j = 1, r = 0, c = 0, s = 0, f, length,
|
|
3400
3486
|
|
|
3401
3487
|
// biquad LP/HP filter
|
|
@@ -3417,7 +3503,6 @@ function zzfxG
|
|
|
3417
3503
|
pitchJump *= PI2 / sampleRate;
|
|
3418
3504
|
pitchJumpTime *= sampleRate;
|
|
3419
3505
|
repeatTime = repeatTime * sampleRate | 0;
|
|
3420
|
-
volume *= soundVolume;
|
|
3421
3506
|
|
|
3422
3507
|
// generate waveform
|
|
3423
3508
|
for(length = attack + decay + sustain + release + delay | 0;
|
|
@@ -3654,7 +3739,7 @@ function tileCollisionTest(pos, size=vec2(), object)
|
|
|
3654
3739
|
}
|
|
3655
3740
|
}
|
|
3656
3741
|
|
|
3657
|
-
/** Return the center of tile
|
|
3742
|
+
/** Return the center of first tile hit (does not return the exact intersection)
|
|
3658
3743
|
* @param {Vector2} posStart
|
|
3659
3744
|
* @param {Vector2} posEnd
|
|
3660
3745
|
* @param {EngineObject} [object]
|
|
@@ -3763,7 +3848,7 @@ class TileLayer extends EngineObject
|
|
|
3763
3848
|
|
|
3764
3849
|
/** @property {HTMLCanvasElement} - The canvas used by this tile layer */
|
|
3765
3850
|
this.canvas = document.createElement('canvas');
|
|
3766
|
-
/** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
3851
|
+
/** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
3767
3852
|
this.context = this.canvas.getContext('2d');
|
|
3768
3853
|
/** @property {Vector2} - How much to scale this layer when rendered */
|
|
3769
3854
|
this.scale = scale;
|
|
@@ -3774,6 +3859,17 @@ class TileLayer extends EngineObject
|
|
|
3774
3859
|
this.data = [];
|
|
3775
3860
|
for (let j = this.size.area(); j--;)
|
|
3776
3861
|
this.data.push(new TileLayerData);
|
|
3862
|
+
|
|
3863
|
+
if (headlessMode)
|
|
3864
|
+
{
|
|
3865
|
+
// disable rendering
|
|
3866
|
+
this.redraw = () => {};
|
|
3867
|
+
this.render = () => {};
|
|
3868
|
+
this.redrawStart = () => {};
|
|
3869
|
+
this.redrawEnd = () => {};
|
|
3870
|
+
this.drawTileData = () => {};
|
|
3871
|
+
this.drawCanvas2D = () => {};
|
|
3872
|
+
}
|
|
3777
3873
|
}
|
|
3778
3874
|
|
|
3779
3875
|
/** Set data at a given position in the array
|
|
@@ -3804,7 +3900,7 @@ class TileLayer extends EngineObject
|
|
|
3804
3900
|
ASSERT(mainContext != this.context, 'must call redrawEnd() after drawing tiles');
|
|
3805
3901
|
|
|
3806
3902
|
// flush and copy gl canvas because tile canvas does not use webgl
|
|
3807
|
-
|
|
3903
|
+
!glOverlay && !this.isOverlay && glCopyToContext(mainContext);
|
|
3808
3904
|
|
|
3809
3905
|
// draw the entire cached level onto the canvas
|
|
3810
3906
|
const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
|
|
@@ -3850,15 +3946,18 @@ class TileLayer extends EngineObject
|
|
|
3850
3946
|
mainCanvas.height = mainCanvasSize.y;
|
|
3851
3947
|
}
|
|
3852
3948
|
|
|
3853
|
-
//
|
|
3854
|
-
|
|
3949
|
+
// disable smoothing for pixel art
|
|
3950
|
+
this.context.imageSmoothingEnabled = !canvasPixelated;
|
|
3951
|
+
|
|
3952
|
+
// setup gl rendering if enabled
|
|
3953
|
+
glPreRender();
|
|
3855
3954
|
}
|
|
3856
3955
|
|
|
3857
3956
|
/** Call to end the redraw process */
|
|
3858
3957
|
redrawEnd()
|
|
3859
3958
|
{
|
|
3860
3959
|
ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
|
|
3861
|
-
|
|
3960
|
+
glCopyToContext(mainContext, true);
|
|
3862
3961
|
//debugSaveCanvas(this.canvas);
|
|
3863
3962
|
|
|
3864
3963
|
// set stuff back to normal
|
|
@@ -4183,20 +4282,21 @@ class ParticleEmitter extends EngineObject
|
|
|
4183
4282
|
class Particle extends EngineObject
|
|
4184
4283
|
{
|
|
4185
4284
|
/**
|
|
4186
|
-
* Create a particle with the
|
|
4187
|
-
*
|
|
4188
|
-
* @param {
|
|
4189
|
-
* @param {
|
|
4190
|
-
* @param {
|
|
4191
|
-
* @param {Color}
|
|
4192
|
-
* @param {
|
|
4193
|
-
* @param {Number}
|
|
4194
|
-
* @param {Number}
|
|
4195
|
-
* @param {Number}
|
|
4196
|
-
* @param {
|
|
4197
|
-
* @param {
|
|
4285
|
+
* Create a particle with the passed in settings
|
|
4286
|
+
* Typically this is created automatically by a ParticleEmitter
|
|
4287
|
+
* @param {Vector2} position - World space position of the particle
|
|
4288
|
+
* @param {TileInfo} tileInfo - Tile info to render particles
|
|
4289
|
+
* @param {Number} angle - Angle to rotate the particle
|
|
4290
|
+
* @param {Color} colorStart - Color at start of life
|
|
4291
|
+
* @param {Color} colorEnd - Color at end of life
|
|
4292
|
+
* @param {Number} lifeTime - How long to live for
|
|
4293
|
+
* @param {Number} sizeStart - Size at start of life
|
|
4294
|
+
* @param {Number} sizeEnd - Size at end of life
|
|
4295
|
+
* @param {Number} fadeRate - How quick to fade in/out
|
|
4296
|
+
* @param {Boolean} additive - Does it use additive blend mode
|
|
4297
|
+
* @param {Number} trailScale - If a trail, how long to make it
|
|
4198
4298
|
* @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
|
|
4199
|
-
* @param {Function}
|
|
4299
|
+
* @param {Function} [destroyCallback] - Callback when particle dies
|
|
4200
4300
|
*/
|
|
4201
4301
|
constructor(position, tileInfo, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, fadeRate, additive, trailScale, localSpaceEmitter, destroyCallback
|
|
4202
4302
|
)
|
|
@@ -4605,6 +4705,8 @@ let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData,
|
|
|
4605
4705
|
// Initalize WebGL, called automatically by the engine
|
|
4606
4706
|
function glInit()
|
|
4607
4707
|
{
|
|
4708
|
+
if (!glEnable || headlessMode) return;
|
|
4709
|
+
|
|
4608
4710
|
// create the canvas and textures
|
|
4609
4711
|
glCanvas = document.createElement('canvas');
|
|
4610
4712
|
glContext = glCanvas.getContext('webgl2');
|
|
@@ -4617,11 +4719,11 @@ function glInit()
|
|
|
4617
4719
|
'#version 300 es\n' + // specify GLSL ES version
|
|
4618
4720
|
'precision highp float;'+ // use highp for better accuracy
|
|
4619
4721
|
'uniform mat4 m;'+ // transform matrix
|
|
4620
|
-
'in vec2 g;'+ // geometry
|
|
4621
|
-
'in vec4 p,u,c,a;'+ // position/size, uvs, color, additiveColor
|
|
4622
|
-
'in float r;'+ // rotation
|
|
4623
|
-
'out vec2 v;'+ //
|
|
4624
|
-
'out vec4 d,e;'+ //
|
|
4722
|
+
'in vec2 g;'+ // in: geometry
|
|
4723
|
+
'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
|
|
4724
|
+
'in float r;'+ // in: rotation
|
|
4725
|
+
'out vec2 v;'+ // out: uv
|
|
4726
|
+
'out vec4 d,e;'+ // out: color, additiveColor
|
|
4625
4727
|
'void main(){'+ // shader entry point
|
|
4626
4728
|
'vec2 s=(g-.5)*p.zw;'+ // get size offset
|
|
4627
4729
|
'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
|
|
@@ -4631,10 +4733,10 @@ function glInit()
|
|
|
4631
4733
|
,
|
|
4632
4734
|
'#version 300 es\n' + // specify GLSL ES version
|
|
4633
4735
|
'precision highp float;'+ // use highp for better accuracy
|
|
4634
|
-
'in vec2 v;'+ // uv
|
|
4635
|
-
'in vec4 d,e;'+ // color, additiveColor
|
|
4636
4736
|
'uniform sampler2D s;'+ // texture
|
|
4637
|
-
'
|
|
4737
|
+
'in vec2 v;'+ // in: uv
|
|
4738
|
+
'in vec4 d,e;'+ // in: color, additiveColor
|
|
4739
|
+
'out vec4 c;'+ // out: color
|
|
4638
4740
|
'void main(){'+ // shader entry point
|
|
4639
4741
|
'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
|
|
4640
4742
|
'}' // end of shader
|
|
@@ -4656,9 +4758,11 @@ function glInit()
|
|
|
4656
4758
|
// Setup render each frame, called automatically by engine
|
|
4657
4759
|
function glPreRender()
|
|
4658
4760
|
{
|
|
4761
|
+
if (!glEnable || headlessMode) return;
|
|
4762
|
+
|
|
4659
4763
|
// clear and set to same size as main canvas
|
|
4660
4764
|
glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
|
|
4661
|
-
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
4765
|
+
//glContext.clear(gl_COLOR_BUFFER_BIT); // auto cleared when size is set
|
|
4662
4766
|
|
|
4663
4767
|
// set up the shader
|
|
4664
4768
|
glContext.useProgram(glShader);
|
|
@@ -4692,12 +4796,12 @@ function glPreRender()
|
|
|
4692
4796
|
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
4693
4797
|
const p = vec2(-1).subtract(cameraPos.multiply(s));
|
|
4694
4798
|
glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), false,
|
|
4695
|
-
|
|
4799
|
+
[
|
|
4696
4800
|
s.x, 0, 0, 0,
|
|
4697
4801
|
0, s.y, 0, 0,
|
|
4698
4802
|
1, 1, 1, 1,
|
|
4699
4803
|
p.x, p.y, 0, 0
|
|
4700
|
-
]
|
|
4804
|
+
]
|
|
4701
4805
|
);
|
|
4702
4806
|
}
|
|
4703
4807
|
|
|
@@ -4708,7 +4812,7 @@ function glPreRender()
|
|
|
4708
4812
|
function glSetTexture(texture)
|
|
4709
4813
|
{
|
|
4710
4814
|
// must flush cache with the old texture to set a new one
|
|
4711
|
-
if (texture == glActiveTexture)
|
|
4815
|
+
if (headlessMode || texture == glActiveTexture)
|
|
4712
4816
|
return;
|
|
4713
4817
|
|
|
4714
4818
|
glFlush();
|
|
@@ -4768,7 +4872,6 @@ function glCreateTexture(image)
|
|
|
4768
4872
|
const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
|
|
4769
4873
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
|
|
4770
4874
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
|
|
4771
|
-
|
|
4772
4875
|
return texture;
|
|
4773
4876
|
}
|
|
4774
4877
|
|
|
@@ -4792,12 +4895,12 @@ function glFlush()
|
|
|
4792
4895
|
}
|
|
4793
4896
|
|
|
4794
4897
|
/** Draw any sprites still in the buffer, copy to main canvas and clear
|
|
4795
|
-
* @param {CanvasRenderingContext2D} context
|
|
4898
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
|
4796
4899
|
* @param {Boolean} [forceDraw]
|
|
4797
4900
|
* @memberof WebGL */
|
|
4798
4901
|
function glCopyToContext(context, forceDraw=false)
|
|
4799
4902
|
{
|
|
4800
|
-
if (!glInstanceCount && !forceDraw) return;
|
|
4903
|
+
if (!glEnable || !glInstanceCount && !forceDraw) return;
|
|
4801
4904
|
|
|
4802
4905
|
glFlush();
|
|
4803
4906
|
|
|
@@ -4854,7 +4957,7 @@ let glPostShader, glPostTexture, glPostIncludeOverlay;
|
|
|
4854
4957
|
function glInitPostProcess(shaderCode, includeOverlay=false)
|
|
4855
4958
|
{
|
|
4856
4959
|
ASSERT(!glPostShader, 'can only have 1 post effects shader');
|
|
4857
|
-
|
|
4960
|
+
if (headlessMode) return;
|
|
4858
4961
|
if (!shaderCode) // default shader pass through
|
|
4859
4962
|
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
|
|
4860
4963
|
|
|
@@ -4893,8 +4996,7 @@ function glInitPostProcess(shaderCode, includeOverlay=false)
|
|
|
4893
4996
|
// Render the post processing shader, called automatically by the engine
|
|
4894
4997
|
function glRenderPostProcess()
|
|
4895
4998
|
{
|
|
4896
|
-
if (!glPostShader)
|
|
4897
|
-
return;
|
|
4999
|
+
if (!glPostShader || headlessMode) return;
|
|
4898
5000
|
|
|
4899
5001
|
// prepare to render post process shader
|
|
4900
5002
|
if (glEnable)
|
|
@@ -5000,7 +5102,7 @@ const engineName = 'LittleJS';
|
|
|
5000
5102
|
* @type {String}
|
|
5001
5103
|
* @default
|
|
5002
5104
|
* @memberof Engine */
|
|
5003
|
-
const engineVersion = '1.9.
|
|
5105
|
+
const engineVersion = '1.9.6';
|
|
5004
5106
|
|
|
5005
5107
|
/** Frames per second to update
|
|
5006
5108
|
* @type {Number}
|
|
@@ -5067,6 +5169,19 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5067
5169
|
{
|
|
5068
5170
|
ASSERT(Array.isArray(imageSources), 'pass in images as array');
|
|
5069
5171
|
|
|
5172
|
+
// Called automatically by engine to setup render system
|
|
5173
|
+
function enginePreRender()
|
|
5174
|
+
{
|
|
5175
|
+
// save canvas size
|
|
5176
|
+
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
5177
|
+
|
|
5178
|
+
// disable smoothing for pixel art
|
|
5179
|
+
mainContext.imageSmoothingEnabled = !canvasPixelated;
|
|
5180
|
+
|
|
5181
|
+
// setup gl rendering if enabled
|
|
5182
|
+
glPreRender();
|
|
5183
|
+
}
|
|
5184
|
+
|
|
5070
5185
|
// internal update loop for engine
|
|
5071
5186
|
function engineUpdate(frameTimeMS=0)
|
|
5072
5187
|
{
|
|
@@ -5083,11 +5198,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5083
5198
|
frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
|
|
5084
5199
|
if (!debugSpeedUp)
|
|
5085
5200
|
frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp in case of slow framerate
|
|
5201
|
+
|
|
5086
5202
|
updateCanvas();
|
|
5087
5203
|
|
|
5088
5204
|
if (paused)
|
|
5089
5205
|
{
|
|
5090
|
-
//
|
|
5206
|
+
// update object transforms even when paused
|
|
5207
|
+
for (const o of engineObjects)
|
|
5208
|
+
o.parent || o.updateTransforms();
|
|
5091
5209
|
inputUpdate();
|
|
5092
5210
|
debugUpdate();
|
|
5093
5211
|
gameUpdatePost();
|
|
@@ -5099,7 +5217,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5099
5217
|
let deltaSmooth = 0;
|
|
5100
5218
|
if (frameTimeBufferMS < 0 && frameTimeBufferMS > -9)
|
|
5101
5219
|
{
|
|
5102
|
-
// force
|
|
5220
|
+
// force at least one update each frame since it is waiting for refresh
|
|
5103
5221
|
deltaSmooth = frameTimeBufferMS;
|
|
5104
5222
|
frameTimeBufferMS = 0;
|
|
5105
5223
|
}
|
|
@@ -5124,34 +5242,37 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5124
5242
|
// add the time smoothing back in
|
|
5125
5243
|
frameTimeBufferMS += deltaSmooth;
|
|
5126
5244
|
}
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
enginePreRender();
|
|
5130
|
-
gameRender();
|
|
5131
|
-
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
5132
|
-
for (const o of engineObjects)
|
|
5133
|
-
o.destroyed || o.render();
|
|
5134
|
-
gameRenderPost();
|
|
5135
|
-
glRenderPostProcess();
|
|
5136
|
-
medalsRender();
|
|
5137
|
-
touchGamepadRender();
|
|
5138
|
-
debugRender();
|
|
5139
|
-
glEnable && glCopyToContext(mainContext);
|
|
5140
|
-
|
|
5141
|
-
if (showWatermark)
|
|
5245
|
+
|
|
5246
|
+
if (!headlessMode)
|
|
5142
5247
|
{
|
|
5143
|
-
//
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5248
|
+
// render sort then render while removing destroyed objects
|
|
5249
|
+
enginePreRender();
|
|
5250
|
+
gameRender();
|
|
5251
|
+
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
5252
|
+
for (const o of engineObjects)
|
|
5253
|
+
o.destroyed || o.render();
|
|
5254
|
+
gameRenderPost();
|
|
5255
|
+
glRenderPostProcess();
|
|
5256
|
+
medalsRender();
|
|
5257
|
+
touchGamepadRender();
|
|
5258
|
+
debugRender();
|
|
5259
|
+
glCopyToContext(mainContext);
|
|
5260
|
+
|
|
5261
|
+
if (showWatermark)
|
|
5262
|
+
{
|
|
5263
|
+
// update fps
|
|
5264
|
+
overlayContext.textAlign = 'right';
|
|
5265
|
+
overlayContext.textBaseline = 'top';
|
|
5266
|
+
overlayContext.font = '1em monospace';
|
|
5267
|
+
overlayContext.fillStyle = '#000';
|
|
5268
|
+
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
5269
|
+
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
5270
|
+
+ (glEnable ? ' GL' : ' 2D') ;
|
|
5271
|
+
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
5272
|
+
overlayContext.fillStyle = '#fff';
|
|
5273
|
+
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
5274
|
+
drawCount = 0;
|
|
5275
|
+
}
|
|
5155
5276
|
}
|
|
5156
5277
|
|
|
5157
5278
|
requestAnimationFrame(engineUpdate);
|
|
@@ -5159,6 +5280,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5159
5280
|
|
|
5160
5281
|
function updateCanvas()
|
|
5161
5282
|
{
|
|
5283
|
+
if (headlessMode) return;
|
|
5284
|
+
|
|
5162
5285
|
if (canvasFixedSize.x)
|
|
5163
5286
|
{
|
|
5164
5287
|
// clear canvas and set fixed size
|
|
@@ -5186,8 +5309,20 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5186
5309
|
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
5187
5310
|
}
|
|
5188
5311
|
|
|
5312
|
+
function startEngine()
|
|
5313
|
+
{
|
|
5314
|
+
gameInit();
|
|
5315
|
+
engineUpdate();
|
|
5316
|
+
}
|
|
5317
|
+
|
|
5318
|
+
if (headlessMode)
|
|
5319
|
+
{
|
|
5320
|
+
startEngine();
|
|
5321
|
+
return;
|
|
5322
|
+
}
|
|
5323
|
+
|
|
5189
5324
|
// setup html
|
|
5190
|
-
|
|
5325
|
+
const styleBody =
|
|
5191
5326
|
'margin:0;overflow:hidden;' + // fill the window
|
|
5192
5327
|
'background:#000;' + // set background color
|
|
5193
5328
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
@@ -5199,8 +5334,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5199
5334
|
mainContext = mainCanvas.getContext('2d');
|
|
5200
5335
|
|
|
5201
5336
|
// init stuff and start engine
|
|
5337
|
+
inputInit();
|
|
5202
5338
|
debugInit();
|
|
5203
|
-
|
|
5339
|
+
glInit();
|
|
5204
5340
|
|
|
5205
5341
|
// create overlay canvas for hud to appear above gl canvas
|
|
5206
5342
|
document.body.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
@@ -5224,7 +5360,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5224
5360
|
}
|
|
5225
5361
|
image.src = src;
|
|
5226
5362
|
})
|
|
5227
|
-
)
|
|
5363
|
+
);
|
|
5228
5364
|
|
|
5229
5365
|
// draw splash screen
|
|
5230
5366
|
showSplashScreen && promises.push(new Promise(resolve =>
|
|
@@ -5241,25 +5377,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5241
5377
|
}));
|
|
5242
5378
|
|
|
5243
5379
|
// load all of the images
|
|
5244
|
-
Promise.all(promises).then(
|
|
5245
|
-
{
|
|
5246
|
-
// start the engine
|
|
5247
|
-
gameInit();
|
|
5248
|
-
engineUpdate();
|
|
5249
|
-
});
|
|
5250
|
-
}
|
|
5251
|
-
|
|
5252
|
-
// Called automatically by engine to setup render system
|
|
5253
|
-
function enginePreRender()
|
|
5254
|
-
{
|
|
5255
|
-
// save canvas size
|
|
5256
|
-
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
5257
|
-
|
|
5258
|
-
// disable smoothing for pixel art
|
|
5259
|
-
mainContext.imageSmoothingEnabled = !canvasPixelated;
|
|
5260
|
-
|
|
5261
|
-
// setup gl rendering if enabled
|
|
5262
|
-
glEnable && glPreRender();
|
|
5380
|
+
Promise.all(promises).then(startEngine);
|
|
5263
5381
|
}
|
|
5264
5382
|
|
|
5265
5383
|
/** Update each engine object, remove destroyed objects, and update time
|
|
@@ -5280,7 +5398,14 @@ function engineObjectsUpdate()
|
|
|
5280
5398
|
}
|
|
5281
5399
|
}
|
|
5282
5400
|
for (const o of engineObjects)
|
|
5283
|
-
|
|
5401
|
+
{
|
|
5402
|
+
// update top level objects
|
|
5403
|
+
if (!o.parent)
|
|
5404
|
+
{
|
|
5405
|
+
updateObject(o);
|
|
5406
|
+
o.updateTransforms();
|
|
5407
|
+
}
|
|
5408
|
+
}
|
|
5284
5409
|
|
|
5285
5410
|
// remove destroyed objects
|
|
5286
5411
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
@@ -5295,30 +5420,63 @@ function engineObjectsDestroy()
|
|
|
5295
5420
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
5296
5421
|
}
|
|
5297
5422
|
|
|
5298
|
-
/**
|
|
5299
|
-
* @param {Vector2} [pos] - Center of test area
|
|
5423
|
+
/** Collects all object within a given area
|
|
5424
|
+
* @param {Vector2} [pos] - Center of test area, or undefined for all objects
|
|
5300
5425
|
* @param {Number|Vector2} [size] - Radius of circle if float, rectangle size if Vector2
|
|
5301
|
-
* @param {Function} [callbackFunction] - Calls this function on every object that passes the test
|
|
5302
5426
|
* @param {Array} [objects=engineObjects] - List of objects to check
|
|
5427
|
+
* @return {Array} - List of collected objects
|
|
5303
5428
|
* @memberof Engine */
|
|
5304
|
-
function
|
|
5429
|
+
function engineObjectsCollect(pos, size, objects=engineObjects)
|
|
5305
5430
|
{
|
|
5431
|
+
const collectedObjects = [];
|
|
5306
5432
|
if (!pos) // all objects
|
|
5307
5433
|
{
|
|
5308
5434
|
for (const o of objects)
|
|
5309
|
-
|
|
5435
|
+
collectedObjects.push(o);
|
|
5310
5436
|
}
|
|
5311
|
-
else if (
|
|
5437
|
+
else if (size instanceof Vector2) // bounding box test
|
|
5312
5438
|
{
|
|
5313
5439
|
for (const o of objects)
|
|
5314
|
-
isOverlapping(pos, size, o.pos, o.size) &&
|
|
5440
|
+
isOverlapping(pos, size, o.pos, o.size) && collectedObjects.push(o);
|
|
5315
5441
|
}
|
|
5316
5442
|
else // circle test
|
|
5317
5443
|
{
|
|
5318
5444
|
const sizeSquared = size*size;
|
|
5319
5445
|
for (const o of objects)
|
|
5320
|
-
pos.distanceSquared(o.pos) < sizeSquared &&
|
|
5446
|
+
pos.distanceSquared(o.pos) < sizeSquared && collectedObjects.push(o);
|
|
5447
|
+
}
|
|
5448
|
+
return collectedObjects;
|
|
5449
|
+
}
|
|
5450
|
+
|
|
5451
|
+
/** Triggers a callback for each object within a given area
|
|
5452
|
+
* @param {Vector2} [pos] - Center of test area, or undefined for all objects
|
|
5453
|
+
* @param {Number|Vector2} [size] - Radius of circle if float, rectangle size if Vector2
|
|
5454
|
+
* @param {Function} [callbackFunction] - Calls this function on every object that passes the test
|
|
5455
|
+
* @param {Array} [objects=engineObjects] - List of objects to check
|
|
5456
|
+
* @memberof Engine */
|
|
5457
|
+
function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
|
|
5458
|
+
{ engineObjectsCollect(pos, size, objects).forEach(o => callbackFunction(o)); }
|
|
5459
|
+
|
|
5460
|
+
/** Return a list of objects intersecting a ray
|
|
5461
|
+
* @param {Vector2} start
|
|
5462
|
+
* @param {Vector2} end
|
|
5463
|
+
* @param {Array} [objects=engineObjects] - List of objects to check
|
|
5464
|
+
* @return {Array} - List of objects hit
|
|
5465
|
+
* @memberof Engine */
|
|
5466
|
+
function engineObjectsRaycast(start, end, objects=engineObjects)
|
|
5467
|
+
{
|
|
5468
|
+
const hitObjects = [];
|
|
5469
|
+
for (const o of objects)
|
|
5470
|
+
{
|
|
5471
|
+
if (o.collideRaycast && isIntersecting(start, end, o.pos, o.size))
|
|
5472
|
+
{
|
|
5473
|
+
debugRaycast && debugRect(o.pos, o.size, '#f00');
|
|
5474
|
+
hitObjects.push(o);
|
|
5475
|
+
}
|
|
5321
5476
|
}
|
|
5477
|
+
|
|
5478
|
+
debugRaycast && debugLine(start, end, hitObjects.length ? '#f00' : '#00f', .02);
|
|
5479
|
+
return hitObjects;
|
|
5322
5480
|
}
|
|
5323
5481
|
|
|
5324
5482
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -5406,16 +5564,16 @@ function drawEngineSplashScreen(t)
|
|
|
5406
5564
|
rect(37,14,9,6);
|
|
5407
5565
|
|
|
5408
5566
|
// big stack
|
|
5409
|
-
rect(50,20,10,-
|
|
5410
|
-
rect(50,20,6.5,-
|
|
5411
|
-
rect(50,20,3.5,-
|
|
5412
|
-
rect(50,20,10,-
|
|
5413
|
-
circle(55,2,11.4,.5,PI-.5,color(3,3))
|
|
5414
|
-
circle(55,2,11.4,.5,PI/2,color(3,2),1)
|
|
5415
|
-
circle(55,2,11.4,.5,PI-.5)
|
|
5416
|
-
rect(45,7,20,-7,color(0,2))
|
|
5417
|
-
rect(45,
|
|
5418
|
-
rect(45,
|
|
5567
|
+
rect(50,20,10,-8,color(0,1))
|
|
5568
|
+
rect(50,20,6.5,-8,color(0,2))
|
|
5569
|
+
rect(50,20,3.5,-8,color(0,3))
|
|
5570
|
+
rect(50,20,10,-8)
|
|
5571
|
+
circle(55,2,11.4,.5,PI-.5,color(3,3))
|
|
5572
|
+
circle(55,2,11.4,.5,PI/2,color(3,2),1)
|
|
5573
|
+
circle(55,2,11.4,.5,PI-.5)
|
|
5574
|
+
rect(45,7,20,-7,color(0,2))
|
|
5575
|
+
rect(45,-1,20,4,color(0,3))
|
|
5576
|
+
rect(45,-1,20,8)
|
|
5419
5577
|
|
|
5420
5578
|
// engine
|
|
5421
5579
|
for (let i=5; i--;)
|
|
@@ -5533,6 +5691,7 @@ export {
|
|
|
5533
5691
|
canvasPixelated,
|
|
5534
5692
|
fontDefault,
|
|
5535
5693
|
showSplashScreen,
|
|
5694
|
+
headlessMode,
|
|
5536
5695
|
tileSizeDefault,
|
|
5537
5696
|
tileFixBleedScale,
|
|
5538
5697
|
enablePhysicsSolver,
|
|
@@ -5571,6 +5730,7 @@ export {
|
|
|
5571
5730
|
setCanvasPixelated,
|
|
5572
5731
|
setFontDefault,
|
|
5573
5732
|
setShowSplashScreen,
|
|
5733
|
+
setHeadlessMode,
|
|
5574
5734
|
setGlEnable,
|
|
5575
5735
|
setGlOverlay,
|
|
5576
5736
|
setTileSizeDefault,
|