littlejsengine 1.10.2 → 1.10.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -93
- package/dist/littlejs.d.ts +79 -17
- package/dist/littlejs.esm.js +179 -90
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +165 -90
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +165 -90
- package/examples/box2d/game.js +15 -1
- package/examples/box2d/gameObjects.js +10 -9
- package/examples/box2d/index.html +5 -5
- package/examples/box2d/scenes.js +4 -4
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/index.html +3 -3
- package/examples/breakoutTutorial/README.md +9 -7
- package/examples/breakoutTutorial/game.js +6 -6
- package/examples/breakoutTutorial/index.html +2 -2
- package/examples/electron/game.js +3 -0
- package/examples/electron/index.html +2 -2
- package/examples/htmlMenu/index.html +3 -3
- package/examples/js13k/game.js +3 -0
- package/examples/js13k/index.html +13 -13
- package/examples/module/game.js +3 -0
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +4 -1
- package/examples/platformer/game.js +21 -15
- package/examples/platformer/gameCharacter.js +15 -14
- package/examples/platformer/gameEffects.js +3 -4
- package/examples/platformer/gameLevel.js +18 -33
- package/examples/platformer/gameObjects.js +9 -14
- package/examples/platformer/index.html +8 -8
- package/examples/platformer/tiles.png +0 -0
- package/examples/puzzle/index.html +2 -2
- package/examples/starter/game.js +3 -0
- package/examples/starter/index.html +13 -13
- package/examples/stress/index.html +1 -1
- package/examples/typescript/game.js +2 -0
- package/examples/typescript/game.ts +4 -1
- package/examples/typescript/index.html +1 -1
- package/examples/uiSystem/game.js +23 -16
- package/examples/uiSystem/index.html +3 -14
- package/package.json +1 -1
- package/plugins/postProcess.js +1 -1
- package/plugins/uiSystem.js +84 -24
- package/src/engine.js +14 -22
- package/src/engineAudio.js +7 -13
- package/src/engineDraw.js +20 -19
- package/src/engineExport.js +14 -0
- package/src/engineInput.js +8 -6
- package/src/engineMedals.js +19 -5
- package/src/engineObject.js +9 -5
- package/src/engineTileLayer.js +10 -8
- package/src/engineUtilities.js +60 -10
- package/src/engineWebGL.js +17 -2
package/dist/littlejs.release.js
CHANGED
|
@@ -239,7 +239,8 @@ function formatTime(t) { return (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0); }
|
|
|
239
239
|
* @memberof Random */
|
|
240
240
|
function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valueB); }
|
|
241
241
|
|
|
242
|
-
/** Returns a floored random value the two values passed in
|
|
242
|
+
/** Returns a floored random value between the two values passed in
|
|
243
|
+
* The upper bound is exclusive. (If 2 is passed in, result will be 0 or 1)
|
|
243
244
|
* @param {Number} valueA
|
|
244
245
|
* @param {Number} [valueB]
|
|
245
246
|
* @return {Number}
|
|
@@ -368,18 +369,24 @@ class Vector2
|
|
|
368
369
|
* @param {Number} [y] - Y axis location */
|
|
369
370
|
constructor(x=0, y=0)
|
|
370
371
|
{
|
|
371
|
-
ASSERT(typeof x == 'number' && typeof y == 'number');
|
|
372
372
|
/** @property {Number} - X axis location */
|
|
373
373
|
this.x = x;
|
|
374
374
|
/** @property {Number} - Y axis location */
|
|
375
375
|
this.y = y;
|
|
376
|
+
ASSERT(this.isValid());
|
|
376
377
|
}
|
|
377
378
|
|
|
378
379
|
/** Sets values of this vector and returns self
|
|
379
380
|
* @param {Number} [x] - X axis location
|
|
380
381
|
* @param {Number} [y] - Y axis location
|
|
381
382
|
* @return {Vector2} */
|
|
382
|
-
set(x=0, y=0)
|
|
383
|
+
set(x=0, y=0)
|
|
384
|
+
{
|
|
385
|
+
this.x = x;
|
|
386
|
+
this.y = y;
|
|
387
|
+
ASSERT(this.isValid());
|
|
388
|
+
return this;
|
|
389
|
+
}
|
|
383
390
|
|
|
384
391
|
/** Returns a new vector that is a copy of this
|
|
385
392
|
* @return {Vector2} */
|
|
@@ -521,6 +528,7 @@ class Vector2
|
|
|
521
528
|
* @param {Number} [length] */
|
|
522
529
|
setDirection(direction, length=1)
|
|
523
530
|
{
|
|
531
|
+
direction = mod(direction, 4);
|
|
524
532
|
ASSERT(direction==0 || direction==1 || direction==2 || direction==3);
|
|
525
533
|
return vec2(direction%2 ? direction-1 ? -length : length : 0,
|
|
526
534
|
direction%2 ? 0 : direction ? -length : length);
|
|
@@ -570,6 +578,14 @@ class Vector2
|
|
|
570
578
|
if (debug)
|
|
571
579
|
return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`;
|
|
572
580
|
}
|
|
581
|
+
|
|
582
|
+
/** Checks if this is a valid vector
|
|
583
|
+
* @return {Boolean} */
|
|
584
|
+
isValid()
|
|
585
|
+
{
|
|
586
|
+
return typeof this.x == 'number' && !isNaN(this.x)
|
|
587
|
+
&& typeof this.y == 'number' && !isNaN(this.y);
|
|
588
|
+
}
|
|
573
589
|
}
|
|
574
590
|
|
|
575
591
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -630,6 +646,7 @@ class Color
|
|
|
630
646
|
this.b = b;
|
|
631
647
|
/** @property {Number} - Alpha */
|
|
632
648
|
this.a = a;
|
|
649
|
+
ASSERT(this.isValid());
|
|
633
650
|
}
|
|
634
651
|
|
|
635
652
|
/** Sets values of this color and returns self
|
|
@@ -639,7 +656,14 @@ class Color
|
|
|
639
656
|
* @param {Number} [a] - alpha
|
|
640
657
|
* @return {Color} */
|
|
641
658
|
set(r=1, g=1, b=1, a=1)
|
|
642
|
-
{
|
|
659
|
+
{
|
|
660
|
+
this.r = r;
|
|
661
|
+
this.g = g;
|
|
662
|
+
this.b = b;
|
|
663
|
+
this.a = a;
|
|
664
|
+
ASSERT(this.isValid());
|
|
665
|
+
return this;
|
|
666
|
+
}
|
|
643
667
|
|
|
644
668
|
/** Returns a new color that is a copy of this
|
|
645
669
|
* @return {Color} */
|
|
@@ -722,6 +746,7 @@ class Color
|
|
|
722
746
|
this.g = f(p, q, h);
|
|
723
747
|
this.b = f(p, q, h - 1/3);
|
|
724
748
|
this.a = a;
|
|
749
|
+
ASSERT(this.isValid());
|
|
725
750
|
return this;
|
|
726
751
|
}
|
|
727
752
|
|
|
@@ -749,7 +774,6 @@ class Color
|
|
|
749
774
|
else if (b == max)
|
|
750
775
|
h = (r - g) / d + 4;
|
|
751
776
|
}
|
|
752
|
-
|
|
753
777
|
return [h / 6, s, l, a];
|
|
754
778
|
}
|
|
755
779
|
|
|
@@ -782,11 +806,27 @@ class Color
|
|
|
782
806
|
* @return {Color} */
|
|
783
807
|
setHex(hex)
|
|
784
808
|
{
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
809
|
+
ASSERT(typeof hex == 'string' && hex[0] == '#');
|
|
810
|
+
ASSERT([4,5,7,9].includes(hex.length), 'Invalid hex');
|
|
811
|
+
|
|
812
|
+
if (hex.length < 6)
|
|
813
|
+
{
|
|
814
|
+
const fromHex = (c)=> clamp(parseInt(hex[c],16)/15);
|
|
815
|
+
this.r = fromHex(1);
|
|
816
|
+
this.g = fromHex(2),
|
|
817
|
+
this.b = fromHex(3);
|
|
818
|
+
this.a = hex.length == 5 ? fromHex(4) : 1;
|
|
819
|
+
}
|
|
820
|
+
else
|
|
821
|
+
{
|
|
822
|
+
const fromHex = (c)=> clamp(parseInt(hex.slice(c,c+2),16)/255);
|
|
823
|
+
this.r = fromHex(1);
|
|
824
|
+
this.g = fromHex(3),
|
|
825
|
+
this.b = fromHex(5);
|
|
826
|
+
this.a = hex.length == 9 ? fromHex(7) : 1;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
ASSERT(this.isValid());
|
|
790
830
|
return this;
|
|
791
831
|
}
|
|
792
832
|
|
|
@@ -800,6 +840,16 @@ class Color
|
|
|
800
840
|
const a = clamp(this.a)*255<<24;
|
|
801
841
|
return r + g + b + a;
|
|
802
842
|
}
|
|
843
|
+
|
|
844
|
+
/** Checks if this is a valid color
|
|
845
|
+
* @return {Boolean} */
|
|
846
|
+
isValid()
|
|
847
|
+
{
|
|
848
|
+
return typeof this.r == 'number' && !isNaN(this.r)
|
|
849
|
+
&& typeof this.g == 'number' && !isNaN(this.g)
|
|
850
|
+
&& typeof this.b == 'number' && !isNaN(this.b)
|
|
851
|
+
&& typeof this.a == 'number' && !isNaN(this.a);
|
|
852
|
+
}
|
|
803
853
|
}
|
|
804
854
|
|
|
805
855
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -1435,7 +1485,7 @@ class EngineObject
|
|
|
1435
1485
|
* @param {Color} [color=(1,1,1,1)] - Color to apply to tile when rendered
|
|
1436
1486
|
* @param {Number} [renderOrder] - Objects sorted by renderOrder before being rendered
|
|
1437
1487
|
*/
|
|
1438
|
-
constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color, renderOrder=0)
|
|
1488
|
+
constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color=new Color, renderOrder=0)
|
|
1439
1489
|
{
|
|
1440
1490
|
// set passed in params
|
|
1441
1491
|
ASSERT(isVector2(pos) && isVector2(size), 'ensure pos and size are vec2s');
|
|
@@ -1549,15 +1599,18 @@ class EngineObject
|
|
|
1549
1599
|
|
|
1550
1600
|
// apply physics
|
|
1551
1601
|
const oldPos = this.pos.copy();
|
|
1552
|
-
this.
|
|
1553
|
-
this.
|
|
1554
|
-
|
|
1602
|
+
this.velocity.x *= this.damping;
|
|
1603
|
+
this.velocity.y *= this.damping;
|
|
1604
|
+
if (this.mass) // dont apply gravity to static objects
|
|
1605
|
+
this.velocity.y += gravity * this.gravityScale;
|
|
1606
|
+
this.pos.x += this.velocity.x;
|
|
1607
|
+
this.pos.y += this.velocity.y;
|
|
1555
1608
|
this.angle += this.angleVelocity *= this.angleDamping;
|
|
1556
1609
|
|
|
1557
1610
|
// physics sanity checks
|
|
1558
1611
|
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
1559
1612
|
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
1560
|
-
if (!enablePhysicsSolver || !this.mass) // dont do collision for
|
|
1613
|
+
if (!enablePhysicsSolver || !this.mass) // dont do collision for static objects
|
|
1561
1614
|
return;
|
|
1562
1615
|
|
|
1563
1616
|
const wasMovingDown = this.velocity.y < 0;
|
|
@@ -1703,6 +1756,7 @@ class EngineObject
|
|
|
1703
1756
|
this.pos.x = oldPos.x;
|
|
1704
1757
|
this.velocity.x *= -this.elasticity;
|
|
1705
1758
|
}
|
|
1759
|
+
debugOverlay && debugPhysics && debugRect(this.pos, this.size, '#f00');
|
|
1706
1760
|
}
|
|
1707
1761
|
}
|
|
1708
1762
|
}
|
|
@@ -1917,7 +1971,7 @@ let drawCount;
|
|
|
1917
1971
|
* tile(2) // a tile at index 2 using the default tile size of 16
|
|
1918
1972
|
* tile(5, 8) // a tile at index 5 using a tile size of 8
|
|
1919
1973
|
* tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
|
|
1920
|
-
* tile(vec2(4,8), vec2(30,10)) // a tile at
|
|
1974
|
+
* tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
|
|
1921
1975
|
* @memberof Draw
|
|
1922
1976
|
*/
|
|
1923
1977
|
function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
|
|
@@ -2135,6 +2189,22 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
2135
2189
|
drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
|
|
2136
2190
|
}
|
|
2137
2191
|
|
|
2192
|
+
/** Draw colored line between two points
|
|
2193
|
+
* @param {Vector2} posA
|
|
2194
|
+
* @param {Vector2} posB
|
|
2195
|
+
* @param {Number} [thickness]
|
|
2196
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
2197
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
2198
|
+
* @param {Boolean} [screenSpace=false]
|
|
2199
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
2200
|
+
* @memberof Draw */
|
|
2201
|
+
function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
|
|
2202
|
+
{
|
|
2203
|
+
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
2204
|
+
const size = vec2(thickness, halfDelta.length()*2);
|
|
2205
|
+
drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), useWebGL, screenSpace, context);
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2138
2208
|
/** Draw colored polygon using passed in points
|
|
2139
2209
|
* @param {Array} points - Array of Vector2 points
|
|
2140
2210
|
* @param {Color} [color=(1,1,1,1)]
|
|
@@ -2203,22 +2273,6 @@ function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth
|
|
|
2203
2273
|
function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
|
|
2204
2274
|
{ drawEllipse(pos, radius, radius, 0, color, lineWidth, lineColor, screenSpace, context); }
|
|
2205
2275
|
|
|
2206
|
-
/** Draw colored line between two points
|
|
2207
|
-
* @param {Vector2} posA
|
|
2208
|
-
* @param {Vector2} posB
|
|
2209
|
-
* @param {Number} [thickness]
|
|
2210
|
-
* @param {Color} [color=(1,1,1,1)]
|
|
2211
|
-
* @param {Boolean} [useWebGL=glEnable]
|
|
2212
|
-
* @param {Boolean} [screenSpace=false]
|
|
2213
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
2214
|
-
* @memberof Draw */
|
|
2215
|
-
function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
|
|
2216
|
-
{
|
|
2217
|
-
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
2218
|
-
const size = vec2(thickness, halfDelta.length()*2);
|
|
2219
|
-
drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), useWebGL, screenSpace, context);
|
|
2220
|
-
}
|
|
2221
|
-
|
|
2222
2276
|
/** Draw directly to a 2d canvas context in world space
|
|
2223
2277
|
* @param {Vector2} pos
|
|
2224
2278
|
* @param {Vector2} size
|
|
@@ -2410,13 +2464,14 @@ function isFullscreen() { return !!document.fullscreenElement; }
|
|
|
2410
2464
|
* @memberof Draw */
|
|
2411
2465
|
function toggleFullscreen()
|
|
2412
2466
|
{
|
|
2467
|
+
const rootElement = mainCanvas.parentElement;
|
|
2413
2468
|
if (isFullscreen())
|
|
2414
2469
|
{
|
|
2415
2470
|
if (document.exitFullscreen)
|
|
2416
2471
|
document.exitFullscreen();
|
|
2417
2472
|
}
|
|
2418
|
-
else if (
|
|
2419
|
-
|
|
2473
|
+
else if (rootElement.requestFullscreen)
|
|
2474
|
+
rootElement.requestFullscreen();
|
|
2420
2475
|
}
|
|
2421
2476
|
/**
|
|
2422
2477
|
* LittleJS Input System
|
|
@@ -2465,7 +2520,7 @@ function keyWasReleased(key, device=0)
|
|
|
2465
2520
|
|
|
2466
2521
|
/** Clears all input
|
|
2467
2522
|
* @memberof Input */
|
|
2468
|
-
function clearInput() { inputData = [[]]; }
|
|
2523
|
+
function clearInput() { inputData = [[]]; touchGamepadButtons = []; }
|
|
2469
2524
|
|
|
2470
2525
|
/** Returns true if mouse button is down
|
|
2471
2526
|
* @function
|
|
@@ -2587,7 +2642,6 @@ function inputInit()
|
|
|
2587
2642
|
|
|
2588
2643
|
onkeydown = (e)=>
|
|
2589
2644
|
{
|
|
2590
|
-
if (debug && e.target != engineRoot) return;
|
|
2591
2645
|
if (!e.repeat)
|
|
2592
2646
|
{
|
|
2593
2647
|
isUsingGamepad = false;
|
|
@@ -2600,7 +2654,6 @@ function inputInit()
|
|
|
2600
2654
|
|
|
2601
2655
|
onkeyup = (e)=>
|
|
2602
2656
|
{
|
|
2603
|
-
if (debug && e.target != engineRoot) return;
|
|
2604
2657
|
inputData[0][e.code] = 4;
|
|
2605
2658
|
if (inputWASDEmulateDirection)
|
|
2606
2659
|
inputData[0][remapKey(e.code)] = 4;
|
|
@@ -2632,6 +2685,7 @@ function inputInit()
|
|
|
2632
2685
|
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
2633
2686
|
onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
|
|
2634
2687
|
oncontextmenu = (e)=> false; // prevent right click menu
|
|
2688
|
+
onblur = (e) => clearInput(); // reset input when focus is lost
|
|
2635
2689
|
|
|
2636
2690
|
// init touch input
|
|
2637
2691
|
if (isTouchDevice && touchInputEnable)
|
|
@@ -2802,7 +2856,7 @@ function touchInputInit()
|
|
|
2802
2856
|
// set event pos and pass it along
|
|
2803
2857
|
const p = vec2(e.touches[0].clientX, e.touches[0].clientY);
|
|
2804
2858
|
mousePosScreen = mouseToScreen(p);
|
|
2805
|
-
wasTouching ? isUsingGamepad =
|
|
2859
|
+
wasTouching ? isUsingGamepad = touchGamepadEnable : inputData[0][button] = 3;
|
|
2806
2860
|
}
|
|
2807
2861
|
else if (wasTouching)
|
|
2808
2862
|
inputData[0][button] = inputData[0][button] & 2 | 4;
|
|
@@ -2830,10 +2884,13 @@ function touchInputInit()
|
|
|
2830
2884
|
if (touching)
|
|
2831
2885
|
{
|
|
2832
2886
|
touchGamepadTimer.set();
|
|
2833
|
-
if (paused)
|
|
2887
|
+
if (paused && !wasTouching)
|
|
2834
2888
|
{
|
|
2835
2889
|
// touch anywhere to press start when paused
|
|
2836
2890
|
touchGamepadButtons[9] = 1;
|
|
2891
|
+
|
|
2892
|
+
// call default touch handler so normal touch events still work
|
|
2893
|
+
handleTouchDefault(e);
|
|
2837
2894
|
return;
|
|
2838
2895
|
}
|
|
2839
2896
|
}
|
|
@@ -2858,7 +2915,7 @@ function touchInputInit()
|
|
|
2858
2915
|
const button = touchPos.subtract(buttonCenter).direction();
|
|
2859
2916
|
touchGamepadButtons[button] = 1;
|
|
2860
2917
|
}
|
|
2861
|
-
else if (touchPos.distance(startCenter) < touchGamepadSize)
|
|
2918
|
+
else if (touchPos.distance(startCenter) < touchGamepadSize && !wasTouching)
|
|
2862
2919
|
{
|
|
2863
2920
|
// virtual start button in center
|
|
2864
2921
|
touchGamepadButtons[9] = 1;
|
|
@@ -2946,7 +3003,7 @@ function touchGamepadRender()
|
|
|
2946
3003
|
/** Audio context used by the engine
|
|
2947
3004
|
* @type {AudioContext}
|
|
2948
3005
|
* @memberof Audio */
|
|
2949
|
-
let audioContext;
|
|
3006
|
+
let audioContext = new AudioContext;
|
|
2950
3007
|
|
|
2951
3008
|
/** Master gain node for all audio to pass through
|
|
2952
3009
|
* @type {GainNode}
|
|
@@ -2957,14 +3014,10 @@ function audioInit()
|
|
|
2957
3014
|
{
|
|
2958
3015
|
if (!soundEnable || headlessMode) return;
|
|
2959
3016
|
|
|
2960
|
-
// create audio context
|
|
2961
|
-
audioContext = new AudioContext;
|
|
2962
|
-
|
|
2963
|
-
// create and connect gain node
|
|
2964
3017
|
// (createGain is more widely spported then GainNode construtor)
|
|
2965
3018
|
audioGainNode = audioContext.createGain();
|
|
2966
3019
|
audioGainNode.connect(audioContext.destination);
|
|
2967
|
-
|
|
3020
|
+
audioGainNode.gain.value = soundVolume; // set starting value
|
|
2968
3021
|
}
|
|
2969
3022
|
|
|
2970
3023
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -2999,12 +3052,15 @@ class Sound
|
|
|
2999
3052
|
|
|
3000
3053
|
/** @property {Number} - How much to randomize frequency each time sound plays */
|
|
3001
3054
|
this.randomness = 0;
|
|
3055
|
+
|
|
3056
|
+
/** @property {GainNode} - Gain node for this sound */
|
|
3057
|
+
this.gainNode = audioContext.createGain();
|
|
3002
3058
|
|
|
3003
3059
|
if (zzfxSound)
|
|
3004
3060
|
{
|
|
3005
3061
|
// generate zzfx sound now for fast playback
|
|
3006
3062
|
const defaultRandomness = .05;
|
|
3007
|
-
this.randomness = zzfxSound[1]
|
|
3063
|
+
this.randomness = zzfxSound[1] != undefined ? zzfxSound[1] : defaultRandomness;
|
|
3008
3064
|
zzfxSound[1] = 0; // generate without randomness
|
|
3009
3065
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
3010
3066
|
this.sampleRate = zzfxR;
|
|
@@ -3045,18 +3101,13 @@ class Sound
|
|
|
3045
3101
|
|
|
3046
3102
|
// play the sound
|
|
3047
3103
|
const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
|
|
3048
|
-
this.gainNode = audioContext.createGain();
|
|
3049
3104
|
return this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate, this.gainNode);
|
|
3050
3105
|
}
|
|
3051
3106
|
|
|
3052
3107
|
/** Set the sound volume
|
|
3053
3108
|
* @param {Number} [volume] - How much to scale volume by
|
|
3054
3109
|
*/
|
|
3055
|
-
setVolume(volume=1)
|
|
3056
|
-
{
|
|
3057
|
-
if (this.gainNode)
|
|
3058
|
-
this.gainNode.gain.value = volume;
|
|
3059
|
-
}
|
|
3110
|
+
setVolume(volume=1) { this.gainNode.gain.value = volume; }
|
|
3060
3111
|
|
|
3061
3112
|
/** Stop the last instance of this sound that was played */
|
|
3062
3113
|
stop()
|
|
@@ -3540,18 +3591,18 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
3540
3591
|
|
|
3541
3592
|
|
|
3542
3593
|
|
|
3543
|
-
/** The tile collision layer
|
|
3594
|
+
/** The tile collision layer grid, use setTileCollisionData and getTileCollisionData to access
|
|
3544
3595
|
* @type {Array}
|
|
3545
3596
|
* @memberof TileCollision */
|
|
3546
3597
|
let tileCollision = [];
|
|
3547
3598
|
|
|
3548
|
-
/** Size of the tile collision layer
|
|
3599
|
+
/** Size of the tile collision layer 2d grid
|
|
3549
3600
|
* @type {Vector2}
|
|
3550
3601
|
* @memberof TileCollision */
|
|
3551
3602
|
let tileCollisionSize = vec2();
|
|
3552
3603
|
|
|
3553
3604
|
/** Clear and initialize tile collision
|
|
3554
|
-
* @param {Vector2} size
|
|
3605
|
+
* @param {Vector2} size - width and height of tile collision 2d grid
|
|
3555
3606
|
* @memberof TileCollision */
|
|
3556
3607
|
function initTileCollision(size)
|
|
3557
3608
|
{
|
|
@@ -3561,7 +3612,7 @@ function initTileCollision(size)
|
|
|
3561
3612
|
tileCollision[i] = 0;
|
|
3562
3613
|
}
|
|
3563
3614
|
|
|
3564
|
-
/** Set tile collision data
|
|
3615
|
+
/** Set tile collision data for a given cell in the grid
|
|
3565
3616
|
* @param {Vector2} pos
|
|
3566
3617
|
* @param {Number} [data]
|
|
3567
3618
|
* @memberof TileCollision */
|
|
@@ -3570,7 +3621,7 @@ function setTileCollisionData(pos, data=0)
|
|
|
3570
3621
|
pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
|
|
3571
3622
|
}
|
|
3572
3623
|
|
|
3573
|
-
/** Get tile collision data
|
|
3624
|
+
/** Get tile collision data for a given cell in the grid
|
|
3574
3625
|
* @param {Vector2} pos
|
|
3575
3626
|
* @return {Number}
|
|
3576
3627
|
* @memberof TileCollision */
|
|
@@ -3598,9 +3649,11 @@ function tileCollisionTest(pos, size=vec2(), object)
|
|
|
3598
3649
|
if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
|
|
3599
3650
|
return true;
|
|
3600
3651
|
}
|
|
3652
|
+
return false;
|
|
3601
3653
|
}
|
|
3602
3654
|
|
|
3603
|
-
/** Return the center of first tile hit
|
|
3655
|
+
/** Return the center of first tile hit, undefined if nothing was hit.
|
|
3656
|
+
* This does not return the exact intersection, but the center of the tile hit.
|
|
3604
3657
|
* @param {Vector2} posStart
|
|
3605
3658
|
* @param {Vector2} posEnd
|
|
3606
3659
|
* @param {EngineObject} [object]
|
|
@@ -3621,7 +3674,7 @@ function tileCollisionRaycast(posStart, posEnd, object)
|
|
|
3621
3674
|
let xi = unit.x * (delta.x < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1);
|
|
3622
3675
|
let yi = unit.y * (delta.y < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1);
|
|
3623
3676
|
|
|
3624
|
-
while (
|
|
3677
|
+
while (true)
|
|
3625
3678
|
{
|
|
3626
3679
|
// check for tile collision
|
|
3627
3680
|
const tileData = getTileCollisionData(pos);
|
|
@@ -3845,8 +3898,8 @@ class TileLayer extends EngineObject
|
|
|
3845
3898
|
const d = this.getData(layerPos);
|
|
3846
3899
|
if (d.tile != undefined)
|
|
3847
3900
|
{
|
|
3848
|
-
const pos = this.pos.add(layerPos).add(vec2(.5));
|
|
3849
3901
|
ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
|
|
3902
|
+
const pos = layerPos.add(vec2(.5));
|
|
3850
3903
|
const tileInfo = tile(d.tile, s, this.tileInfo.textureIndex);
|
|
3851
3904
|
drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
|
|
3852
3905
|
}
|
|
@@ -4275,7 +4328,7 @@ function medalsInit(saveName)
|
|
|
4275
4328
|
// check if medals are unlocked
|
|
4276
4329
|
medalsSaveName = saveName;
|
|
4277
4330
|
if (!debugMedals)
|
|
4278
|
-
medalsForEach(medal=> medal.unlocked =
|
|
4331
|
+
medalsForEach(medal=> medal.unlocked = !!localStorage[medal.storageKey()]);
|
|
4279
4332
|
|
|
4280
4333
|
// engine automatically renders medals
|
|
4281
4334
|
engineAddPlugin(undefined, medalsRender);
|
|
@@ -4338,14 +4391,28 @@ class Medal
|
|
|
4338
4391
|
constructor(id, name, description='', icon='🏆', src)
|
|
4339
4392
|
{
|
|
4340
4393
|
ASSERT(id >= 0 && !medals[id]);
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4394
|
+
|
|
4395
|
+
/** @property {Number} - The unique identifier of the medal */
|
|
4396
|
+
this.id = id;
|
|
4397
|
+
|
|
4398
|
+
/** @property {String} - Name of the medal */
|
|
4344
4399
|
this.name = name;
|
|
4400
|
+
|
|
4401
|
+
/** @property {String} - Description of the medal */
|
|
4345
4402
|
this.description = description;
|
|
4403
|
+
|
|
4404
|
+
/** @property {String} - Icon for the medal */
|
|
4346
4405
|
this.icon = icon;
|
|
4406
|
+
|
|
4407
|
+
/** @property {Boolean} - Is the medal unlocked? */
|
|
4408
|
+
this.unlocked = false;
|
|
4409
|
+
|
|
4410
|
+
// load the source image if provided
|
|
4347
4411
|
if (src)
|
|
4348
4412
|
(this.image = new Image).src = src;
|
|
4413
|
+
|
|
4414
|
+
// add this to list of medals
|
|
4415
|
+
medals[id] = this;
|
|
4349
4416
|
}
|
|
4350
4417
|
|
|
4351
4418
|
/** Unlocks a medal if not already unlocked */
|
|
@@ -4356,7 +4423,7 @@ class Medal
|
|
|
4356
4423
|
|
|
4357
4424
|
// save the medal
|
|
4358
4425
|
ASSERT(medalsSaveName, 'save name must be set');
|
|
4359
|
-
localStorage[this.storageKey()] = this.unlocked =
|
|
4426
|
+
localStorage[this.storageKey()] = this.unlocked = true;
|
|
4360
4427
|
medalsDisplayQueue.push(this);
|
|
4361
4428
|
}
|
|
4362
4429
|
|
|
@@ -4430,6 +4497,11 @@ let glCanvas;
|
|
|
4430
4497
|
* @memberof WebGL */
|
|
4431
4498
|
let glContext;
|
|
4432
4499
|
|
|
4500
|
+
/** Shoule webgl be setup with antialiasing, must be set before calling engineInit
|
|
4501
|
+
* @type {Boolean}
|
|
4502
|
+
* @memberof WebGL */
|
|
4503
|
+
let glAntialias = true;
|
|
4504
|
+
|
|
4433
4505
|
// WebGL internal variables not exposed to documentation
|
|
4434
4506
|
let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glInstanceCount, glAdditive, glBatchAdditive;
|
|
4435
4507
|
|
|
@@ -4442,10 +4514,11 @@ function glInit()
|
|
|
4442
4514
|
|
|
4443
4515
|
// create the canvas and textures
|
|
4444
4516
|
glCanvas = document.createElement('canvas');
|
|
4445
|
-
glContext = glCanvas.getContext('webgl2', {antialias
|
|
4517
|
+
glContext = glCanvas.getContext('webgl2', {antialias:glAntialias});
|
|
4446
4518
|
|
|
4447
4519
|
// some browsers are much faster without copying the gl buffer so we just overlay it instead
|
|
4448
|
-
|
|
4520
|
+
const rootElement = mainCanvas.parentElement;
|
|
4521
|
+
glOverlay && rootElement.appendChild(glCanvas);
|
|
4449
4522
|
|
|
4450
4523
|
// setup vertex and fragment shaders
|
|
4451
4524
|
glShader = glCreateProgram(
|
|
@@ -4649,6 +4722,15 @@ function glCopyToContext(context, forceDraw=false)
|
|
|
4649
4722
|
context.drawImage(glCanvas, 0, 0);
|
|
4650
4723
|
}
|
|
4651
4724
|
|
|
4725
|
+
/** Set antialiasing for webgl canvas
|
|
4726
|
+
* @param {Boolean} [antialias]
|
|
4727
|
+
* @memberof WebGL */
|
|
4728
|
+
function glSetAntialias(antialias=true)
|
|
4729
|
+
{
|
|
4730
|
+
ASSERT(!glCanvas, 'must be called before engineInit');
|
|
4731
|
+
glAntialias = antialias;
|
|
4732
|
+
}
|
|
4733
|
+
|
|
4652
4734
|
/** Add a sprite to the gl draw list, used by all gl draw functions
|
|
4653
4735
|
* @param {Number} x
|
|
4654
4736
|
* @param {Number} y
|
|
@@ -4749,7 +4831,7 @@ const engineName = 'LittleJS';
|
|
|
4749
4831
|
* @type {String}
|
|
4750
4832
|
* @default
|
|
4751
4833
|
* @memberof Engine */
|
|
4752
|
-
const engineVersion = '1.10.
|
|
4834
|
+
const engineVersion = '1.10.7';
|
|
4753
4835
|
|
|
4754
4836
|
/** Frames per second to update
|
|
4755
4837
|
* @type {Number}
|
|
@@ -4793,13 +4875,6 @@ let timeReal = 0;
|
|
|
4793
4875
|
* @default false
|
|
4794
4876
|
* @memberof Engine */
|
|
4795
4877
|
let paused = false;
|
|
4796
|
-
|
|
4797
|
-
/** The root element that engine is attached to
|
|
4798
|
-
* @type {HTMLElement}
|
|
4799
|
-
* @default document.body
|
|
4800
|
-
* @memberof Engine */
|
|
4801
|
-
let engineRoot;
|
|
4802
|
-
|
|
4803
4878
|
/** Set if game is paused
|
|
4804
4879
|
* @param {Boolean} isPaused
|
|
4805
4880
|
* @memberof Engine */
|
|
@@ -4819,6 +4894,8 @@ const pluginUpdateList = [], pluginRenderList = [];
|
|
|
4819
4894
|
* @memberof Engine */
|
|
4820
4895
|
function engineAddPlugin(updateFunction, renderFunction)
|
|
4821
4896
|
{
|
|
4897
|
+
ASSERT(!pluginUpdateList.includes(updateFunction));
|
|
4898
|
+
ASSERT(!pluginRenderList.includes(renderFunction));
|
|
4822
4899
|
updateFunction && pluginUpdateList.push(updateFunction);
|
|
4823
4900
|
renderFunction && pluginRenderList.push(renderFunction);
|
|
4824
4901
|
}
|
|
@@ -4827,12 +4904,12 @@ function engineAddPlugin(updateFunction, renderFunction)
|
|
|
4827
4904
|
// Main engine functions
|
|
4828
4905
|
|
|
4829
4906
|
/** Startup LittleJS engine with your callback functions
|
|
4830
|
-
* @param {Function} gameInit
|
|
4831
|
-
* @param {Function} gameUpdate
|
|
4832
|
-
* @param {Function} gameUpdatePost - Called after physics and objects are updated,
|
|
4833
|
-
* @param {Function} gameRender
|
|
4834
|
-
* @param {Function} gameRenderPost - Called after objects are rendered,
|
|
4835
|
-
* @param {Array} [imageSources=[
|
|
4907
|
+
* @param {Function|function():Promise} gameInit - Called once after the engine starts up
|
|
4908
|
+
* @param {Function} gameUpdate - Called every frame before objects are updated
|
|
4909
|
+
* @param {Function} gameUpdatePost - Called after physics and objects are updated, even when paused
|
|
4910
|
+
* @param {Function} gameRender - Called before objects are rendered, for drawing the background
|
|
4911
|
+
* @param {Function} gameRenderPost - Called after objects are rendered, useful for drawing UI
|
|
4912
|
+
* @param {Array} [imageSources=[]] - List of images to load
|
|
4836
4913
|
* @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
|
|
4837
4914
|
* @memberof Engine */
|
|
4838
4915
|
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
@@ -4982,8 +5059,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4982
5059
|
|
|
4983
5060
|
function startEngine()
|
|
4984
5061
|
{
|
|
4985
|
-
gameInit();
|
|
4986
|
-
engineUpdate();
|
|
5062
|
+
new Promise((resolve) => resolve(gameInit())).then(engineUpdate);
|
|
4987
5063
|
}
|
|
4988
5064
|
|
|
4989
5065
|
if (headlessMode)
|
|
@@ -4998,7 +5074,6 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4998
5074
|
'width:100vw;height:100vh;' + // fill the window
|
|
4999
5075
|
'display:flex;' + // use flexbox
|
|
5000
5076
|
'align-items:center;' + // horizontal center
|
|
5001
|
-
(canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
|
|
5002
5077
|
'justify-content:center;' + // vertical center
|
|
5003
5078
|
'background:#000;' + // set background color
|
|
5004
5079
|
'user-select:none;' + // prevent hold to select
|
|
@@ -5006,9 +5081,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5006
5081
|
(!touchInputEnable ? '' : // no touch css setttings
|
|
5007
5082
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
5008
5083
|
'-webkit-touch-callout:none');// compatibility for ios
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
engineRoot.appendChild(mainCanvas = document.createElement('canvas'));
|
|
5084
|
+
rootElement.style.cssText = styleRoot;
|
|
5085
|
+
rootElement.appendChild(mainCanvas = document.createElement('canvas'));
|
|
5012
5086
|
mainContext = mainCanvas.getContext('2d');
|
|
5013
5087
|
|
|
5014
5088
|
// init stuff and start engine
|
|
@@ -5018,7 +5092,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5018
5092
|
glInit();
|
|
5019
5093
|
|
|
5020
5094
|
// create overlay canvas for hud to appear above gl canvas
|
|
5021
|
-
|
|
5095
|
+
rootElement.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
5022
5096
|
overlayContext = overlayCanvas.getContext('2d');
|
|
5023
5097
|
|
|
5024
5098
|
// set canvas style
|
|
@@ -5337,4 +5411,5 @@ function drawEngineSplashScreen(t)
|
|
|
5337
5411
|
}
|
|
5338
5412
|
|
|
5339
5413
|
x.restore();
|
|
5340
|
-
}
|
|
5414
|
+
}
|
|
5415
|
+
|
package/examples/box2d/game.js
CHANGED
|
@@ -19,6 +19,7 @@ setShowSplashScreen(true);
|
|
|
19
19
|
const maxScenes = 11;
|
|
20
20
|
let scene = 0;
|
|
21
21
|
let sceneName;
|
|
22
|
+
let spriteAtlas;
|
|
22
23
|
let groundObject;
|
|
23
24
|
let mouseJoint;
|
|
24
25
|
let car;
|
|
@@ -27,6 +28,19 @@ let repeatSpawnTimer = new Timer;
|
|
|
27
28
|
///////////////////////////////////////////////////////////////////////////////
|
|
28
29
|
function gameInit()
|
|
29
30
|
{
|
|
31
|
+
// create a table of all sprites
|
|
32
|
+
const gameTile = (i)=> tile(i, 16, 0, 1);
|
|
33
|
+
spriteAtlas =
|
|
34
|
+
{
|
|
35
|
+
circle: gameTile(0),
|
|
36
|
+
dot: gameTile(1),
|
|
37
|
+
circleOutline: gameTile(2),
|
|
38
|
+
squareOutline: gameTile(3),
|
|
39
|
+
wheel: gameTile(4),
|
|
40
|
+
gear: gameTile(5),
|
|
41
|
+
squareOutline2: gameTile(6),
|
|
42
|
+
};
|
|
43
|
+
|
|
30
44
|
loadScene(scene);
|
|
31
45
|
}
|
|
32
46
|
|
|
@@ -125,7 +139,7 @@ function gameRenderPost()
|
|
|
125
139
|
{
|
|
126
140
|
// draw mouse joint
|
|
127
141
|
const ab = vec2(mouseJoint.GetAnchorB());
|
|
128
|
-
drawTile(ab, vec2(.3),
|
|
142
|
+
drawTile(ab, vec2(.3), spriteAtlas.circle, BLACK);
|
|
129
143
|
drawLine(mousePos, ab, .1, BLACK);
|
|
130
144
|
}
|
|
131
145
|
|