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.release.js
CHANGED
|
@@ -99,7 +99,7 @@ function clamp(value, min=0, max=1) { return value < min ? min : value > max ? m
|
|
|
99
99
|
* @return {Number}
|
|
100
100
|
* @memberof Utilities */
|
|
101
101
|
function percent(value, valueA, valueB)
|
|
102
|
-
{ return valueB
|
|
102
|
+
{ return (valueB-=valueA) ? clamp((value-valueA)/valueB) : 0; }
|
|
103
103
|
|
|
104
104
|
/** Linearly interpolates between values passed in using percent
|
|
105
105
|
* @param {Number} percent
|
|
@@ -156,16 +156,57 @@ function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
|
|
|
156
156
|
function nearestPowerOfTwo(value) { return 2**Math.ceil(Math.log2(value)); }
|
|
157
157
|
|
|
158
158
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
159
|
-
* @param {Vector2}
|
|
160
|
-
* @param {Vector2} sizeA
|
|
161
|
-
* @param {Vector2}
|
|
162
|
-
* @param {Vector2} [sizeB=(0,0)]
|
|
163
|
-
* @return {Boolean}
|
|
159
|
+
* @param {Vector2} posA - Center of box A
|
|
160
|
+
* @param {Vector2} sizeA - Size of box A
|
|
161
|
+
* @param {Vector2} posB - Center of box B
|
|
162
|
+
* @param {Vector2} [sizeB=(0,0)] - Size of box B, a point if undefined
|
|
163
|
+
* @return {Boolean} - True if overlapping
|
|
164
164
|
* @memberof Utilities */
|
|
165
|
-
function isOverlapping(
|
|
165
|
+
function isOverlapping(posA, sizeA, posB, sizeB=vec2())
|
|
166
166
|
{
|
|
167
|
-
return abs(
|
|
168
|
-
&& abs(
|
|
167
|
+
return abs(posA.x - posB.x)*2 < sizeA.x + sizeB.x
|
|
168
|
+
&& abs(posA.y - posB.y)*2 < sizeA.y + sizeB.y;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Returns true if a line segment is intersecting an axis aligned box
|
|
172
|
+
* @param {Vector2} start - Start of raycast
|
|
173
|
+
* @param {Vector2} end - End of raycast
|
|
174
|
+
* @param {Vector2} pos - Center of box
|
|
175
|
+
* @param {Vector2} size - Size of box
|
|
176
|
+
* @return {Boolean} - True if intersecting
|
|
177
|
+
* @memberof Utilities */
|
|
178
|
+
function isIntersecting(start, end, pos, size)
|
|
179
|
+
{
|
|
180
|
+
// Liang-Barsky algorithm
|
|
181
|
+
const boxMin = pos.subtract(size.scale(.5));
|
|
182
|
+
const boxMax = boxMin.add(size);
|
|
183
|
+
const delta = end.subtract(start);
|
|
184
|
+
const a = start.subtract(boxMin);
|
|
185
|
+
const b = start.subtract(boxMax);
|
|
186
|
+
const p = [-delta.x, delta.x, -delta.y, delta.y];
|
|
187
|
+
const q = [a.x, -b.x, a.y, -b.y];
|
|
188
|
+
let tMin = 0, tMax = 1;
|
|
189
|
+
for (let i = 4; i--;)
|
|
190
|
+
{
|
|
191
|
+
if (p[i])
|
|
192
|
+
{
|
|
193
|
+
const t = q[i] / p[i];
|
|
194
|
+
if (p[i] < 0)
|
|
195
|
+
{
|
|
196
|
+
if (t > tMax) return false;
|
|
197
|
+
tMin = max(t, tMin);
|
|
198
|
+
}
|
|
199
|
+
else
|
|
200
|
+
{
|
|
201
|
+
if (t < tMin) return false;
|
|
202
|
+
tMax = min(t, tMax);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else if (q[i] < 0)
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return true;
|
|
169
210
|
}
|
|
170
211
|
|
|
171
212
|
/** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
|
|
@@ -265,7 +306,7 @@ class RandomGenerator
|
|
|
265
306
|
this.seed ^= this.seed << 13;
|
|
266
307
|
this.seed ^= this.seed >>> 17;
|
|
267
308
|
this.seed ^= this.seed << 5;
|
|
268
|
-
return valueB + (valueA - valueB) * abs(this.seed %
|
|
309
|
+
return valueB + (valueA - valueB) * abs(this.seed % 1e8) / 1e8;
|
|
269
310
|
}
|
|
270
311
|
|
|
271
312
|
/** Returns a floored seeded random value the two values passed in
|
|
@@ -276,7 +317,7 @@ class RandomGenerator
|
|
|
276
317
|
|
|
277
318
|
/** Randomly returns either -1 or 1 deterministically
|
|
278
319
|
* @return {Number} */
|
|
279
|
-
sign() { return this.
|
|
320
|
+
sign() { return this.float() > .5 ? 1 : -1; }
|
|
280
321
|
}
|
|
281
322
|
|
|
282
323
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -324,6 +365,7 @@ class Vector2
|
|
|
324
365
|
* @param {Number} [y] - Y axis location */
|
|
325
366
|
constructor(x=0, y=0)
|
|
326
367
|
{
|
|
368
|
+
ASSERT(typeof x === 'number' && typeof y === 'number');
|
|
327
369
|
/** @property {Number} - X axis location */
|
|
328
370
|
this.x = x;
|
|
329
371
|
/** @property {Number} - Y axis location */
|
|
@@ -650,12 +692,14 @@ class Color
|
|
|
650
692
|
* @return {Color} */
|
|
651
693
|
setHSLA(h=0, s=0, l=1, a=1)
|
|
652
694
|
{
|
|
695
|
+
h = mod(h,1);
|
|
696
|
+
s = clamp(s);
|
|
697
|
+
l = clamp(l);
|
|
653
698
|
const q = l < .5 ? l*(1+s) : l+s-l*s, p = 2*l-q,
|
|
654
699
|
f = (p, q, t)=>
|
|
655
|
-
(t = (
|
|
656
|
-
t < 1
|
|
657
|
-
t < 2
|
|
658
|
-
|
|
700
|
+
(t = mod(t,1))*6 < 1 ? p+(q-p)*6*t :
|
|
701
|
+
t*2 < 1 ? q :
|
|
702
|
+
t*3 < 2 ? p+(q-p)*(4-t*6) : p;
|
|
659
703
|
this.r = f(p, q, h + 1/3);
|
|
660
704
|
this.g = f(p, q, h);
|
|
661
705
|
this.b = f(p, q, h - 1/3);
|
|
@@ -706,13 +750,12 @@ class Color
|
|
|
706
750
|
).clamp();
|
|
707
751
|
}
|
|
708
752
|
|
|
709
|
-
/** Returns this color expressed as a
|
|
753
|
+
/** Returns this color expressed as a rgb color code
|
|
710
754
|
* @param {Boolean} [useAlpha] - if alpha should be included in result
|
|
711
755
|
* @return {String} */
|
|
712
|
-
toString(useAlpha = true)
|
|
713
|
-
{
|
|
714
|
-
|
|
715
|
-
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
756
|
+
toString(useAlpha = true)
|
|
757
|
+
{
|
|
758
|
+
return `rgb(${this.r*255},${this.g*255},${this.b*255},${useAlpha ? this.a : 0})`;
|
|
716
759
|
}
|
|
717
760
|
|
|
718
761
|
/** Set this color from a hex code
|
|
@@ -770,11 +813,11 @@ class Timer
|
|
|
770
813
|
|
|
771
814
|
/** Returns true if set and has not elapsed
|
|
772
815
|
* @return {Boolean} */
|
|
773
|
-
active() { return time
|
|
816
|
+
active() { return time < this.time; }
|
|
774
817
|
|
|
775
818
|
/** Returns true if set and elapsed
|
|
776
819
|
* @return {Boolean} */
|
|
777
|
-
elapsed() { return time
|
|
820
|
+
elapsed() { return time >= this.time; }
|
|
778
821
|
|
|
779
822
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
780
823
|
* @return {Number} */
|
|
@@ -849,6 +892,12 @@ let fontDefault = 'arial';
|
|
|
849
892
|
* @memberof Settings */
|
|
850
893
|
let showSplashScreen = false;
|
|
851
894
|
|
|
895
|
+
/** Disables all rendering, audio, and input for servers
|
|
896
|
+
* @type {Boolean}
|
|
897
|
+
* @default
|
|
898
|
+
* @memberof Settings */
|
|
899
|
+
let headlessMode = false;
|
|
900
|
+
|
|
852
901
|
///////////////////////////////////////////////////////////////////////////////
|
|
853
902
|
// WebGL settings
|
|
854
903
|
|
|
@@ -877,7 +926,7 @@ let tileSizeDefault = vec2(16);
|
|
|
877
926
|
* @type {Number}
|
|
878
927
|
* @default
|
|
879
928
|
* @memberof Settings */
|
|
880
|
-
let tileFixBleedScale = .
|
|
929
|
+
let tileFixBleedScale = .5;
|
|
881
930
|
|
|
882
931
|
///////////////////////////////////////////////////////////////////////////////
|
|
883
932
|
// Object settings
|
|
@@ -1002,7 +1051,7 @@ let soundEnable = true;
|
|
|
1002
1051
|
* @type {Number}
|
|
1003
1052
|
* @default
|
|
1004
1053
|
* @memberof Settings */
|
|
1005
|
-
let soundVolume = .
|
|
1054
|
+
let soundVolume = .3;
|
|
1006
1055
|
|
|
1007
1056
|
/** Default range where sound no longer plays
|
|
1008
1057
|
* @type {Number}
|
|
@@ -1087,6 +1136,11 @@ function setFontDefault(font) { fontDefault = font; }
|
|
|
1087
1136
|
* @memberof Settings */
|
|
1088
1137
|
function setShowSplashScreen(show) { showSplashScreen = show; }
|
|
1089
1138
|
|
|
1139
|
+
/** Set to disalbe rendering, audio, and input for servers
|
|
1140
|
+
* @param {Boolean} headless
|
|
1141
|
+
* @memberof Settings */
|
|
1142
|
+
function setHeadlessMode(headless) { headlessMode = headless; }
|
|
1143
|
+
|
|
1090
1144
|
/** Set if webgl rendering is enabled
|
|
1091
1145
|
* @param {Boolean} enable
|
|
1092
1146
|
* @memberof Settings */
|
|
@@ -1349,23 +1403,37 @@ class EngineObject
|
|
|
1349
1403
|
this.collideSolidObjects = false;
|
|
1350
1404
|
/** @property {Boolean} - Object collides with and blocks other objects */
|
|
1351
1405
|
this.isSolid = false;
|
|
1406
|
+
/** @property {Boolean} - Object collides with raycasts */
|
|
1407
|
+
this.collideRaycast = false;
|
|
1352
1408
|
|
|
1353
1409
|
// add to list of objects
|
|
1354
1410
|
engineObjects.push(this);
|
|
1355
1411
|
}
|
|
1356
1412
|
|
|
1357
|
-
/** Update the object transform
|
|
1358
|
-
|
|
1413
|
+
/** Update the object transform, called automatically by engine even when paused */
|
|
1414
|
+
updateTransforms()
|
|
1359
1415
|
{
|
|
1360
1416
|
const parent = this.parent;
|
|
1361
1417
|
if (parent)
|
|
1362
1418
|
{
|
|
1363
1419
|
// copy parent pos/angle
|
|
1364
|
-
|
|
1365
|
-
this.
|
|
1366
|
-
|
|
1420
|
+
const mirror = parent.getMirrorSign();
|
|
1421
|
+
this.pos = this.localPos.multiply(vec2(mirror,1)).rotate(-parent.angle).add(parent.pos);
|
|
1422
|
+
this.angle = mirror*this.localAngle + parent.angle;
|
|
1367
1423
|
}
|
|
1368
1424
|
|
|
1425
|
+
// update children
|
|
1426
|
+
for (const child of this.children)
|
|
1427
|
+
child.updateTransforms();
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
/** Update the object physics, called automatically by engine once each frame */
|
|
1431
|
+
update()
|
|
1432
|
+
{
|
|
1433
|
+
// child objects do not have physics
|
|
1434
|
+
if (this.parent)
|
|
1435
|
+
return;
|
|
1436
|
+
|
|
1369
1437
|
// limit max speed to prevent missing collisions
|
|
1370
1438
|
this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
|
|
1371
1439
|
this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
|
|
@@ -1380,8 +1448,7 @@ class EngineObject
|
|
|
1380
1448
|
// physics sanity checks
|
|
1381
1449
|
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
1382
1450
|
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
1383
|
-
|
|
1384
|
-
if (!enablePhysicsSolver || !this.mass) // do not update collision for fixed objects
|
|
1451
|
+
if (!enablePhysicsSolver || !this.mass) // dont do collision for fixed objects
|
|
1385
1452
|
return;
|
|
1386
1453
|
|
|
1387
1454
|
const wasMovingDown = this.velocity.y < 0;
|
|
@@ -1553,13 +1620,7 @@ class EngineObject
|
|
|
1553
1620
|
* @param {Number} tileData - the value of the tile at the position
|
|
1554
1621
|
* @param {Vector2} pos - tile where the collision occured
|
|
1555
1622
|
* @return {Boolean} - true if the collision should be resolved */
|
|
1556
|
-
collideWithTile(tileData, pos)
|
|
1557
|
-
|
|
1558
|
-
/** Called to check if a tile raycast hit
|
|
1559
|
-
* @param {Number} tileData - the value of the tile at the position
|
|
1560
|
-
* @param {Vector2} pos - tile where the raycast is
|
|
1561
|
-
* @return {Boolean} - true if the raycast should hit */
|
|
1562
|
-
collideWithTileRaycast(tileData, pos) { return tileData > 0; }
|
|
1623
|
+
collideWithTile(tileData, pos) { return tileData > 0; }
|
|
1563
1624
|
|
|
1564
1625
|
/** Called to check if a object collision should be resolved
|
|
1565
1626
|
* @param {EngineObject} object - the object to test against
|
|
@@ -1606,16 +1667,18 @@ class EngineObject
|
|
|
1606
1667
|
}
|
|
1607
1668
|
|
|
1608
1669
|
/** Set how this object collides
|
|
1609
|
-
* @param {Boolean} [collideSolidObjects] - Does it collide with solid objects
|
|
1610
|
-
* @param {Boolean} [isSolid] - Does it collide with and block other objects (expensive in large numbers)
|
|
1611
|
-
* @param {Boolean} [collideTiles] - Does it collide with the tile collision
|
|
1612
|
-
|
|
1670
|
+
* @param {Boolean} [collideSolidObjects] - Does it collide with solid objects?
|
|
1671
|
+
* @param {Boolean} [isSolid] - Does it collide with and block other objects? (expensive in large numbers)
|
|
1672
|
+
* @param {Boolean} [collideTiles] - Does it collide with the tile collision?
|
|
1673
|
+
* @param {Boolean} [collideRaycast] - Does it collide with raycasts? */
|
|
1674
|
+
setCollision(collideSolidObjects=true, isSolid=true, collideTiles=true, collideRaycast=true)
|
|
1613
1675
|
{
|
|
1614
1676
|
ASSERT(collideSolidObjects || !isSolid, 'solid objects must be set to collide');
|
|
1615
1677
|
|
|
1616
1678
|
this.collideSolidObjects = collideSolidObjects;
|
|
1617
1679
|
this.isSolid = isSolid;
|
|
1618
1680
|
this.collideTiles = collideTiles;
|
|
1681
|
+
this.collideRaycast = collideRaycast;
|
|
1619
1682
|
}
|
|
1620
1683
|
|
|
1621
1684
|
/** Returns string containg info about this object for debugging
|
|
@@ -1715,6 +1778,9 @@ let drawCount;
|
|
|
1715
1778
|
*/
|
|
1716
1779
|
function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
1717
1780
|
{
|
|
1781
|
+
if (headlessMode)
|
|
1782
|
+
return new TileInfo;
|
|
1783
|
+
|
|
1718
1784
|
// if size is a number, make it a vector
|
|
1719
1785
|
if (typeof size === 'number')
|
|
1720
1786
|
{
|
|
@@ -1748,9 +1814,9 @@ class TileInfo
|
|
|
1748
1814
|
constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
1749
1815
|
{
|
|
1750
1816
|
/** @property {Vector2} - Top left corner of tile in pixels */
|
|
1751
|
-
this.pos = pos;
|
|
1817
|
+
this.pos = pos.copy();
|
|
1752
1818
|
/** @property {Vector2} - Size of tile in pixels */
|
|
1753
|
-
this.size = size;
|
|
1819
|
+
this.size = size.copy();
|
|
1754
1820
|
/** @property {Number} - Texture index to use */
|
|
1755
1821
|
this.textureIndex = textureIndex;
|
|
1756
1822
|
}
|
|
@@ -1842,7 +1908,7 @@ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
|
1842
1908
|
* @param {Color} [additiveColor=(0,0,0,0)] - Additive color to be applied
|
|
1843
1909
|
* @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
1844
1910
|
* @param {Boolean} [screenSpace=false] - If true the pos and size are in screen space
|
|
1845
|
-
* @param {CanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
1911
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
1846
1912
|
* @memberof Draw */
|
|
1847
1913
|
function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
1848
1914
|
angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace, context)
|
|
@@ -1915,7 +1981,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
1915
1981
|
* @param {Number} [angle]
|
|
1916
1982
|
* @param {Boolean} [useWebGL=glEnable]
|
|
1917
1983
|
* @param {Boolean} [screenSpace=false]
|
|
1918
|
-
* @param {CanvasRenderingContext2D} [context]
|
|
1984
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
1919
1985
|
* @memberof Draw */
|
|
1920
1986
|
function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
1921
1987
|
{
|
|
@@ -1929,7 +1995,7 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
1929
1995
|
* @param {Color} [color=(1,1,1,1)]
|
|
1930
1996
|
* @param {Boolean} [useWebGL=glEnable]
|
|
1931
1997
|
* @param {Boolean} [screenSpace=false]
|
|
1932
|
-
* @param {CanvasRenderingContext2D} [context]
|
|
1998
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
1933
1999
|
* @memberof Draw */
|
|
1934
2000
|
function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
|
|
1935
2001
|
{
|
|
@@ -1945,7 +2011,7 @@ function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, contex
|
|
|
1945
2011
|
* @param {Boolean} mirror
|
|
1946
2012
|
* @param {Function} drawFunction
|
|
1947
2013
|
* @param {Boolean} [screenSpace=false]
|
|
1948
|
-
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
2014
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
|
|
1949
2015
|
* @memberof Draw */
|
|
1950
2016
|
function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, context=mainContext)
|
|
1951
2017
|
{
|
|
@@ -1966,7 +2032,7 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
|
|
|
1966
2032
|
/** Enable normal or additive blend mode
|
|
1967
2033
|
* @param {Boolean} [additive]
|
|
1968
2034
|
* @param {Boolean} [useWebGL=glEnable]
|
|
1969
|
-
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
2035
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
|
|
1970
2036
|
* @memberof Draw */
|
|
1971
2037
|
function setBlendMode(additive, useWebGL=glEnable, context)
|
|
1972
2038
|
{
|
|
@@ -1991,7 +2057,7 @@ function setBlendMode(additive, useWebGL=glEnable, context)
|
|
|
1991
2057
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
1992
2058
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
1993
2059
|
* @param {String} [font=fontDefault]
|
|
1994
|
-
* @param {CanvasRenderingContext2D} [context=overlayContext]
|
|
2060
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
1995
2061
|
* @memberof Draw */
|
|
1996
2062
|
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context)
|
|
1997
2063
|
{
|
|
@@ -2008,7 +2074,7 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
2008
2074
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
2009
2075
|
* @param {CanvasTextAlign} [textAlign]
|
|
2010
2076
|
* @param {String} [font=fontDefault]
|
|
2011
|
-
* @param {CanvasRenderingContext2D} [context=overlayContext]
|
|
2077
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
2012
2078
|
* @memberof Draw */
|
|
2013
2079
|
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
|
|
2014
2080
|
{
|
|
@@ -2051,7 +2117,7 @@ class FontImage
|
|
|
2051
2117
|
* @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
|
|
2052
2118
|
* @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
|
|
2053
2119
|
* @param {Vector2} [paddingSize=(0,1)] - How much extra space to add between characters
|
|
2054
|
-
* @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
2120
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
2055
2121
|
*/
|
|
2056
2122
|
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), context=overlayContext)
|
|
2057
2123
|
{
|
|
@@ -2259,7 +2325,7 @@ function gamepadWasReleased(button, gamepad=0)
|
|
|
2259
2325
|
* @return {Vector2}
|
|
2260
2326
|
* @memberof Input */
|
|
2261
2327
|
function gamepadStick(stick, gamepad=0)
|
|
2262
|
-
{ return
|
|
2328
|
+
{ return gamepadStickData[gamepad] ? gamepadStickData[gamepad][stick] || vec2() : vec2(); }
|
|
2263
2329
|
|
|
2264
2330
|
///////////////////////////////////////////////////////////////////////////////
|
|
2265
2331
|
// Input update called by engine
|
|
@@ -2270,6 +2336,8 @@ let inputData = [[]];
|
|
|
2270
2336
|
|
|
2271
2337
|
function inputUpdate()
|
|
2272
2338
|
{
|
|
2339
|
+
if (headlessMode) return;
|
|
2340
|
+
|
|
2273
2341
|
// clear input when lost focus (prevent stuck keys)
|
|
2274
2342
|
isTouchDevice || document.hasFocus() || clearInput();
|
|
2275
2343
|
|
|
@@ -2282,6 +2350,8 @@ function inputUpdate()
|
|
|
2282
2350
|
|
|
2283
2351
|
function inputUpdatePost()
|
|
2284
2352
|
{
|
|
2353
|
+
if (headlessMode) return;
|
|
2354
|
+
|
|
2285
2355
|
// clear input to prepare for next frame
|
|
2286
2356
|
for (const deviceInputData of inputData)
|
|
2287
2357
|
for (const i in deviceInputData)
|
|
@@ -2290,9 +2360,12 @@ function inputUpdatePost()
|
|
|
2290
2360
|
}
|
|
2291
2361
|
|
|
2292
2362
|
///////////////////////////////////////////////////////////////////////////////
|
|
2293
|
-
//
|
|
2363
|
+
// Input event handlers
|
|
2294
2364
|
|
|
2365
|
+
function inputInit()
|
|
2295
2366
|
{
|
|
2367
|
+
if (headlessMode) return;
|
|
2368
|
+
|
|
2296
2369
|
onkeydown = (e)=>
|
|
2297
2370
|
{
|
|
2298
2371
|
if (debug && e.target != document.body) return;
|
|
@@ -2323,21 +2396,29 @@ function inputUpdatePost()
|
|
|
2323
2396
|
c == 'KeyA' ? 'ArrowLeft' :
|
|
2324
2397
|
c == 'KeyD' ? 'ArrowRight' : c : c;
|
|
2325
2398
|
}
|
|
2399
|
+
|
|
2400
|
+
// mouse event handlers
|
|
2401
|
+
onmousedown = (e)=>
|
|
2402
|
+
{
|
|
2403
|
+
isUsingGamepad = false;
|
|
2404
|
+
inputData[0][e.button] = 3;
|
|
2405
|
+
mousePosScreen = mouseToScreen(e);
|
|
2406
|
+
e.button && e.preventDefault();
|
|
2407
|
+
}
|
|
2408
|
+
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
2409
|
+
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
2410
|
+
onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
|
|
2411
|
+
oncontextmenu = (e)=> false; // prevent right click menu
|
|
2412
|
+
|
|
2413
|
+
// init touch input
|
|
2414
|
+
if (isTouchDevice)
|
|
2415
|
+
touchInputInit();
|
|
2326
2416
|
}
|
|
2327
2417
|
|
|
2328
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2329
|
-
// Mouse event handlers
|
|
2330
|
-
|
|
2331
|
-
onmousedown = (e)=> {isUsingGamepad = false; inputData[0][e.button] = 3; mousePosScreen = mouseToScreen(e); e.button && e.preventDefault();}
|
|
2332
|
-
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
2333
|
-
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
2334
|
-
onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
|
|
2335
|
-
oncontextmenu = (e)=> false; // prevent right click menu
|
|
2336
|
-
|
|
2337
2418
|
// convert a mouse or touch event position to screen space
|
|
2338
2419
|
function mouseToScreen(mousePos)
|
|
2339
2420
|
{
|
|
2340
|
-
if (!mainCanvas)
|
|
2421
|
+
if (!mainCanvas || headlessMode)
|
|
2341
2422
|
return vec2(); // fix bug that can occur if user clicks before page loads
|
|
2342
2423
|
|
|
2343
2424
|
const rect = mainCanvas.getBoundingClientRect();
|
|
@@ -2349,7 +2430,7 @@ function mouseToScreen(mousePos)
|
|
|
2349
2430
|
// Gamepad input
|
|
2350
2431
|
|
|
2351
2432
|
// gamepad internal variables
|
|
2352
|
-
const
|
|
2433
|
+
const gamepadStickData = [];
|
|
2353
2434
|
|
|
2354
2435
|
// gamepads are updated by engine every frame automatically
|
|
2355
2436
|
function gamepadsUpdate()
|
|
@@ -2366,14 +2447,11 @@ function gamepadsUpdate()
|
|
|
2366
2447
|
// update touch gamepad if enabled
|
|
2367
2448
|
if (touchGamepadEnable && isTouchDevice)
|
|
2368
2449
|
{
|
|
2369
|
-
|
|
2370
|
-
if (!touchGamepadButtons)
|
|
2371
|
-
createTouchGamepad();
|
|
2372
|
-
|
|
2450
|
+
ASSERT(touchGamepadButtons, 'set touchGamepadEnable before calling init!');
|
|
2373
2451
|
if (touchGamepadTimer.isSet())
|
|
2374
2452
|
{
|
|
2375
2453
|
// read virtual analog stick
|
|
2376
|
-
const sticks =
|
|
2454
|
+
const sticks = gamepadStickData[0] || (gamepadStickData[0] = []);
|
|
2377
2455
|
sticks[0] = vec2();
|
|
2378
2456
|
if (touchGamepadAnalog)
|
|
2379
2457
|
sticks[0] = applyDeadZones(touchGamepadStick);
|
|
@@ -2390,7 +2468,8 @@ function gamepadsUpdate()
|
|
|
2390
2468
|
for (let i=10; i--;)
|
|
2391
2469
|
{
|
|
2392
2470
|
const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
|
|
2393
|
-
|
|
2471
|
+
const wasDown = gamepadIsDown(j,0);
|
|
2472
|
+
data[j] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
2394
2473
|
}
|
|
2395
2474
|
}
|
|
2396
2475
|
}
|
|
@@ -2410,7 +2489,7 @@ function gamepadsUpdate()
|
|
|
2410
2489
|
// get or create gamepad data
|
|
2411
2490
|
const gamepad = gamepads[i];
|
|
2412
2491
|
const data = inputData[i+1] || (inputData[i+1] = []);
|
|
2413
|
-
const sticks =
|
|
2492
|
+
const sticks = gamepadStickData[i] || (gamepadStickData[i] = []);
|
|
2414
2493
|
|
|
2415
2494
|
if (gamepad)
|
|
2416
2495
|
{
|
|
@@ -2449,28 +2528,44 @@ function gamepadsUpdate()
|
|
|
2449
2528
|
* @param {Number|Array} [pattern] - single value in ms or vibration interval array
|
|
2450
2529
|
* @memberof Input */
|
|
2451
2530
|
function vibrate(pattern=100)
|
|
2452
|
-
{ vibrateEnable && navigator && navigator.vibrate && navigator.vibrate(pattern); }
|
|
2531
|
+
{ vibrateEnable && !headlessMode && navigator && navigator.vibrate && navigator.vibrate(pattern); }
|
|
2453
2532
|
|
|
2454
2533
|
/** Cancel any ongoing vibration
|
|
2455
2534
|
* @memberof Input */
|
|
2456
2535
|
function vibrateStop() { vibrate(0); }
|
|
2457
2536
|
|
|
2458
2537
|
///////////////////////////////////////////////////////////////////////////////
|
|
2459
|
-
// Touch input
|
|
2538
|
+
// Touch input & virtual on screen gamepad
|
|
2460
2539
|
|
|
2461
2540
|
/** True if a touch device has been detected
|
|
2462
2541
|
* @memberof Input */
|
|
2463
|
-
const isTouchDevice = window.ontouchstart !== undefined;
|
|
2542
|
+
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
2543
|
+
|
|
2544
|
+
// touch gamepad internal variables
|
|
2545
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
2464
2546
|
|
|
2465
2547
|
// try to enable touch mouse
|
|
2466
|
-
|
|
2548
|
+
function touchInputInit()
|
|
2467
2549
|
{
|
|
2550
|
+
// add non passive touch event listeners
|
|
2551
|
+
let handleTouch = handleTouchDefault;
|
|
2552
|
+
if (touchGamepadEnable)
|
|
2553
|
+
{
|
|
2554
|
+
// touch input internal variables
|
|
2555
|
+
handleTouch = handleTouchGamepad;
|
|
2556
|
+
touchGamepadButtons = [];
|
|
2557
|
+
touchGamepadStick = vec2();
|
|
2558
|
+
}
|
|
2559
|
+
document.addEventListener('touchstart', (e) => handleTouch(e), { passive: false });
|
|
2560
|
+
document.addEventListener('touchmove', (e) => handleTouch(e), { passive: false });
|
|
2561
|
+
document.addEventListener('touchend', (e) => handleTouch(e), { passive: false });
|
|
2562
|
+
|
|
2468
2563
|
// override mouse events
|
|
2469
|
-
let wasTouching;
|
|
2470
2564
|
onmousedown = onmouseup = ()=> 0;
|
|
2471
2565
|
|
|
2472
2566
|
// handle all touch events the same way
|
|
2473
|
-
|
|
2567
|
+
let wasTouching;
|
|
2568
|
+
function handleTouchDefault(e)
|
|
2474
2569
|
{
|
|
2475
2570
|
// fix stalled audio requiring user interaction
|
|
2476
2571
|
if (soundEnable && audioContext && audioContext.state != 'running')
|
|
@@ -2499,27 +2594,14 @@ if (isTouchDevice)
|
|
|
2499
2594
|
// must return true so the document will get focus
|
|
2500
2595
|
return true;
|
|
2501
2596
|
}
|
|
2502
|
-
}
|
|
2503
2597
|
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
// touch input internal variables
|
|
2508
|
-
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
2509
|
-
|
|
2510
|
-
// create the touch gamepad, called automatically by the engine
|
|
2511
|
-
function createTouchGamepad()
|
|
2512
|
-
{
|
|
2513
|
-
// touch input internal variables
|
|
2514
|
-
touchGamepadButtons = [];
|
|
2515
|
-
touchGamepadStick = vec2();
|
|
2516
|
-
|
|
2517
|
-
const touchHandler = ontouchstart;
|
|
2518
|
-
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
2598
|
+
// special handling for virtual gamepad mode
|
|
2599
|
+
function handleTouchGamepad(e)
|
|
2519
2600
|
{
|
|
2520
2601
|
// clear touch gamepad input
|
|
2521
2602
|
touchGamepadStick = vec2();
|
|
2522
2603
|
touchGamepadButtons = [];
|
|
2604
|
+
isUsingGamepad = true;
|
|
2523
2605
|
|
|
2524
2606
|
const touching = e.touches.length;
|
|
2525
2607
|
if (touching)
|
|
@@ -2560,9 +2642,8 @@ function createTouchGamepad()
|
|
|
2560
2642
|
}
|
|
2561
2643
|
}
|
|
2562
2644
|
|
|
2563
|
-
// call default touch handler
|
|
2564
|
-
|
|
2565
|
-
isUsingGamepad = true;
|
|
2645
|
+
// call default touch handler so normal touch events still work
|
|
2646
|
+
handleTouchDefault(e);
|
|
2566
2647
|
|
|
2567
2648
|
// must return true so the document will get focus
|
|
2568
2649
|
return true;
|
|
@@ -2581,32 +2662,33 @@ function touchGamepadRender()
|
|
|
2581
2662
|
return;
|
|
2582
2663
|
|
|
2583
2664
|
// setup the canvas
|
|
2584
|
-
overlayContext
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2665
|
+
const context = overlayContext;
|
|
2666
|
+
context.save();
|
|
2667
|
+
context.globalAlpha = alpha*touchGamepadAlpha;
|
|
2668
|
+
context.strokeStyle = '#fff';
|
|
2669
|
+
context.lineWidth = 3;
|
|
2588
2670
|
|
|
2589
2671
|
// draw left analog stick
|
|
2590
|
-
|
|
2591
|
-
|
|
2672
|
+
context.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
|
|
2673
|
+
context.beginPath();
|
|
2592
2674
|
|
|
2593
2675
|
const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2594
2676
|
if (touchGamepadAnalog) // draw circle shaped gamepad
|
|
2595
2677
|
{
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2678
|
+
context.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
|
|
2679
|
+
context.fill();
|
|
2680
|
+
context.stroke();
|
|
2599
2681
|
}
|
|
2600
2682
|
else // draw cross shaped gamepad
|
|
2601
2683
|
{
|
|
2602
2684
|
for(let i=10; i--;)
|
|
2603
2685
|
{
|
|
2604
2686
|
const angle = i*PI/4;
|
|
2605
|
-
|
|
2606
|
-
i%2 &&
|
|
2607
|
-
i==1 &&
|
|
2687
|
+
context.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
|
|
2688
|
+
i%2 && context.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
|
|
2689
|
+
i==1 && context.fill();
|
|
2608
2690
|
}
|
|
2609
|
-
|
|
2691
|
+
context.stroke();
|
|
2610
2692
|
}
|
|
2611
2693
|
|
|
2612
2694
|
// draw right face buttons
|
|
@@ -2614,15 +2696,15 @@ function touchGamepadRender()
|
|
|
2614
2696
|
for (let i=4; i--;)
|
|
2615
2697
|
{
|
|
2616
2698
|
const pos = rightCenter.add(vec2().setDirection(i, touchGamepadSize/2));
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2699
|
+
context.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
|
|
2700
|
+
context.beginPath();
|
|
2701
|
+
context.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
|
|
2702
|
+
context.fill();
|
|
2703
|
+
context.stroke();
|
|
2622
2704
|
}
|
|
2623
2705
|
|
|
2624
2706
|
// set canvas back to normal
|
|
2625
|
-
|
|
2707
|
+
context.restore();
|
|
2626
2708
|
}
|
|
2627
2709
|
/**
|
|
2628
2710
|
* LittleJS Audio System
|
|
@@ -2657,7 +2739,7 @@ class Sound
|
|
|
2657
2739
|
*/
|
|
2658
2740
|
constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
|
|
2659
2741
|
{
|
|
2660
|
-
if (!soundEnable) return;
|
|
2742
|
+
if (!soundEnable || headlessMode) return;
|
|
2661
2743
|
|
|
2662
2744
|
/** @property {Number} - World space max range of sound, will not play if camera is farther away */
|
|
2663
2745
|
this.range = range;
|
|
@@ -2671,8 +2753,8 @@ class Sound
|
|
|
2671
2753
|
if (zzfxSound)
|
|
2672
2754
|
{
|
|
2673
2755
|
// generate zzfx sound now for fast playback
|
|
2674
|
-
|
|
2675
|
-
zzfxSound[1]
|
|
2756
|
+
const defaultRandomness = .05;
|
|
2757
|
+
this.randomness = zzfxSound[1] || defaultRandomness;
|
|
2676
2758
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
2677
2759
|
this.sampleRate = zzfxR;
|
|
2678
2760
|
}
|
|
@@ -2688,7 +2770,7 @@ class Sound
|
|
|
2688
2770
|
*/
|
|
2689
2771
|
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false)
|
|
2690
2772
|
{
|
|
2691
|
-
if (!soundEnable || !this.sampleChannels) return;
|
|
2773
|
+
if (!soundEnable || !this.sampleChannels || headlessMode) return;
|
|
2692
2774
|
|
|
2693
2775
|
let pan;
|
|
2694
2776
|
if (pos)
|
|
@@ -2771,7 +2853,9 @@ class SoundWave extends Sound
|
|
|
2771
2853
|
super(undefined, range, taper);
|
|
2772
2854
|
this.randomness = randomness;
|
|
2773
2855
|
|
|
2774
|
-
if (!soundEnable) return;
|
|
2856
|
+
if (!soundEnable || headlessMode) return;
|
|
2857
|
+
if (!audioContext)
|
|
2858
|
+
audioContext = new AudioContext; // create audio context
|
|
2775
2859
|
|
|
2776
2860
|
fetch(filename)
|
|
2777
2861
|
.then(response => response.arrayBuffer())
|
|
@@ -2825,7 +2909,7 @@ class Music extends Sound
|
|
|
2825
2909
|
{
|
|
2826
2910
|
super(undefined);
|
|
2827
2911
|
|
|
2828
|
-
if (!soundEnable) return;
|
|
2912
|
+
if (!soundEnable || headlessMode) return;
|
|
2829
2913
|
this.randomness = 0;
|
|
2830
2914
|
this.sampleChannels = zzfxM(...zzfxMusic);
|
|
2831
2915
|
this.sampleRate = zzfxR;
|
|
@@ -2848,7 +2932,7 @@ class Music extends Sound
|
|
|
2848
2932
|
* @memberof Audio */
|
|
2849
2933
|
function playAudioFile(filename, volume=1, loop=false)
|
|
2850
2934
|
{
|
|
2851
|
-
if (!soundEnable) return;
|
|
2935
|
+
if (!soundEnable || headlessMode) return;
|
|
2852
2936
|
|
|
2853
2937
|
const audio = new Audio(filename);
|
|
2854
2938
|
audio.volume = soundVolume * volume;
|
|
@@ -2867,7 +2951,7 @@ function playAudioFile(filename, volume=1, loop=false)
|
|
|
2867
2951
|
* @memberof Audio */
|
|
2868
2952
|
function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
2869
2953
|
{
|
|
2870
|
-
if (!soundEnable || !speechSynthesis) return;
|
|
2954
|
+
if (!soundEnable || !speechSynthesis || headlessMode) return;
|
|
2871
2955
|
|
|
2872
2956
|
// common languages (not supported by all browsers)
|
|
2873
2957
|
// en - english, it - italian, fr - french, de - german, es - spanish
|
|
@@ -2900,7 +2984,7 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
2900
2984
|
/** Audio context used by the engine
|
|
2901
2985
|
* @type {AudioContext}
|
|
2902
2986
|
* @memberof Audio */
|
|
2903
|
-
let audioContext
|
|
2987
|
+
let audioContext;
|
|
2904
2988
|
|
|
2905
2989
|
/** Keep track if audio was suspended when last sound was played
|
|
2906
2990
|
* @type {Boolean}
|
|
@@ -2918,7 +3002,9 @@ let audioSuspended = false;
|
|
|
2918
3002
|
* @memberof Audio */
|
|
2919
3003
|
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR)
|
|
2920
3004
|
{
|
|
2921
|
-
if (!soundEnable) return;
|
|
3005
|
+
if (!soundEnable || headlessMode) return;
|
|
3006
|
+
if (!audioContext)
|
|
3007
|
+
audioContext = new AudioContext; // create audio context
|
|
2922
3008
|
|
|
2923
3009
|
// prevent sounds from building up if they can't be played
|
|
2924
3010
|
const audioWasSuspended = audioSuspended;
|
|
@@ -2964,7 +3050,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
2964
3050
|
* @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
|
|
2965
3051
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
2966
3052
|
* @memberof Audio */
|
|
2967
|
-
function zzfx(...zzfxSound) { return
|
|
3053
|
+
function zzfx(...zzfxSound) { return new Sound(zzfxSound).play(); }
|
|
2968
3054
|
|
|
2969
3055
|
/** Sample rate used for all ZzFX sounds
|
|
2970
3056
|
* @default 44100
|
|
@@ -2973,7 +3059,7 @@ const zzfxR = 44100;
|
|
|
2973
3059
|
|
|
2974
3060
|
/** Generate samples for a ZzFX sound
|
|
2975
3061
|
* @param {Number} [volume] - Volume scale (percent)
|
|
2976
|
-
* @param {Number} [randomness] -
|
|
3062
|
+
* @param {Number} [randomness] - Unused in this fuction, handled by Sound class
|
|
2977
3063
|
* @param {Number} [frequency] - Frequency of sound (Hz)
|
|
2978
3064
|
* @param {Number} [attack] - Attack time, how fast sound starts (seconds)
|
|
2979
3065
|
* @param {Number} [sustain] - Sustain time, how long sound holds (seconds)
|
|
@@ -2999,17 +3085,18 @@ const zzfxR = 44100;
|
|
|
2999
3085
|
function zzfxG
|
|
3000
3086
|
(
|
|
3001
3087
|
// parameters
|
|
3002
|
-
volume = 1, randomness =
|
|
3088
|
+
volume = 1, randomness = 0, frequency = 220, attack = 0, sustain = 0,
|
|
3003
3089
|
release = .1, shape = 0, shapeCurve = 1, slide = 0, deltaSlide = 0,
|
|
3004
3090
|
pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, modulation = 0,
|
|
3005
3091
|
bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0, filter = 0
|
|
3006
3092
|
)
|
|
3007
3093
|
{
|
|
3094
|
+
// LJS Note: ZZFX modded so randomness is handled by Sound class
|
|
3095
|
+
|
|
3008
3096
|
// init parameters
|
|
3009
3097
|
let PI2 = PI*2, sampleRate = zzfxR,
|
|
3010
3098
|
startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
|
|
3011
|
-
startFrequency = frequency *=
|
|
3012
|
-
rand(1 + randomness, 1-randomness) * PI2 / sampleRate,
|
|
3099
|
+
startFrequency = frequency *= PI2 / sampleRate,
|
|
3013
3100
|
b = [], t = 0, tm = 0, i = 0, j = 1, r = 0, c = 0, s = 0, f, length,
|
|
3014
3101
|
|
|
3015
3102
|
// biquad LP/HP filter
|
|
@@ -3031,7 +3118,6 @@ function zzfxG
|
|
|
3031
3118
|
pitchJump *= PI2 / sampleRate;
|
|
3032
3119
|
pitchJumpTime *= sampleRate;
|
|
3033
3120
|
repeatTime = repeatTime * sampleRate | 0;
|
|
3034
|
-
volume *= soundVolume;
|
|
3035
3121
|
|
|
3036
3122
|
// generate waveform
|
|
3037
3123
|
for(length = attack + decay + sustain + release + delay | 0;
|
|
@@ -3268,7 +3354,7 @@ function tileCollisionTest(pos, size=vec2(), object)
|
|
|
3268
3354
|
}
|
|
3269
3355
|
}
|
|
3270
3356
|
|
|
3271
|
-
/** Return the center of tile
|
|
3357
|
+
/** Return the center of first tile hit (does not return the exact intersection)
|
|
3272
3358
|
* @param {Vector2} posStart
|
|
3273
3359
|
* @param {Vector2} posEnd
|
|
3274
3360
|
* @param {EngineObject} [object]
|
|
@@ -3377,7 +3463,7 @@ class TileLayer extends EngineObject
|
|
|
3377
3463
|
|
|
3378
3464
|
/** @property {HTMLCanvasElement} - The canvas used by this tile layer */
|
|
3379
3465
|
this.canvas = document.createElement('canvas');
|
|
3380
|
-
/** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
3466
|
+
/** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
3381
3467
|
this.context = this.canvas.getContext('2d');
|
|
3382
3468
|
/** @property {Vector2} - How much to scale this layer when rendered */
|
|
3383
3469
|
this.scale = scale;
|
|
@@ -3388,6 +3474,17 @@ class TileLayer extends EngineObject
|
|
|
3388
3474
|
this.data = [];
|
|
3389
3475
|
for (let j = this.size.area(); j--;)
|
|
3390
3476
|
this.data.push(new TileLayerData);
|
|
3477
|
+
|
|
3478
|
+
if (headlessMode)
|
|
3479
|
+
{
|
|
3480
|
+
// disable rendering
|
|
3481
|
+
this.redraw = () => {};
|
|
3482
|
+
this.render = () => {};
|
|
3483
|
+
this.redrawStart = () => {};
|
|
3484
|
+
this.redrawEnd = () => {};
|
|
3485
|
+
this.drawTileData = () => {};
|
|
3486
|
+
this.drawCanvas2D = () => {};
|
|
3487
|
+
}
|
|
3391
3488
|
}
|
|
3392
3489
|
|
|
3393
3490
|
/** Set data at a given position in the array
|
|
@@ -3418,7 +3515,7 @@ class TileLayer extends EngineObject
|
|
|
3418
3515
|
ASSERT(mainContext != this.context, 'must call redrawEnd() after drawing tiles');
|
|
3419
3516
|
|
|
3420
3517
|
// flush and copy gl canvas because tile canvas does not use webgl
|
|
3421
|
-
|
|
3518
|
+
!glOverlay && !this.isOverlay && glCopyToContext(mainContext);
|
|
3422
3519
|
|
|
3423
3520
|
// draw the entire cached level onto the canvas
|
|
3424
3521
|
const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
|
|
@@ -3464,15 +3561,18 @@ class TileLayer extends EngineObject
|
|
|
3464
3561
|
mainCanvas.height = mainCanvasSize.y;
|
|
3465
3562
|
}
|
|
3466
3563
|
|
|
3467
|
-
//
|
|
3468
|
-
|
|
3564
|
+
// disable smoothing for pixel art
|
|
3565
|
+
this.context.imageSmoothingEnabled = !canvasPixelated;
|
|
3566
|
+
|
|
3567
|
+
// setup gl rendering if enabled
|
|
3568
|
+
glPreRender();
|
|
3469
3569
|
}
|
|
3470
3570
|
|
|
3471
3571
|
/** Call to end the redraw process */
|
|
3472
3572
|
redrawEnd()
|
|
3473
3573
|
{
|
|
3474
3574
|
ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
|
|
3475
|
-
|
|
3575
|
+
glCopyToContext(mainContext, true);
|
|
3476
3576
|
//debugSaveCanvas(this.canvas);
|
|
3477
3577
|
|
|
3478
3578
|
// set stuff back to normal
|
|
@@ -3797,20 +3897,21 @@ class ParticleEmitter extends EngineObject
|
|
|
3797
3897
|
class Particle extends EngineObject
|
|
3798
3898
|
{
|
|
3799
3899
|
/**
|
|
3800
|
-
* Create a particle with the
|
|
3801
|
-
*
|
|
3802
|
-
* @param {
|
|
3803
|
-
* @param {
|
|
3804
|
-
* @param {
|
|
3805
|
-
* @param {Color}
|
|
3806
|
-
* @param {
|
|
3807
|
-
* @param {Number}
|
|
3808
|
-
* @param {Number}
|
|
3809
|
-
* @param {Number}
|
|
3810
|
-
* @param {
|
|
3811
|
-
* @param {
|
|
3900
|
+
* Create a particle with the passed in settings
|
|
3901
|
+
* Typically this is created automatically by a ParticleEmitter
|
|
3902
|
+
* @param {Vector2} position - World space position of the particle
|
|
3903
|
+
* @param {TileInfo} tileInfo - Tile info to render particles
|
|
3904
|
+
* @param {Number} angle - Angle to rotate the particle
|
|
3905
|
+
* @param {Color} colorStart - Color at start of life
|
|
3906
|
+
* @param {Color} colorEnd - Color at end of life
|
|
3907
|
+
* @param {Number} lifeTime - How long to live for
|
|
3908
|
+
* @param {Number} sizeStart - Size at start of life
|
|
3909
|
+
* @param {Number} sizeEnd - Size at end of life
|
|
3910
|
+
* @param {Number} fadeRate - How quick to fade in/out
|
|
3911
|
+
* @param {Boolean} additive - Does it use additive blend mode
|
|
3912
|
+
* @param {Number} trailScale - If a trail, how long to make it
|
|
3812
3913
|
* @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
|
|
3813
|
-
* @param {Function}
|
|
3914
|
+
* @param {Function} [destroyCallback] - Callback when particle dies
|
|
3814
3915
|
*/
|
|
3815
3916
|
constructor(position, tileInfo, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, fadeRate, additive, trailScale, localSpaceEmitter, destroyCallback
|
|
3816
3917
|
)
|
|
@@ -4219,6 +4320,8 @@ let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData,
|
|
|
4219
4320
|
// Initalize WebGL, called automatically by the engine
|
|
4220
4321
|
function glInit()
|
|
4221
4322
|
{
|
|
4323
|
+
if (!glEnable || headlessMode) return;
|
|
4324
|
+
|
|
4222
4325
|
// create the canvas and textures
|
|
4223
4326
|
glCanvas = document.createElement('canvas');
|
|
4224
4327
|
glContext = glCanvas.getContext('webgl2');
|
|
@@ -4231,11 +4334,11 @@ function glInit()
|
|
|
4231
4334
|
'#version 300 es\n' + // specify GLSL ES version
|
|
4232
4335
|
'precision highp float;'+ // use highp for better accuracy
|
|
4233
4336
|
'uniform mat4 m;'+ // transform matrix
|
|
4234
|
-
'in vec2 g;'+ // geometry
|
|
4235
|
-
'in vec4 p,u,c,a;'+ // position/size, uvs, color, additiveColor
|
|
4236
|
-
'in float r;'+ // rotation
|
|
4237
|
-
'out vec2 v;'+ //
|
|
4238
|
-
'out vec4 d,e;'+ //
|
|
4337
|
+
'in vec2 g;'+ // in: geometry
|
|
4338
|
+
'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
|
|
4339
|
+
'in float r;'+ // in: rotation
|
|
4340
|
+
'out vec2 v;'+ // out: uv
|
|
4341
|
+
'out vec4 d,e;'+ // out: color, additiveColor
|
|
4239
4342
|
'void main(){'+ // shader entry point
|
|
4240
4343
|
'vec2 s=(g-.5)*p.zw;'+ // get size offset
|
|
4241
4344
|
'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
|
|
@@ -4245,10 +4348,10 @@ function glInit()
|
|
|
4245
4348
|
,
|
|
4246
4349
|
'#version 300 es\n' + // specify GLSL ES version
|
|
4247
4350
|
'precision highp float;'+ // use highp for better accuracy
|
|
4248
|
-
'in vec2 v;'+ // uv
|
|
4249
|
-
'in vec4 d,e;'+ // color, additiveColor
|
|
4250
4351
|
'uniform sampler2D s;'+ // texture
|
|
4251
|
-
'
|
|
4352
|
+
'in vec2 v;'+ // in: uv
|
|
4353
|
+
'in vec4 d,e;'+ // in: color, additiveColor
|
|
4354
|
+
'out vec4 c;'+ // out: color
|
|
4252
4355
|
'void main(){'+ // shader entry point
|
|
4253
4356
|
'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
|
|
4254
4357
|
'}' // end of shader
|
|
@@ -4270,9 +4373,11 @@ function glInit()
|
|
|
4270
4373
|
// Setup render each frame, called automatically by engine
|
|
4271
4374
|
function glPreRender()
|
|
4272
4375
|
{
|
|
4376
|
+
if (!glEnable || headlessMode) return;
|
|
4377
|
+
|
|
4273
4378
|
// clear and set to same size as main canvas
|
|
4274
4379
|
glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
|
|
4275
|
-
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
4380
|
+
//glContext.clear(gl_COLOR_BUFFER_BIT); // auto cleared when size is set
|
|
4276
4381
|
|
|
4277
4382
|
// set up the shader
|
|
4278
4383
|
glContext.useProgram(glShader);
|
|
@@ -4306,12 +4411,12 @@ function glPreRender()
|
|
|
4306
4411
|
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
4307
4412
|
const p = vec2(-1).subtract(cameraPos.multiply(s));
|
|
4308
4413
|
glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), false,
|
|
4309
|
-
|
|
4414
|
+
[
|
|
4310
4415
|
s.x, 0, 0, 0,
|
|
4311
4416
|
0, s.y, 0, 0,
|
|
4312
4417
|
1, 1, 1, 1,
|
|
4313
4418
|
p.x, p.y, 0, 0
|
|
4314
|
-
]
|
|
4419
|
+
]
|
|
4315
4420
|
);
|
|
4316
4421
|
}
|
|
4317
4422
|
|
|
@@ -4322,7 +4427,7 @@ function glPreRender()
|
|
|
4322
4427
|
function glSetTexture(texture)
|
|
4323
4428
|
{
|
|
4324
4429
|
// must flush cache with the old texture to set a new one
|
|
4325
|
-
if (texture == glActiveTexture)
|
|
4430
|
+
if (headlessMode || texture == glActiveTexture)
|
|
4326
4431
|
return;
|
|
4327
4432
|
|
|
4328
4433
|
glFlush();
|
|
@@ -4382,7 +4487,6 @@ function glCreateTexture(image)
|
|
|
4382
4487
|
const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
|
|
4383
4488
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
|
|
4384
4489
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
|
|
4385
|
-
|
|
4386
4490
|
return texture;
|
|
4387
4491
|
}
|
|
4388
4492
|
|
|
@@ -4406,12 +4510,12 @@ function glFlush()
|
|
|
4406
4510
|
}
|
|
4407
4511
|
|
|
4408
4512
|
/** Draw any sprites still in the buffer, copy to main canvas and clear
|
|
4409
|
-
* @param {CanvasRenderingContext2D} context
|
|
4513
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
|
4410
4514
|
* @param {Boolean} [forceDraw]
|
|
4411
4515
|
* @memberof WebGL */
|
|
4412
4516
|
function glCopyToContext(context, forceDraw=false)
|
|
4413
4517
|
{
|
|
4414
|
-
if (!glInstanceCount && !forceDraw) return;
|
|
4518
|
+
if (!glEnable || !glInstanceCount && !forceDraw) return;
|
|
4415
4519
|
|
|
4416
4520
|
glFlush();
|
|
4417
4521
|
|
|
@@ -4468,7 +4572,7 @@ let glPostShader, glPostTexture, glPostIncludeOverlay;
|
|
|
4468
4572
|
function glInitPostProcess(shaderCode, includeOverlay=false)
|
|
4469
4573
|
{
|
|
4470
4574
|
ASSERT(!glPostShader, 'can only have 1 post effects shader');
|
|
4471
|
-
|
|
4575
|
+
if (headlessMode) return;
|
|
4472
4576
|
if (!shaderCode) // default shader pass through
|
|
4473
4577
|
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
|
|
4474
4578
|
|
|
@@ -4507,8 +4611,7 @@ function glInitPostProcess(shaderCode, includeOverlay=false)
|
|
|
4507
4611
|
// Render the post processing shader, called automatically by the engine
|
|
4508
4612
|
function glRenderPostProcess()
|
|
4509
4613
|
{
|
|
4510
|
-
if (!glPostShader)
|
|
4511
|
-
return;
|
|
4614
|
+
if (!glPostShader || headlessMode) return;
|
|
4512
4615
|
|
|
4513
4616
|
// prepare to render post process shader
|
|
4514
4617
|
if (glEnable)
|
|
@@ -4614,7 +4717,7 @@ const engineName = 'LittleJS';
|
|
|
4614
4717
|
* @type {String}
|
|
4615
4718
|
* @default
|
|
4616
4719
|
* @memberof Engine */
|
|
4617
|
-
const engineVersion = '1.9.
|
|
4720
|
+
const engineVersion = '1.9.6';
|
|
4618
4721
|
|
|
4619
4722
|
/** Frames per second to update
|
|
4620
4723
|
* @type {Number}
|
|
@@ -4681,6 +4784,19 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4681
4784
|
{
|
|
4682
4785
|
ASSERT(Array.isArray(imageSources), 'pass in images as array');
|
|
4683
4786
|
|
|
4787
|
+
// Called automatically by engine to setup render system
|
|
4788
|
+
function enginePreRender()
|
|
4789
|
+
{
|
|
4790
|
+
// save canvas size
|
|
4791
|
+
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
4792
|
+
|
|
4793
|
+
// disable smoothing for pixel art
|
|
4794
|
+
mainContext.imageSmoothingEnabled = !canvasPixelated;
|
|
4795
|
+
|
|
4796
|
+
// setup gl rendering if enabled
|
|
4797
|
+
glPreRender();
|
|
4798
|
+
}
|
|
4799
|
+
|
|
4684
4800
|
// internal update loop for engine
|
|
4685
4801
|
function engineUpdate(frameTimeMS=0)
|
|
4686
4802
|
{
|
|
@@ -4697,11 +4813,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4697
4813
|
frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
|
|
4698
4814
|
if (!debugSpeedUp)
|
|
4699
4815
|
frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp in case of slow framerate
|
|
4816
|
+
|
|
4700
4817
|
updateCanvas();
|
|
4701
4818
|
|
|
4702
4819
|
if (paused)
|
|
4703
4820
|
{
|
|
4704
|
-
//
|
|
4821
|
+
// update object transforms even when paused
|
|
4822
|
+
for (const o of engineObjects)
|
|
4823
|
+
o.parent || o.updateTransforms();
|
|
4705
4824
|
inputUpdate();
|
|
4706
4825
|
debugUpdate();
|
|
4707
4826
|
gameUpdatePost();
|
|
@@ -4713,7 +4832,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4713
4832
|
let deltaSmooth = 0;
|
|
4714
4833
|
if (frameTimeBufferMS < 0 && frameTimeBufferMS > -9)
|
|
4715
4834
|
{
|
|
4716
|
-
// force
|
|
4835
|
+
// force at least one update each frame since it is waiting for refresh
|
|
4717
4836
|
deltaSmooth = frameTimeBufferMS;
|
|
4718
4837
|
frameTimeBufferMS = 0;
|
|
4719
4838
|
}
|
|
@@ -4738,34 +4857,37 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4738
4857
|
// add the time smoothing back in
|
|
4739
4858
|
frameTimeBufferMS += deltaSmooth;
|
|
4740
4859
|
}
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
enginePreRender();
|
|
4744
|
-
gameRender();
|
|
4745
|
-
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
4746
|
-
for (const o of engineObjects)
|
|
4747
|
-
o.destroyed || o.render();
|
|
4748
|
-
gameRenderPost();
|
|
4749
|
-
glRenderPostProcess();
|
|
4750
|
-
medalsRender();
|
|
4751
|
-
touchGamepadRender();
|
|
4752
|
-
debugRender();
|
|
4753
|
-
glEnable && glCopyToContext(mainContext);
|
|
4754
|
-
|
|
4755
|
-
if (showWatermark)
|
|
4860
|
+
|
|
4861
|
+
if (!headlessMode)
|
|
4756
4862
|
{
|
|
4757
|
-
//
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4863
|
+
// render sort then render while removing destroyed objects
|
|
4864
|
+
enginePreRender();
|
|
4865
|
+
gameRender();
|
|
4866
|
+
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
4867
|
+
for (const o of engineObjects)
|
|
4868
|
+
o.destroyed || o.render();
|
|
4869
|
+
gameRenderPost();
|
|
4870
|
+
glRenderPostProcess();
|
|
4871
|
+
medalsRender();
|
|
4872
|
+
touchGamepadRender();
|
|
4873
|
+
debugRender();
|
|
4874
|
+
glCopyToContext(mainContext);
|
|
4875
|
+
|
|
4876
|
+
if (showWatermark)
|
|
4877
|
+
{
|
|
4878
|
+
// update fps
|
|
4879
|
+
overlayContext.textAlign = 'right';
|
|
4880
|
+
overlayContext.textBaseline = 'top';
|
|
4881
|
+
overlayContext.font = '1em monospace';
|
|
4882
|
+
overlayContext.fillStyle = '#000';
|
|
4883
|
+
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
4884
|
+
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
4885
|
+
+ (glEnable ? ' GL' : ' 2D') ;
|
|
4886
|
+
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
4887
|
+
overlayContext.fillStyle = '#fff';
|
|
4888
|
+
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
4889
|
+
drawCount = 0;
|
|
4890
|
+
}
|
|
4769
4891
|
}
|
|
4770
4892
|
|
|
4771
4893
|
requestAnimationFrame(engineUpdate);
|
|
@@ -4773,6 +4895,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4773
4895
|
|
|
4774
4896
|
function updateCanvas()
|
|
4775
4897
|
{
|
|
4898
|
+
if (headlessMode) return;
|
|
4899
|
+
|
|
4776
4900
|
if (canvasFixedSize.x)
|
|
4777
4901
|
{
|
|
4778
4902
|
// clear canvas and set fixed size
|
|
@@ -4800,8 +4924,20 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4800
4924
|
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
4801
4925
|
}
|
|
4802
4926
|
|
|
4927
|
+
function startEngine()
|
|
4928
|
+
{
|
|
4929
|
+
gameInit();
|
|
4930
|
+
engineUpdate();
|
|
4931
|
+
}
|
|
4932
|
+
|
|
4933
|
+
if (headlessMode)
|
|
4934
|
+
{
|
|
4935
|
+
startEngine();
|
|
4936
|
+
return;
|
|
4937
|
+
}
|
|
4938
|
+
|
|
4803
4939
|
// setup html
|
|
4804
|
-
|
|
4940
|
+
const styleBody =
|
|
4805
4941
|
'margin:0;overflow:hidden;' + // fill the window
|
|
4806
4942
|
'background:#000;' + // set background color
|
|
4807
4943
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
@@ -4813,8 +4949,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4813
4949
|
mainContext = mainCanvas.getContext('2d');
|
|
4814
4950
|
|
|
4815
4951
|
// init stuff and start engine
|
|
4952
|
+
inputInit();
|
|
4816
4953
|
debugInit();
|
|
4817
|
-
|
|
4954
|
+
glInit();
|
|
4818
4955
|
|
|
4819
4956
|
// create overlay canvas for hud to appear above gl canvas
|
|
4820
4957
|
document.body.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
@@ -4838,7 +4975,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4838
4975
|
}
|
|
4839
4976
|
image.src = src;
|
|
4840
4977
|
})
|
|
4841
|
-
)
|
|
4978
|
+
);
|
|
4842
4979
|
|
|
4843
4980
|
// draw splash screen
|
|
4844
4981
|
showSplashScreen && promises.push(new Promise(resolve =>
|
|
@@ -4855,25 +4992,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4855
4992
|
}));
|
|
4856
4993
|
|
|
4857
4994
|
// load all of the images
|
|
4858
|
-
Promise.all(promises).then(
|
|
4859
|
-
{
|
|
4860
|
-
// start the engine
|
|
4861
|
-
gameInit();
|
|
4862
|
-
engineUpdate();
|
|
4863
|
-
});
|
|
4864
|
-
}
|
|
4865
|
-
|
|
4866
|
-
// Called automatically by engine to setup render system
|
|
4867
|
-
function enginePreRender()
|
|
4868
|
-
{
|
|
4869
|
-
// save canvas size
|
|
4870
|
-
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
4871
|
-
|
|
4872
|
-
// disable smoothing for pixel art
|
|
4873
|
-
mainContext.imageSmoothingEnabled = !canvasPixelated;
|
|
4874
|
-
|
|
4875
|
-
// setup gl rendering if enabled
|
|
4876
|
-
glEnable && glPreRender();
|
|
4995
|
+
Promise.all(promises).then(startEngine);
|
|
4877
4996
|
}
|
|
4878
4997
|
|
|
4879
4998
|
/** Update each engine object, remove destroyed objects, and update time
|
|
@@ -4894,7 +5013,14 @@ function engineObjectsUpdate()
|
|
|
4894
5013
|
}
|
|
4895
5014
|
}
|
|
4896
5015
|
for (const o of engineObjects)
|
|
4897
|
-
|
|
5016
|
+
{
|
|
5017
|
+
// update top level objects
|
|
5018
|
+
if (!o.parent)
|
|
5019
|
+
{
|
|
5020
|
+
updateObject(o);
|
|
5021
|
+
o.updateTransforms();
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
4898
5024
|
|
|
4899
5025
|
// remove destroyed objects
|
|
4900
5026
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
@@ -4909,30 +5035,63 @@ function engineObjectsDestroy()
|
|
|
4909
5035
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
4910
5036
|
}
|
|
4911
5037
|
|
|
4912
|
-
/**
|
|
4913
|
-
* @param {Vector2} [pos] - Center of test area
|
|
5038
|
+
/** Collects all object within a given area
|
|
5039
|
+
* @param {Vector2} [pos] - Center of test area, or undefined for all objects
|
|
4914
5040
|
* @param {Number|Vector2} [size] - Radius of circle if float, rectangle size if Vector2
|
|
4915
|
-
* @param {Function} [callbackFunction] - Calls this function on every object that passes the test
|
|
4916
5041
|
* @param {Array} [objects=engineObjects] - List of objects to check
|
|
5042
|
+
* @return {Array} - List of collected objects
|
|
4917
5043
|
* @memberof Engine */
|
|
4918
|
-
function
|
|
5044
|
+
function engineObjectsCollect(pos, size, objects=engineObjects)
|
|
4919
5045
|
{
|
|
5046
|
+
const collectedObjects = [];
|
|
4920
5047
|
if (!pos) // all objects
|
|
4921
5048
|
{
|
|
4922
5049
|
for (const o of objects)
|
|
4923
|
-
|
|
5050
|
+
collectedObjects.push(o);
|
|
4924
5051
|
}
|
|
4925
|
-
else if (
|
|
5052
|
+
else if (size instanceof Vector2) // bounding box test
|
|
4926
5053
|
{
|
|
4927
5054
|
for (const o of objects)
|
|
4928
|
-
isOverlapping(pos, size, o.pos, o.size) &&
|
|
5055
|
+
isOverlapping(pos, size, o.pos, o.size) && collectedObjects.push(o);
|
|
4929
5056
|
}
|
|
4930
5057
|
else // circle test
|
|
4931
5058
|
{
|
|
4932
5059
|
const sizeSquared = size*size;
|
|
4933
5060
|
for (const o of objects)
|
|
4934
|
-
pos.distanceSquared(o.pos) < sizeSquared &&
|
|
5061
|
+
pos.distanceSquared(o.pos) < sizeSquared && collectedObjects.push(o);
|
|
4935
5062
|
}
|
|
5063
|
+
return collectedObjects;
|
|
5064
|
+
}
|
|
5065
|
+
|
|
5066
|
+
/** Triggers a callback for each object within a given area
|
|
5067
|
+
* @param {Vector2} [pos] - Center of test area, or undefined for all objects
|
|
5068
|
+
* @param {Number|Vector2} [size] - Radius of circle if float, rectangle size if Vector2
|
|
5069
|
+
* @param {Function} [callbackFunction] - Calls this function on every object that passes the test
|
|
5070
|
+
* @param {Array} [objects=engineObjects] - List of objects to check
|
|
5071
|
+
* @memberof Engine */
|
|
5072
|
+
function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
|
|
5073
|
+
{ engineObjectsCollect(pos, size, objects).forEach(o => callbackFunction(o)); }
|
|
5074
|
+
|
|
5075
|
+
/** Return a list of objects intersecting a ray
|
|
5076
|
+
* @param {Vector2} start
|
|
5077
|
+
* @param {Vector2} end
|
|
5078
|
+
* @param {Array} [objects=engineObjects] - List of objects to check
|
|
5079
|
+
* @return {Array} - List of objects hit
|
|
5080
|
+
* @memberof Engine */
|
|
5081
|
+
function engineObjectsRaycast(start, end, objects=engineObjects)
|
|
5082
|
+
{
|
|
5083
|
+
const hitObjects = [];
|
|
5084
|
+
for (const o of objects)
|
|
5085
|
+
{
|
|
5086
|
+
if (o.collideRaycast && isIntersecting(start, end, o.pos, o.size))
|
|
5087
|
+
{
|
|
5088
|
+
debugRaycast && debugRect(o.pos, o.size, '#f00');
|
|
5089
|
+
hitObjects.push(o);
|
|
5090
|
+
}
|
|
5091
|
+
}
|
|
5092
|
+
|
|
5093
|
+
debugRaycast && debugLine(start, end, hitObjects.length ? '#f00' : '#00f', .02);
|
|
5094
|
+
return hitObjects;
|
|
4936
5095
|
}
|
|
4937
5096
|
|
|
4938
5097
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -5020,16 +5179,16 @@ function drawEngineSplashScreen(t)
|
|
|
5020
5179
|
rect(37,14,9,6);
|
|
5021
5180
|
|
|
5022
5181
|
// big stack
|
|
5023
|
-
rect(50,20,10,-
|
|
5024
|
-
rect(50,20,6.5,-
|
|
5025
|
-
rect(50,20,3.5,-
|
|
5026
|
-
rect(50,20,10,-
|
|
5027
|
-
circle(55,2,11.4,.5,PI-.5,color(3,3))
|
|
5028
|
-
circle(55,2,11.4,.5,PI/2,color(3,2),1)
|
|
5029
|
-
circle(55,2,11.4,.5,PI-.5)
|
|
5030
|
-
rect(45,7,20,-7,color(0,2))
|
|
5031
|
-
rect(45,
|
|
5032
|
-
rect(45,
|
|
5182
|
+
rect(50,20,10,-8,color(0,1))
|
|
5183
|
+
rect(50,20,6.5,-8,color(0,2))
|
|
5184
|
+
rect(50,20,3.5,-8,color(0,3))
|
|
5185
|
+
rect(50,20,10,-8)
|
|
5186
|
+
circle(55,2,11.4,.5,PI-.5,color(3,3))
|
|
5187
|
+
circle(55,2,11.4,.5,PI/2,color(3,2),1)
|
|
5188
|
+
circle(55,2,11.4,.5,PI-.5)
|
|
5189
|
+
rect(45,7,20,-7,color(0,2))
|
|
5190
|
+
rect(45,-1,20,4,color(0,3))
|
|
5191
|
+
rect(45,-1,20,8)
|
|
5033
5192
|
|
|
5034
5193
|
// engine
|
|
5035
5194
|
for (let i=5; i--;)
|