littlejsengine 1.17.5 → 1.17.10
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/LICENSE +10 -10
- package/dist/littlejs.d.ts +160 -88
- package/dist/littlejs.esm.js +586 -426
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +583 -425
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +582 -424
- package/examples/electron/index.html +2 -2
- package/examples/index.html +2 -2
- package/examples/logo.png +0 -0
- package/examples/logo2.png +0 -0
- package/examples/particles/index.html +1 -1
- package/examples/platformer/gameEffects.js +3 -3
- package/examples/shorts/base.html +1 -1
- package/examples/shorts/colors.js +1 -1
- package/examples/shorts/fps.js +1 -1
- package/examples/shorts/music.js +3 -2
- package/examples/shorts/musicPlayer.js +2 -1
- package/examples/shorts/parallax.js +2 -2
- package/examples/shorts/tiltedView.js +1 -1
- package/examples/starter/index.html +2 -2
- package/package.json +1 -1
- package/plugins/box2d.js +6 -7
- package/plugins/pluginExport.js +1 -1
- package/plugins/postProcess.js +1 -1
- package/plugins/uiSystem.js +30 -17
- package/reference.md +6 -6
- package/src/engine.js +103 -127
- package/src/engineDraw.js +2 -2
- package/src/engineExport.js +2 -0
- package/src/engineInput.js +1 -1
- package/src/engineObject.js +24 -24
- package/src/engineParticles.js +241 -118
- package/src/engineTileLayer.js +66 -48
- package/src/engineUtilities.js +105 -78
- package/plugins/Box2D_License.txt +0 -17
package/dist/littlejs.release.js
CHANGED
|
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
|
|
|
33
33
|
* @type {string}
|
|
34
34
|
* @default
|
|
35
35
|
* @memberof Engine */
|
|
36
|
-
const engineVersion = '1.17.
|
|
36
|
+
const engineVersion = '1.17.10';
|
|
37
37
|
|
|
38
38
|
/** Frames per second to update
|
|
39
39
|
* @type {number}
|
|
@@ -464,13 +464,13 @@ function engineObjectsUpdate()
|
|
|
464
464
|
engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
|
|
465
465
|
|
|
466
466
|
// recursive object update
|
|
467
|
-
function
|
|
467
|
+
function updateChildObject(o)
|
|
468
468
|
{
|
|
469
469
|
if (o.destroyed) return;
|
|
470
470
|
|
|
471
471
|
o.update();
|
|
472
472
|
for (const child of o.children)
|
|
473
|
-
|
|
473
|
+
updateChildObject(child);
|
|
474
474
|
}
|
|
475
475
|
for (const o of engineObjects)
|
|
476
476
|
{
|
|
@@ -480,7 +480,7 @@ function engineObjectsUpdate()
|
|
|
480
480
|
o.update();
|
|
481
481
|
o.updatePhysics();
|
|
482
482
|
for (const child of o.children)
|
|
483
|
-
|
|
483
|
+
updateChildObject(child);
|
|
484
484
|
o.updateTransforms();
|
|
485
485
|
}
|
|
486
486
|
|
|
@@ -489,11 +489,14 @@ function engineObjectsUpdate()
|
|
|
489
489
|
}
|
|
490
490
|
|
|
491
491
|
/** Destroy and remove all objects
|
|
492
|
+
* - This can be used to clear out all objects when restarting a level
|
|
493
|
+
* - Objects can override their destroy function to do cleanup or stick around
|
|
494
|
+
* @param {boolean} [immediate] - should attached effects be allowed to die off?
|
|
492
495
|
* @memberof Engine */
|
|
493
|
-
function engineObjectsDestroy()
|
|
496
|
+
function engineObjectsDestroy(immediate=true)
|
|
494
497
|
{
|
|
495
498
|
for (const o of engineObjects)
|
|
496
|
-
o.parent || o.destroy();
|
|
499
|
+
o.parent || o.destroy(immediate);
|
|
497
500
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
498
501
|
}
|
|
499
502
|
|
|
@@ -568,165 +571,140 @@ function engineObjectsRaycast(start, end, objects=engineObjects)
|
|
|
568
571
|
///////////////////////////////////////////////////////////////////////////////
|
|
569
572
|
function drawEngineLogo(t)
|
|
570
573
|
{
|
|
574
|
+
const blackAndWhite = 0;
|
|
575
|
+
const showName = 1;
|
|
576
|
+
|
|
571
577
|
// LittleJS Logo and Splash Screen
|
|
572
578
|
const x = mainContext;
|
|
573
579
|
const w = mainCanvas.width = innerWidth;
|
|
574
580
|
const h = mainCanvas.height = innerHeight;
|
|
575
|
-
|
|
576
581
|
{
|
|
577
582
|
// background
|
|
578
583
|
const p3 = percent(t, 1, .8);
|
|
579
584
|
const p4 = percent(t, 0, .5);
|
|
580
|
-
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.
|
|
585
|
+
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.6);
|
|
581
586
|
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
582
587
|
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
583
588
|
x.save();
|
|
584
589
|
x.fillStyle = g;
|
|
585
590
|
x.fillRect(0,0,w,h);
|
|
586
591
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
592
|
+
const gradient = (X1,Y1,X2,Y2,C,S=1)=>
|
|
593
|
+
{
|
|
594
|
+
if (C >= 0)
|
|
595
|
+
{
|
|
596
|
+
if (blackAndWhite)
|
|
597
|
+
x.fillStyle = '#fff';
|
|
598
|
+
else
|
|
599
|
+
{
|
|
600
|
+
const g = x.fillStyle = x.createLinearGradient(X1,Y1,X2,Y2);
|
|
601
|
+
g.addColorStop(0,color(C,2));
|
|
602
|
+
g.addColorStop(1,color(C,1));
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
else
|
|
606
|
+
x.fillStyle = '#000';
|
|
607
|
+
C >= -1 ? (x.fill(), S && x.stroke()) : x.stroke();
|
|
608
|
+
}
|
|
609
|
+
const circle = (X,Y,R,A=0,B=2*PI,C,S)=>
|
|
590
610
|
{
|
|
591
611
|
x.beginPath();
|
|
592
|
-
x.
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
const line = (X, Y, Z, W)=>
|
|
612
|
+
x.arc(X,Y,R,p*A,p*B);
|
|
613
|
+
gradient(X,Y-R,X,Y+R,C,S);
|
|
614
|
+
}
|
|
615
|
+
const rect = (X,Y,W,H,C)=>
|
|
597
616
|
{
|
|
598
617
|
x.beginPath();
|
|
599
|
-
x.
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
|
|
618
|
+
x.rect(X,Y,W,H*p);
|
|
619
|
+
gradient(X,Y+H,X+W,Y,C);
|
|
620
|
+
}
|
|
621
|
+
const poly = (points,C,Y,H)=>
|
|
604
622
|
{
|
|
605
|
-
const D = (A+B)/2, E = p*(B-A)/2;
|
|
606
623
|
x.beginPath();
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
x.
|
|
610
|
-
|
|
611
|
-
}
|
|
612
|
-
const color = (c
|
|
613
|
-
|
|
624
|
+
for (const p of points)
|
|
625
|
+
x.lineTo(p.x, p.y);
|
|
626
|
+
x.closePath();
|
|
627
|
+
gradient(0, Y, 0, Y+H,C);
|
|
628
|
+
}
|
|
629
|
+
const color = (c,l)=> l?`hsl(${[.95,.56,.13][c%3]*360} 99%${[0,50,75][l]}%`:'#000';
|
|
630
|
+
|
|
631
|
+
// center and fit tos screen
|
|
614
632
|
const alpha = wave(1,1,t);
|
|
615
633
|
const p = percent(alpha, .1, .5);
|
|
616
|
-
|
|
617
|
-
// setup
|
|
634
|
+
const size = min(6, min(w,h)/99);
|
|
618
635
|
x.translate(w/2,h/2);
|
|
619
|
-
const size = min(6, min(w,h)/99); // fit to screen
|
|
620
636
|
x.scale(size,size);
|
|
621
637
|
x.translate(-40,-35);
|
|
638
|
+
p < 1 && x.setLineDash([99*p,99]);
|
|
622
639
|
x.lineJoin = x.lineCap = 'round';
|
|
623
640
|
x.lineWidth = .1 + p*1.9;
|
|
641
|
+
//x.strokeStyle='#fff7';
|
|
642
|
+
|
|
643
|
+
if (showName)
|
|
644
|
+
{
|
|
645
|
+
// engine name text
|
|
646
|
+
const Y = 54;
|
|
647
|
+
const s = 'LittleJS';
|
|
648
|
+
x.font = '900 15.5px arial';
|
|
649
|
+
x.lineWidth = .1+p*3.9;
|
|
650
|
+
x.textAlign = 'center';
|
|
651
|
+
x.textBaseline = 'top';
|
|
652
|
+
rect(11,Y+2,59,8*p,-1);
|
|
653
|
+
x.beginPath();
|
|
624
654
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
rect(
|
|
642
|
-
|
|
643
|
-
// cab
|
|
644
|
-
rect(
|
|
645
|
-
|
|
646
|
-
rect(21,21,-6,9);
|
|
647
|
-
|
|
648
|
-
// little stack
|
|
649
|
-
rect(37,14,9,6,color(3,2));
|
|
650
|
-
rect(37,14,4.5,6,color(3,3));
|
|
651
|
-
rect(37,14,9,6);
|
|
655
|
+
let w2 = 0;
|
|
656
|
+
for (let i=0;i<s.length;++i)
|
|
657
|
+
w2 += x.measureText(s[i]).width;
|
|
658
|
+
for (let j=2;j--;)
|
|
659
|
+
for (let i=0,X=40-w2/2;i<s.length;++i)
|
|
660
|
+
{
|
|
661
|
+
const w = x.measureText(s[i]).width, X2 = X+w/2;
|
|
662
|
+
gradient(X2,Y,X2+2,Y+13,i>5?1:0);
|
|
663
|
+
x[j?'strokeText':'fillText'](s[i],X2,Y+.5,17*p);
|
|
664
|
+
X += w;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
x.lineWidth = .1 + p*1.9;
|
|
668
|
+
rect(3,Y,73,0); // bottom
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
rect(7,15,26,-7,0); // cab top
|
|
672
|
+
rect(25,15,8,25,-1); // cab front
|
|
673
|
+
rect(10,40,15,-25,1); // cab back
|
|
674
|
+
rect(14,21,7,9,2); // cab window
|
|
675
|
+
rect(38,15,6,6,2); // little stack
|
|
652
676
|
|
|
653
677
|
// big stack
|
|
654
|
-
rect(
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
rect(
|
|
658
|
-
circle(55,2,11.4,.5,PI-.5,color(3,3));
|
|
659
|
-
circle(55,2,11.4,.5,PI/2,color(3,2),1);
|
|
660
|
-
circle(55,2,11.4,.5,PI-.5);
|
|
661
|
-
rect(45,7,20,-7,color(0,2));
|
|
662
|
-
rect(45,-1,20,4,color(0,3));
|
|
663
|
-
rect(45,-1,20,8);
|
|
678
|
+
rect(49,20,10,-6,0);
|
|
679
|
+
const stackPoints = [vec2(44,8),vec2(64,8),vec2(59,8+6*p),vec2(49,8+6*p)];
|
|
680
|
+
poly(stackPoints,2,8,6*p);
|
|
681
|
+
rect(44,8,20,-7,0);
|
|
664
682
|
|
|
665
683
|
// engine
|
|
666
|
-
for (let i=5;
|
|
667
|
-
|
|
668
|
-
// stagger radius to fix slight seam
|
|
669
|
-
circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
|
|
670
|
-
circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
|
|
671
|
-
circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
|
|
672
|
-
}
|
|
684
|
+
for (let i=5;i--;) circle(59-i*6,30,10,0,9,1,0);
|
|
685
|
+
circle(59,30,4,0,9,2); // light
|
|
673
686
|
|
|
674
687
|
// engine outline
|
|
675
|
-
|
|
676
|
-
circle(
|
|
677
|
-
circle(
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
//
|
|
681
|
-
circle(60,30,4,PI,3*PI,color(3,2));
|
|
682
|
-
circle(60,30,4,PI,2*PI,color(3,3));
|
|
683
|
-
circle(60,30,4,PI,3*PI);
|
|
688
|
+
rect(35,20,24,0); // top
|
|
689
|
+
circle(59,30,10); // front
|
|
690
|
+
circle(47,30,10,PI/2,PI*3/2); // middle
|
|
691
|
+
circle(35,30,10,PI/2,PI*3/2); // back
|
|
692
|
+
rect(7,40,13,7,-1); // bottom back
|
|
693
|
+
rect(17,40,43,14,-1); // bottom center
|
|
684
694
|
|
|
685
|
-
//
|
|
686
|
-
for (let i=
|
|
695
|
+
// wheels
|
|
696
|
+
for (let i=3;i--;)
|
|
697
|
+
for (let j=2;j--;)
|
|
698
|
+
circle(15*i+17,47,j?7:1,PI,3*PI,2);
|
|
699
|
+
|
|
700
|
+
// cowcatcher
|
|
701
|
+
for (let i=2;i--;)
|
|
687
702
|
{
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
x.lineTo(53+(1+i*2.9)*p,40);
|
|
692
|
-
x.lineTo(53+(4+i*3.5)*p,54);
|
|
693
|
-
x.fillStyle = color(0,i%2+2);
|
|
694
|
-
x.fill();
|
|
695
|
-
i%2 && x.stroke();
|
|
703
|
+
let w=6, s=7, o=53+w*p*i
|
|
704
|
+
const points = [vec2(o+s,54),vec2(o,40),vec2(o+w*p,40),vec2(o+s+w*p,54)];
|
|
705
|
+
poly(points,0,40,14);
|
|
696
706
|
}
|
|
697
707
|
|
|
698
|
-
// wheels
|
|
699
|
-
rect(6,40,5,5);
|
|
700
|
-
rect(6,40,5,5,color());
|
|
701
|
-
rect(15,54,38,-14,color());
|
|
702
|
-
for (let i=3; i--;)
|
|
703
|
-
for (let j=2; j--;)
|
|
704
|
-
{
|
|
705
|
-
circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
|
|
706
|
-
x.stroke();
|
|
707
|
-
circle(15*i+15,47,j?7:1,0,PI,color(i,2));
|
|
708
|
-
x.stroke();
|
|
709
|
-
}
|
|
710
|
-
line(6,40,68,40); // center
|
|
711
|
-
line(77,54,4,54); // bottom
|
|
712
|
-
|
|
713
|
-
// draw engine name
|
|
714
|
-
const s = engineName;
|
|
715
|
-
x.font = '900 16px arial';
|
|
716
|
-
x.textAlign = 'center';
|
|
717
|
-
x.textBaseline = 'top';
|
|
718
|
-
x.lineWidth = .1+p*3.9;
|
|
719
|
-
let w2 = 0;
|
|
720
|
-
for (let i=0; i<s.length; ++i)
|
|
721
|
-
w2 += x.measureText(s[i]).width;
|
|
722
|
-
for (let j=2; j--;)
|
|
723
|
-
for (let i=0, X=41-w2/2; i<s.length; ++i)
|
|
724
|
-
{
|
|
725
|
-
x.fillStyle = color(i,2);
|
|
726
|
-
const w = x.measureText(s[i]).width;
|
|
727
|
-
x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
|
|
728
|
-
X += w;
|
|
729
|
-
}
|
|
730
708
|
x.restore();
|
|
731
709
|
}
|
|
732
710
|
/**
|
|
@@ -1884,6 +1862,90 @@ const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
|
1884
1862
|
* @namespace Utilities
|
|
1885
1863
|
*/
|
|
1886
1864
|
|
|
1865
|
+
/**
|
|
1866
|
+
* Timer object tracks how long has passed since it was set
|
|
1867
|
+
* @memberof Engine
|
|
1868
|
+
* @example
|
|
1869
|
+
* let a = new Timer; // creates a timer that is not set
|
|
1870
|
+
* a.set(3); // sets the timer to 3 seconds
|
|
1871
|
+
*
|
|
1872
|
+
* let b = new Timer(1); // creates a timer with 1 second left
|
|
1873
|
+
* b.unset(); // unset the timer
|
|
1874
|
+
*/
|
|
1875
|
+
class Timer
|
|
1876
|
+
{
|
|
1877
|
+
/** Create a timer object set time passed in
|
|
1878
|
+
* @param {number} [timeLeft] - How much time left before the timer
|
|
1879
|
+
* @param {boolean} [useRealTime] - Should the timer keep running even when the game is paused? (useful for UI) */
|
|
1880
|
+
constructor(timeLeft, useRealTime=false)
|
|
1881
|
+
{
|
|
1882
|
+
ASSERT(timeLeft === undefined || isNumber(timeLeft), 'Constructed Timer is invalid.', timeLeft);
|
|
1883
|
+
this.useRealTime = useRealTime;
|
|
1884
|
+
const globalTime = this.getGlobalTime();
|
|
1885
|
+
this.time = timeLeft === undefined ? undefined : globalTime + timeLeft;
|
|
1886
|
+
this.setTime = timeLeft;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
/** Set the timer with seconds passed in
|
|
1890
|
+
* @param {number} [timeLeft] - How much time left before the timer is elapsed in seconds */
|
|
1891
|
+
set(timeLeft=0)
|
|
1892
|
+
{
|
|
1893
|
+
ASSERT(isNumber(timeLeft), 'Timer is invalid.', timeLeft);
|
|
1894
|
+
const globalTime = this.getGlobalTime();
|
|
1895
|
+
this.time = globalTime + timeLeft;
|
|
1896
|
+
this.setTime = timeLeft;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
/** Set if the timer should keep running even when the game is paused
|
|
1900
|
+
* @param {boolean} [useRealTime] */
|
|
1901
|
+
setUseRealTime(useRealTime=true)
|
|
1902
|
+
{
|
|
1903
|
+
ASSERT(!this.isSet(), 'Cannot change global time setting while timer is set.');
|
|
1904
|
+
this.useRealTime = useRealTime;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
/** Unset the timer */
|
|
1908
|
+
unset() { this.time = undefined; }
|
|
1909
|
+
|
|
1910
|
+
/** Returns true if set
|
|
1911
|
+
* @return {boolean} */
|
|
1912
|
+
isSet() { return this.time !== undefined; }
|
|
1913
|
+
|
|
1914
|
+
/** Returns true if set and has not elapsed
|
|
1915
|
+
* @return {boolean} */
|
|
1916
|
+
active() { return this.getGlobalTime() < this.time; }
|
|
1917
|
+
|
|
1918
|
+
/** Returns true if set and elapsed
|
|
1919
|
+
* @return {boolean} */
|
|
1920
|
+
elapsed() { return this.getGlobalTime() >= this.time; }
|
|
1921
|
+
|
|
1922
|
+
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
1923
|
+
* @return {number} */
|
|
1924
|
+
get() { return this.isSet()? this.getGlobalTime() - this.time : 0; }
|
|
1925
|
+
|
|
1926
|
+
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
1927
|
+
* @return {number} */
|
|
1928
|
+
getPercent() { return this.isSet()? 1-percent(this.time - this.getGlobalTime(), 0, this.setTime) : 0; }
|
|
1929
|
+
|
|
1930
|
+
/** Get the time this timer was set to, returns 0 if not set
|
|
1931
|
+
* @return {number} */
|
|
1932
|
+
getSetTime() { return this.isSet() ? this.setTime : 0; }
|
|
1933
|
+
|
|
1934
|
+
/** Get the current global time this timer is based on
|
|
1935
|
+
* @return {number} */
|
|
1936
|
+
getGlobalTime() { return this.useRealTime ? timeReal : time; }
|
|
1937
|
+
|
|
1938
|
+
/** Returns this timer expressed as a string
|
|
1939
|
+
* @return {string} */
|
|
1940
|
+
toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
|
|
1941
|
+
|
|
1942
|
+
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
1943
|
+
* @return {number} */
|
|
1944
|
+
valueOf() { return this.get(); }
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1948
|
+
|
|
1887
1949
|
/** Formats seconds to mm:ss style for display purposes
|
|
1888
1950
|
* @param {number} t - time in seconds
|
|
1889
1951
|
* @return {string}
|
|
@@ -1970,86 +2032,29 @@ function shareURL(title, url, callback)
|
|
|
1970
2032
|
|
|
1971
2033
|
///////////////////////////////////////////////////////////////////////////////
|
|
1972
2034
|
|
|
1973
|
-
/**
|
|
1974
|
-
*
|
|
1975
|
-
*
|
|
1976
|
-
*
|
|
1977
|
-
*
|
|
1978
|
-
|
|
1979
|
-
*
|
|
1980
|
-
* let b = new Timer(1); // creates a timer with 1 second left
|
|
1981
|
-
* b.unset(); // unset the timer
|
|
1982
|
-
*/
|
|
1983
|
-
class Timer
|
|
2035
|
+
/** Read save data from local storage
|
|
2036
|
+
* @param {string} saveName - unique name for the game/save
|
|
2037
|
+
* @param {Object} [defaultSaveData] - default values for save
|
|
2038
|
+
* @return {Object}
|
|
2039
|
+
* @memberof Utilities */
|
|
2040
|
+
function readSaveData(saveName, defaultSaveData)
|
|
1984
2041
|
{
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
{
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
const globalTime = this.getGlobalTime();
|
|
1993
|
-
this.time = timeLeft === undefined ? undefined : globalTime + timeLeft;
|
|
1994
|
-
this.setTime = timeLeft;
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
|
-
/** Set the timer with seconds passed in
|
|
1998
|
-
* @param {number} [timeLeft] - How much time left before the timer is elapsed in seconds */
|
|
1999
|
-
set(timeLeft=0)
|
|
2000
|
-
{
|
|
2001
|
-
ASSERT(isNumber(timeLeft), 'Timer is invalid.', timeLeft);
|
|
2002
|
-
const globalTime = this.getGlobalTime();
|
|
2003
|
-
this.time = globalTime + timeLeft;
|
|
2004
|
-
this.setTime = timeLeft;
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
/** Set if the timer should keep running even when the game is paused
|
|
2008
|
-
* @param {boolean} [useRealTime] */
|
|
2009
|
-
setUseRealTime(useRealTime=true)
|
|
2010
|
-
{
|
|
2011
|
-
ASSERT(!this.isSet(), 'Cannot change global time setting while timer is set.');
|
|
2012
|
-
this.useRealTime = useRealTime;
|
|
2013
|
-
}
|
|
2014
|
-
|
|
2015
|
-
/** Unset the timer */
|
|
2016
|
-
unset() { this.time = undefined; }
|
|
2017
|
-
|
|
2018
|
-
/** Returns true if set
|
|
2019
|
-
* @return {boolean} */
|
|
2020
|
-
isSet() { return this.time !== undefined; }
|
|
2021
|
-
|
|
2022
|
-
/** Returns true if set and has not elapsed
|
|
2023
|
-
* @return {boolean} */
|
|
2024
|
-
active() { return this.getGlobalTime() < this.time; }
|
|
2025
|
-
|
|
2026
|
-
/** Returns true if set and elapsed
|
|
2027
|
-
* @return {boolean} */
|
|
2028
|
-
elapsed() { return this.getGlobalTime() >= this.time; }
|
|
2029
|
-
|
|
2030
|
-
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
2031
|
-
* @return {number} */
|
|
2032
|
-
get() { return this.isSet()? this.getGlobalTime() - this.time : 0; }
|
|
2033
|
-
|
|
2034
|
-
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
2035
|
-
* @return {number} */
|
|
2036
|
-
getPercent() { return this.isSet()? 1-percent(this.time - this.getGlobalTime(), 0, this.setTime) : 0; }
|
|
2037
|
-
|
|
2038
|
-
/** Get the time this timer was set to, returns 0 if not set
|
|
2039
|
-
* @return {number} */
|
|
2040
|
-
getSetTime() { return this.isSet() ? this.setTime : 0; }
|
|
2041
|
-
|
|
2042
|
-
/** Get the current global time this timer is based on
|
|
2043
|
-
* @return {number} */
|
|
2044
|
-
getGlobalTime() { return this.useRealTime ? timeReal : time; }
|
|
2045
|
-
|
|
2046
|
-
/** Returns this timer expressed as a string
|
|
2047
|
-
* @return {string} */
|
|
2048
|
-
toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
|
|
2042
|
+
ASSERT(isString(saveName), 'loadData requires saveName string');
|
|
2043
|
+
|
|
2044
|
+
// replace undefined values with defaults
|
|
2045
|
+
const data = localStorage[saveName];
|
|
2046
|
+
const loadedData = data ? JSON.parse(data) : {};
|
|
2047
|
+
return { ...defaultSaveData, ...loadedData };
|
|
2048
|
+
}
|
|
2049
2049
|
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2050
|
+
/** Write save data to local storage
|
|
2051
|
+
* @param {string} saveName - unique name for the game/save
|
|
2052
|
+
* @param {Object} saveData - object containing data to be saved
|
|
2053
|
+
* @memberof Utilities */
|
|
2054
|
+
function writeSaveData(saveName, saveData)
|
|
2055
|
+
{
|
|
2056
|
+
ASSERT(isString(saveName), 'saveData requires saveName string');
|
|
2057
|
+
localStorage[saveName] = JSON.stringify(saveData);
|
|
2053
2058
|
}
|
|
2054
2059
|
/**
|
|
2055
2060
|
* LittleJS Engine Settings
|
|
@@ -2710,17 +2715,19 @@ class EngineObject
|
|
|
2710
2715
|
this.additiveColor = undefined;
|
|
2711
2716
|
/** @property {boolean} - Should it flip along y axis when rendered */
|
|
2712
2717
|
this.mirror = false;
|
|
2718
|
+
/** @property {boolean} - Has object been destroyed? */
|
|
2719
|
+
this.destroyed = false;
|
|
2713
2720
|
|
|
2714
2721
|
// physical properties
|
|
2715
|
-
/** @property {number}
|
|
2722
|
+
/** @property {number} - How heavy the object is, static if 0 */
|
|
2716
2723
|
this.mass = objectDefaultMass;
|
|
2717
|
-
/** @property {number}
|
|
2724
|
+
/** @property {number} - How much to slow down velocity each frame (0-1) */
|
|
2718
2725
|
this.damping = objectDefaultDamping;
|
|
2719
|
-
/** @property {number}
|
|
2726
|
+
/** @property {number} - How much to slow down rotation each frame (0-1) */
|
|
2720
2727
|
this.angleDamping = objectDefaultAngleDamping;
|
|
2721
|
-
/** @property {number}
|
|
2728
|
+
/** @property {number} - How bouncy the object is when colliding (0-1) */
|
|
2722
2729
|
this.restitution = objectDefaultRestitution;
|
|
2723
|
-
/** @property {number}
|
|
2730
|
+
/** @property {number} - How much friction to apply when sliding (0-1) */
|
|
2724
2731
|
this.friction = objectDefaultFriction;
|
|
2725
2732
|
/** @property {number} - How much to scale gravity by for this object */
|
|
2726
2733
|
this.gravityScale = 1;
|
|
@@ -2929,10 +2936,10 @@ class EngineObject
|
|
|
2929
2936
|
if (!tileCollisionTest(oldPos, this.size, this))
|
|
2930
2937
|
{
|
|
2931
2938
|
// test which side we bounced off (or both if a corner)
|
|
2932
|
-
const
|
|
2933
|
-
const
|
|
2934
|
-
|
|
2935
|
-
if (
|
|
2939
|
+
const isBlockedX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
|
|
2940
|
+
const isBlockedY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
|
|
2941
|
+
const restitution = max(this.restitution, hitLayer.restitution);
|
|
2942
|
+
if (isBlockedX)
|
|
2936
2943
|
{
|
|
2937
2944
|
// try to move up a tiny bit
|
|
2938
2945
|
const epsilon = 1e-3;
|
|
@@ -2948,16 +2955,12 @@ class EngineObject
|
|
|
2948
2955
|
return;
|
|
2949
2956
|
}
|
|
2950
2957
|
|
|
2951
|
-
// move to previous position and bounce
|
|
2958
|
+
// move to previous X position and bounce
|
|
2952
2959
|
this.pos.x = oldPos.x;
|
|
2953
|
-
this.velocity.x *= -
|
|
2960
|
+
this.velocity.x *= -restitution;
|
|
2954
2961
|
}
|
|
2955
|
-
if (
|
|
2962
|
+
if (isBlockedY || !isBlockedX)
|
|
2956
2963
|
{
|
|
2957
|
-
// bounce velocity
|
|
2958
|
-
const restitution = max(this.restitution, hitLayer.restitution);
|
|
2959
|
-
this.velocity.y *= -restitution;
|
|
2960
|
-
|
|
2961
2964
|
if (wasFalling)
|
|
2962
2965
|
{
|
|
2963
2966
|
// adjust position to slightly away from nearest tile
|
|
@@ -2973,10 +2976,12 @@ class EngineObject
|
|
|
2973
2976
|
}
|
|
2974
2977
|
else
|
|
2975
2978
|
{
|
|
2976
|
-
// move to previous position
|
|
2979
|
+
// move to previous Y position
|
|
2977
2980
|
this.pos.y = oldPos.y;
|
|
2978
2981
|
this.groundObject = undefined;
|
|
2979
2982
|
}
|
|
2983
|
+
// bounce velocity
|
|
2984
|
+
this.velocity.y *= -restitution;
|
|
2980
2985
|
}
|
|
2981
2986
|
debugPhysics && debugRect(this.pos, this.size, '#f00');
|
|
2982
2987
|
}
|
|
@@ -2994,19 +2999,19 @@ class EngineObject
|
|
|
2994
2999
|
drawTile(this.pos, this.drawSize || this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
|
|
2995
3000
|
}
|
|
2996
3001
|
|
|
2997
|
-
/** Destroy this object, destroy its children, detach its parent, and mark it for removal
|
|
2998
|
-
|
|
3002
|
+
/** Destroy this object, destroy its children, detach its parent, and mark it for removal
|
|
3003
|
+
* @param {boolean} [immediate] - should attached effects be allowed to die off? */
|
|
3004
|
+
destroy(immediate=false)
|
|
2999
3005
|
{
|
|
3000
|
-
if (this.destroyed)
|
|
3001
|
-
return;
|
|
3006
|
+
if (this.destroyed) return;
|
|
3002
3007
|
|
|
3003
3008
|
// disconnect from parent and destroy children
|
|
3004
|
-
this.destroyed =
|
|
3009
|
+
this.destroyed = true;
|
|
3005
3010
|
this.parent?.removeChild(this);
|
|
3006
3011
|
for (const child of this.children)
|
|
3007
3012
|
{
|
|
3008
3013
|
child.parent = undefined;
|
|
3009
|
-
child.destroy();
|
|
3014
|
+
child.destroy(immediate);
|
|
3010
3015
|
}
|
|
3011
3016
|
}
|
|
3012
3017
|
|
|
@@ -3105,7 +3110,7 @@ class EngineObject
|
|
|
3105
3110
|
}
|
|
3106
3111
|
|
|
3107
3112
|
/** Check if overlapping another engine object
|
|
3108
|
-
* Collisions are
|
|
3113
|
+
* Collisions are resolved to prevent overlaps
|
|
3109
3114
|
* @param {EngineObject} object
|
|
3110
3115
|
* @return {boolean} */
|
|
3111
3116
|
isOverlappingObject(object)
|
|
@@ -3357,7 +3362,7 @@ class TileInfo
|
|
|
3357
3362
|
}
|
|
3358
3363
|
|
|
3359
3364
|
/**
|
|
3360
|
-
* Returns a tile info for an index using this tile as
|
|
3365
|
+
* Returns a tile info for an index using this tile as reference
|
|
3361
3366
|
* @param {Vector2|number} [index=0]
|
|
3362
3367
|
* @return {TileInfo}
|
|
3363
3368
|
*/
|
|
@@ -4187,7 +4192,7 @@ let engineFontImage;
|
|
|
4187
4192
|
class FontImage
|
|
4188
4193
|
{
|
|
4189
4194
|
/** Create an image font
|
|
4190
|
-
* @param {TileInfo} tileInfo - Tile info of first
|
|
4195
|
+
* @param {TileInfo} tileInfo - Tile info of first character in font
|
|
4191
4196
|
*/
|
|
4192
4197
|
constructor(tileInfo)
|
|
4193
4198
|
{
|
|
@@ -5063,7 +5068,7 @@ function inputRender()
|
|
|
5063
5068
|
}
|
|
5064
5069
|
}
|
|
5065
5070
|
|
|
5066
|
-
// center position for right
|
|
5071
|
+
// center position for right touch pad face buttons
|
|
5067
5072
|
function touchGamepadButtonCenter()
|
|
5068
5073
|
{
|
|
5069
5074
|
const center = mainCanvasSize.subtract(vec2(touchGamepadSize));
|
|
@@ -5764,50 +5769,62 @@ const tileCollisionLayers = [];
|
|
|
5764
5769
|
|
|
5765
5770
|
/** Get tile collision data for a given cell in the grid
|
|
5766
5771
|
* @param {Vector2} pos
|
|
5772
|
+
* @param {boolean} [solidOnly] - Only check solid layers?
|
|
5767
5773
|
* @return {number}
|
|
5768
5774
|
* @memberof TileLayers */
|
|
5769
|
-
function tileCollisionGetData(pos)
|
|
5775
|
+
function tileCollisionGetData(pos, solidOnly=true)
|
|
5770
5776
|
{
|
|
5771
5777
|
// check all tile collision layers
|
|
5772
5778
|
for (const layer of tileCollisionLayers)
|
|
5779
|
+
if (!solidOnly || layer.isSolid)
|
|
5773
5780
|
if (pos.arrayCheck(layer.size))
|
|
5774
|
-
|
|
5781
|
+
{
|
|
5782
|
+
const data = layer.getCollisionData(pos);
|
|
5783
|
+
if (data) return data;
|
|
5784
|
+
}
|
|
5775
5785
|
return 0;
|
|
5776
5786
|
}
|
|
5777
5787
|
|
|
5778
5788
|
/** Check if a tile layer collides with another object
|
|
5779
|
-
* @param {Vector2}
|
|
5780
|
-
* @param {Vector2}
|
|
5781
|
-
* @param {EngineObject} [
|
|
5782
|
-
* @param {boolean}
|
|
5789
|
+
* @param {Vector2} pos
|
|
5790
|
+
* @param {Vector2} [size=vec2()]
|
|
5791
|
+
* @param {EngineObject|TileCollisionCallback} [callbackObject] - Callback, engine object, or undefined
|
|
5792
|
+
* @param {boolean} [solidOnly] - Only check solid layers?
|
|
5783
5793
|
* @return {TileCollisionLayer}
|
|
5784
5794
|
* @memberof TileLayers */
|
|
5785
|
-
function tileCollisionTest(pos, size=vec2(),
|
|
5795
|
+
function tileCollisionTest(pos, size=vec2(), callbackObject, solidOnly=true)
|
|
5786
5796
|
{
|
|
5787
5797
|
for (const layer of tileCollisionLayers)
|
|
5788
5798
|
{
|
|
5789
5799
|
if (!solidOnly || layer.isSolid)
|
|
5790
|
-
if (layer.collisionTest(pos, size,
|
|
5800
|
+
if (layer.collisionTest(pos, size, callbackObject))
|
|
5791
5801
|
return layer;
|
|
5792
5802
|
}
|
|
5793
5803
|
}
|
|
5794
5804
|
|
|
5805
|
+
/**
|
|
5806
|
+
* @callback TileCollisionCallback - Function to handle a tile collision test
|
|
5807
|
+
* @param {number} tileData - the value of the tile at the position
|
|
5808
|
+
* @param {Vector2} pos - world space position of tile where the collision occurred
|
|
5809
|
+
* @memberof TileLayers
|
|
5810
|
+
*/
|
|
5811
|
+
|
|
5795
5812
|
/** Return the exact position of the boundary of first tile hit, undefined if nothing was hit.
|
|
5796
|
-
* The point will be inside the colliding tile if it hits
|
|
5797
|
-
* @param {Vector2}
|
|
5798
|
-
* @param {Vector2}
|
|
5799
|
-
* @param {EngineObject} [
|
|
5800
|
-
* @param {Vector2}
|
|
5801
|
-
* @param {boolean}
|
|
5813
|
+
* The point will be inside the colliding tile if it hits
|
|
5814
|
+
* @param {Vector2} posStart
|
|
5815
|
+
* @param {Vector2} posEnd
|
|
5816
|
+
* @param {EngineObject|TileCollisionCallback} [callbackObject] - Callback, engine object, or undefined
|
|
5817
|
+
* @param {Vector2} [normal] - Optional normal of the surface hit
|
|
5818
|
+
* @param {boolean} [solidOnly=true] - Only check solid layers?
|
|
5802
5819
|
* @return {Vector2|undefined} - position of the center of the tile hit or undefined if no hit
|
|
5803
5820
|
* @memberof TileLayers */
|
|
5804
|
-
function tileCollisionRaycast(posStart, posEnd,
|
|
5821
|
+
function tileCollisionRaycast(posStart, posEnd, callbackObject, normal, solidOnly=true)
|
|
5805
5822
|
{
|
|
5806
5823
|
for (const layer of tileCollisionLayers)
|
|
5807
5824
|
{
|
|
5808
5825
|
if (!solidOnly || layer.isSolid)
|
|
5809
5826
|
{
|
|
5810
|
-
const hitPos = layer.collisionRaycast(posStart, posEnd,
|
|
5827
|
+
const hitPos = layer.collisionRaycast(posStart, posEnd, callbackObject, normal)
|
|
5811
5828
|
if (hitPos) return hitPos;
|
|
5812
5829
|
}
|
|
5813
5830
|
}
|
|
@@ -6371,18 +6388,22 @@ class TileCollisionLayer extends TileLayer
|
|
|
6371
6388
|
/** Check if collision with another object should occur
|
|
6372
6389
|
* @param {Vector2} pos
|
|
6373
6390
|
* @param {Vector2} [size=vec2()]
|
|
6374
|
-
* @param {EngineObject} [object
|
|
6391
|
+
* @param {EngineObject|TileCollisionCallback} [callbackObject] - Callback, engine object, or undefined
|
|
6375
6392
|
* @return {boolean} */
|
|
6376
|
-
collisionTest(pos, size=new Vector2,
|
|
6393
|
+
collisionTest(pos, size=new Vector2, callbackObject)
|
|
6377
6394
|
{
|
|
6378
6395
|
ASSERT(isVector2(pos) && isVector2(size), 'pos and size must be Vector2s');
|
|
6379
|
-
ASSERT(!
|
|
6380
|
-
|
|
6381
|
-
//
|
|
6382
|
-
const
|
|
6383
|
-
|
|
6396
|
+
ASSERT(!callbackObject || typeof callbackObject === 'function' || callbackObject instanceof EngineObject, 'callbackObject must be a funtion or EngineObject');
|
|
6397
|
+
|
|
6398
|
+
// make function to check for collision
|
|
6399
|
+
const collisionTest = callbackObject ? typeof callbackObject === 'function' ?
|
|
6400
|
+
(tileData, pos)=> callbackObject(tileData, pos) :
|
|
6401
|
+
(tileData, pos)=> callbackObject.collideWithTile(tileData, pos) :
|
|
6402
|
+
()=> true;
|
|
6384
6403
|
|
|
6385
6404
|
// check any tiles in the area for collision
|
|
6405
|
+
const posX = pos.x - this.pos.x;
|
|
6406
|
+
const posY = pos.y - this.pos.y;
|
|
6386
6407
|
const minX = max(posX - size.x/2|0, 0);
|
|
6387
6408
|
const minY = max(posY - size.y/2|0, 0);
|
|
6388
6409
|
const maxX = min(posX + size.x/2, this.size.x);
|
|
@@ -6393,9 +6414,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
6393
6414
|
{
|
|
6394
6415
|
// check if the object should collide with this tile
|
|
6395
6416
|
const tileData = this.collisionData[y*this.size.x+x];
|
|
6396
|
-
if (tileData)
|
|
6397
|
-
if (!object || object.collideWithTile(tileData,
|
|
6398
|
-
hitPos.set(x + this.pos.x, y + this.pos.y)))
|
|
6417
|
+
if (tileData && collisionTest(tileData, hitPos.set(x+this.pos.x, y+this.pos.y)))
|
|
6399
6418
|
return true;
|
|
6400
6419
|
}
|
|
6401
6420
|
return false;
|
|
@@ -6403,52 +6422,68 @@ class TileCollisionLayer extends TileLayer
|
|
|
6403
6422
|
|
|
6404
6423
|
/** Return the exact position of the boundary of first tile hit, undefined if nothing was hit.
|
|
6405
6424
|
* The point will be inside the colliding tile if it hits (may have a tiny shift)
|
|
6406
|
-
* @param {Vector2}
|
|
6407
|
-
* @param {Vector2}
|
|
6408
|
-
* @param {EngineObject} [
|
|
6409
|
-
* @param {Vector2}
|
|
6425
|
+
* @param {Vector2} posStart
|
|
6426
|
+
* @param {Vector2} posEnd
|
|
6427
|
+
* @param {EngineObject|TileCollisionCallback} [callbackObject] - Callback, engine object, or undefined
|
|
6428
|
+
* @param {Vector2} [normal] - Optional normal of the surface hit
|
|
6410
6429
|
* @return {Vector2|undefined} */
|
|
6411
|
-
collisionRaycast(posStart, posEnd,
|
|
6430
|
+
collisionRaycast(posStart, posEnd, callbackObject, normal)
|
|
6412
6431
|
{
|
|
6413
6432
|
ASSERT(isVector2(posStart) && isVector2(posEnd), 'positions must be Vector2s');
|
|
6414
|
-
ASSERT(!
|
|
6433
|
+
ASSERT(!callbackObject || typeof callbackObject === 'function' || callbackObject instanceof EngineObject, 'callbackObject must be a funtion or EngineObject');
|
|
6415
6434
|
|
|
6416
|
-
|
|
6417
|
-
const collisionTest =
|
|
6435
|
+
// make function to check for collision
|
|
6436
|
+
const collisionTest = callbackObject ? typeof callbackObject === 'function' ?
|
|
6437
|
+
(tileData, pos)=> callbackObject(tileData, pos) :
|
|
6438
|
+
(tileData, pos)=> callbackObject.collideWithTile(tileData, pos) :
|
|
6439
|
+
(tileData)=> tileData > 0;
|
|
6440
|
+
const testFunction = (pos)=>
|
|
6418
6441
|
{
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
const tileData = this.getCollisionData(localPos);
|
|
6422
|
-
return tileData && (!object || object.collideWithTile(tileData, pos));
|
|
6442
|
+
const tileData = this.getCollisionData(localPos.set(pos.x - this.pos.x, pos.y - this.pos.y));
|
|
6443
|
+
return tileData && collisionTest(tileData, pos);
|
|
6423
6444
|
}
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6445
|
+
|
|
6446
|
+
// use line test against tile collision
|
|
6447
|
+
const localPos = new Vector2;
|
|
6448
|
+
const hitPos = lineTest(posStart, posEnd, testFunction, normal);
|
|
6449
|
+
if (debugRaycast && hitPos)
|
|
6427
6450
|
{
|
|
6428
6451
|
const tilePos = hitPos.floor().add(vec2(.5));
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
return hitPos;
|
|
6452
|
+
debugRect(tilePos, vec2(1), '#f008');
|
|
6453
|
+
debugLine(posStart, posEnd, '#00f', .02);
|
|
6454
|
+
debugLine(posStart, hitPos, '#f00', .02);
|
|
6455
|
+
debugPoint(hitPos, '#0f0');
|
|
6456
|
+
normal && debugLine(hitPos, hitPos.add(normal), '#ff0', .02);
|
|
6435
6457
|
}
|
|
6458
|
+
return hitPos;
|
|
6436
6459
|
}
|
|
6437
6460
|
}
|
|
6438
6461
|
/**
|
|
6439
6462
|
* LittleJS Particle System
|
|
6463
|
+
* - A simple but fast and flexible particle system
|
|
6464
|
+
* - Lightweight Particles are created and managed by ParticleEmitters
|
|
6465
|
+
* - The particle design tool can be used to help create emitters
|
|
6466
|
+
* @namespace Particles
|
|
6440
6467
|
*/
|
|
6441
6468
|
|
|
6442
6469
|
/**
|
|
6443
|
-
* @callback
|
|
6470
|
+
* @callback ParticleCallback - Function that processes a particle
|
|
6444
6471
|
* @param {Particle} particle
|
|
6445
|
-
* @memberof
|
|
6472
|
+
* @memberof Particles
|
|
6473
|
+
*/
|
|
6474
|
+
|
|
6475
|
+
/**
|
|
6476
|
+
* @callback ParticleCollideCallback - Collide callback for particles
|
|
6477
|
+
* @param {Particle} particle
|
|
6478
|
+
* @param {number} tileData
|
|
6479
|
+
* @param {Vector2} pos
|
|
6480
|
+
* @memberof Particles
|
|
6446
6481
|
*/
|
|
6447
6482
|
|
|
6448
6483
|
/**
|
|
6449
6484
|
* Particle Emitter - Spawns particles with the given settings
|
|
6450
6485
|
* @extends EngineObject
|
|
6451
|
-
* @memberof
|
|
6486
|
+
* @memberof Particles
|
|
6452
6487
|
* @example
|
|
6453
6488
|
* // create a particle emitter
|
|
6454
6489
|
* let pos = vec2(2,3);
|
|
@@ -6466,7 +6501,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
6466
6501
|
class ParticleEmitter extends EngineObject
|
|
6467
6502
|
{
|
|
6468
6503
|
/** Create a particle system with the given settings
|
|
6469
|
-
* @param {Vector2}
|
|
6504
|
+
* @param {Vector2} pos - World space position of the emitter
|
|
6470
6505
|
* @param {number} [angle] - Angle to emit the particles
|
|
6471
6506
|
* @param {number|Vector2} [emitSize] - World space size of the emitter (float for circle diameter, vec2 for rect)
|
|
6472
6507
|
* @param {number} [emitTime] - How long to stay alive (0 is forever)
|
|
@@ -6496,7 +6531,7 @@ class ParticleEmitter extends EngineObject
|
|
|
6496
6531
|
*/
|
|
6497
6532
|
constructor
|
|
6498
6533
|
(
|
|
6499
|
-
|
|
6534
|
+
pos,
|
|
6500
6535
|
angle,
|
|
6501
6536
|
emitSize = 0,
|
|
6502
6537
|
emitTime = 0,
|
|
@@ -6525,12 +6560,13 @@ class ParticleEmitter extends EngineObject
|
|
|
6525
6560
|
localSpace = false
|
|
6526
6561
|
)
|
|
6527
6562
|
{
|
|
6528
|
-
super(
|
|
6563
|
+
super(pos, vec2(), tileInfo, angle, undefined, renderOrder);
|
|
6529
6564
|
|
|
6530
6565
|
// emitter settings
|
|
6566
|
+
/** @property {boolean} - Should particles be emitted in a circle */
|
|
6567
|
+
this.emitCircle = typeof emitSize === 'number';
|
|
6531
6568
|
/** @property {number|Vector2} - World space size of the emitter (float for circle diameter, vec2 for rect) */
|
|
6532
|
-
this.emitSize = emitSize
|
|
6533
|
-
emitSize.copy() : emitSize;
|
|
6569
|
+
this.emitSize = typeof emitSize === 'number' ? vec2(emitSize) : emitSize.copy();
|
|
6534
6570
|
/** @property {number} - How long to stay alive (0 is forever) */
|
|
6535
6571
|
this.emitTime = emitTime;
|
|
6536
6572
|
/** @property {number} - How many particles per second to spawn, does not emit if 0 */
|
|
@@ -6581,26 +6617,31 @@ class ParticleEmitter extends EngineObject
|
|
|
6581
6617
|
this.localSpace = localSpace;
|
|
6582
6618
|
/** @property {number} - If non zero the particle is drawn as a trail, stretched in the direction of velocity */
|
|
6583
6619
|
this.trailScale = 0;
|
|
6584
|
-
/** @property {
|
|
6585
|
-
this.particleDestroyCallback = undefined;
|
|
6586
|
-
/** @property {ParticleCallbackFunction} - Callback when particle is created */
|
|
6620
|
+
/** @property {ParticleCallback} - Callback when particle is created */
|
|
6587
6621
|
this.particleCreateCallback = undefined;
|
|
6588
|
-
/** @property {
|
|
6589
|
-
this.
|
|
6622
|
+
/** @property {ParticleCallback} - Callback when particle is destroyed */
|
|
6623
|
+
this.particleDestroyCallback = undefined;
|
|
6624
|
+
/** @property {ParticleCollideCallback} - Callback when particle collides */
|
|
6625
|
+
this.particleCollideCallback = undefined;
|
|
6590
6626
|
/** @property {number} - Percentage of velocity to pass to particles (0-1) */
|
|
6591
6627
|
this.velocityInheritance = 0;
|
|
6628
|
+
/** @property {number} - Track particle emit time */
|
|
6629
|
+
this.emitTimeBuffer = 0;
|
|
6630
|
+
/** @property {Array<Particle>} - Array of particles for this emitter */
|
|
6631
|
+
this.particles = [];
|
|
6592
6632
|
|
|
6593
6633
|
// track previous position and angle
|
|
6594
6634
|
this.previousAngle = this.angle;
|
|
6595
6635
|
this.previousPos = this.pos.copy();
|
|
6596
6636
|
}
|
|
6597
6637
|
|
|
6598
|
-
/** Emitters do not have physics */
|
|
6599
|
-
updatePhysics() {}
|
|
6600
|
-
|
|
6601
6638
|
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
6602
6639
|
update()
|
|
6603
6640
|
{
|
|
6641
|
+
// physics sanity checks
|
|
6642
|
+
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
6643
|
+
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
6644
|
+
|
|
6604
6645
|
if (this.velocityInheritance)
|
|
6605
6646
|
{
|
|
6606
6647
|
// pass emitter velocity to particles
|
|
@@ -6614,7 +6655,7 @@ class ParticleEmitter extends EngineObject
|
|
|
6614
6655
|
}
|
|
6615
6656
|
|
|
6616
6657
|
// update emitter
|
|
6617
|
-
if (
|
|
6658
|
+
if (this.isActive())
|
|
6618
6659
|
{
|
|
6619
6660
|
// emit particles
|
|
6620
6661
|
if (this.emitRate && particleEmitRateScale)
|
|
@@ -6624,14 +6665,21 @@ class ParticleEmitter extends EngineObject
|
|
|
6624
6665
|
this.emitParticle();
|
|
6625
6666
|
}
|
|
6626
6667
|
}
|
|
6627
|
-
else
|
|
6628
|
-
this.destroy();
|
|
6668
|
+
else if (this.particles.length === 0)
|
|
6669
|
+
this.destroy(true);
|
|
6670
|
+
|
|
6671
|
+
// update and remove destroyed particles
|
|
6672
|
+
this.particles = this.particles.filter((p)=>
|
|
6673
|
+
{
|
|
6674
|
+
p.update();
|
|
6675
|
+
return !p.destroyed;
|
|
6676
|
+
});
|
|
6629
6677
|
|
|
6630
6678
|
if (debugParticles)
|
|
6631
6679
|
{
|
|
6632
6680
|
// show emitter bounds
|
|
6633
|
-
if (
|
|
6634
|
-
debugCircle(this.pos, this.emitSize/2, '#0f0');
|
|
6681
|
+
if (this.emitCircle)
|
|
6682
|
+
debugCircle(this.pos, this.emitSize.x/2, '#0f0');
|
|
6635
6683
|
else
|
|
6636
6684
|
debugRect(this.pos, this.emitSize, '#0f0', 0, this.angle);
|
|
6637
6685
|
}
|
|
@@ -6642,14 +6690,15 @@ class ParticleEmitter extends EngineObject
|
|
|
6642
6690
|
emitParticle()
|
|
6643
6691
|
{
|
|
6644
6692
|
// spawn a particle
|
|
6645
|
-
let pos =
|
|
6646
|
-
randInCircle(this.emitSize/2)
|
|
6647
|
-
: vec2(rand(-.5,.5), rand(-.5,.5))
|
|
6693
|
+
let pos = this.emitCircle ? // check if circle emitter
|
|
6694
|
+
randInCircle(this.emitSize.x/2) // circle emitter
|
|
6695
|
+
: vec2(rand(-.5,.5), rand(-.5,.5)) // box emitter
|
|
6648
6696
|
.multiply(this.emitSize).rotate(this.angle)
|
|
6649
6697
|
let angle = rand(this.particleConeAngle, -this.particleConeAngle);
|
|
6650
6698
|
if (!this.localSpace)
|
|
6651
6699
|
{
|
|
6652
|
-
pos
|
|
6700
|
+
pos.x += this.pos.x;
|
|
6701
|
+
pos.y += this.pos.y;
|
|
6653
6702
|
angle += this.angle;
|
|
6654
6703
|
}
|
|
6655
6704
|
|
|
@@ -6669,25 +6718,17 @@ class ParticleEmitter extends EngineObject
|
|
|
6669
6718
|
const velocityAngle = this.localSpace ? coneAngle : this.angle + coneAngle;
|
|
6670
6719
|
|
|
6671
6720
|
// build particle
|
|
6672
|
-
const
|
|
6673
|
-
|
|
6674
|
-
particle.angleVelocity = angleSpeed;
|
|
6721
|
+
const velocity = vec2(speed*sin(velocityAngle), speed*cos(velocityAngle));
|
|
6722
|
+
let angleVelocity = angleSpeed;
|
|
6675
6723
|
if (!this.localSpace && this.velocityInheritance > 0)
|
|
6676
6724
|
{
|
|
6677
6725
|
// apply emitter velocity to particle
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6726
|
+
velocity.x += this.velocity.x;
|
|
6727
|
+
velocity.y += this.velocity.y;
|
|
6728
|
+
angleVelocity += this.angleVelocity;
|
|
6681
6729
|
}
|
|
6682
|
-
particle
|
|
6683
|
-
|
|
6684
|
-
particle.angleDamping = this.angleDamping;
|
|
6685
|
-
particle.restitution = this.restitution;
|
|
6686
|
-
particle.friction = this.friction;
|
|
6687
|
-
particle.gravityScale = this.gravityScale;
|
|
6688
|
-
particle.collideTiles = this.collideTiles;
|
|
6689
|
-
particle.renderOrder = this.renderOrder;
|
|
6690
|
-
particle.mirror = randBool();
|
|
6730
|
+
const particle = new Particle(this, pos, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, velocity, angleVelocity);
|
|
6731
|
+
this.particles.push(particle);
|
|
6691
6732
|
|
|
6692
6733
|
// call particle create callback
|
|
6693
6734
|
this.particleCreateCallback?.(particle);
|
|
@@ -6696,140 +6737,245 @@ class ParticleEmitter extends EngineObject
|
|
|
6696
6737
|
return particle;
|
|
6697
6738
|
}
|
|
6698
6739
|
|
|
6699
|
-
|
|
6700
|
-
|
|
6740
|
+
/** Particle emitters do not have physics */
|
|
6741
|
+
updatePhysics() {}
|
|
6742
|
+
|
|
6743
|
+
/** Render all particles for this emitter */
|
|
6744
|
+
render()
|
|
6745
|
+
{
|
|
6746
|
+
// render all particles
|
|
6747
|
+
for (const particle of this.particles)
|
|
6748
|
+
particle.render();
|
|
6749
|
+
}
|
|
6750
|
+
|
|
6751
|
+
/** is emitter actively spawning */
|
|
6752
|
+
isActive() { return !this.emitTime || this.getAliveTime() < this.emitTime; }
|
|
6753
|
+
|
|
6754
|
+
/** Destroy the particle emitter
|
|
6755
|
+
* @param {boolean} [immediate] - should particle emitters and other attached effects be allowed to die off */
|
|
6756
|
+
destroy(immediate=false)
|
|
6757
|
+
{
|
|
6758
|
+
if (this.destroyed) return;
|
|
6759
|
+
|
|
6760
|
+
super.destroy(immediate);
|
|
6761
|
+
if (!immediate && this.particles.length > 0)
|
|
6762
|
+
{
|
|
6763
|
+
// wait for particles to die off
|
|
6764
|
+
this.destroyed = false;
|
|
6765
|
+
this.emitTime = -1;
|
|
6766
|
+
}
|
|
6767
|
+
}
|
|
6701
6768
|
}
|
|
6702
6769
|
|
|
6703
6770
|
///////////////////////////////////////////////////////////////////////////////
|
|
6704
6771
|
/**
|
|
6705
6772
|
* Particle Object - Created automatically by Particle Emitters
|
|
6706
|
-
* @
|
|
6707
|
-
* @memberof Engine
|
|
6773
|
+
* @memberof Particles
|
|
6708
6774
|
*/
|
|
6709
|
-
class Particle
|
|
6775
|
+
class Particle
|
|
6710
6776
|
{
|
|
6711
6777
|
/**
|
|
6712
6778
|
* Create a particle with the passed in settings
|
|
6713
6779
|
* Typically this is created automatically by a ParticleEmitter
|
|
6714
|
-
* @param {
|
|
6715
|
-
* @param {
|
|
6716
|
-
* @param {number}
|
|
6717
|
-
* @param {Color}
|
|
6718
|
-
* @param {Color}
|
|
6719
|
-
* @param {number}
|
|
6720
|
-
* @param {number}
|
|
6721
|
-
* @param {number}
|
|
6722
|
-
* @param {
|
|
6723
|
-
* @param {
|
|
6724
|
-
* @param {number} trailScale - If a trail, how long to make it
|
|
6725
|
-
* @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
|
|
6726
|
-
* @param {ParticleCallbackFunction} [destroyCallback] - Callback when particle dies
|
|
6780
|
+
* @param {ParticleEmitter} emitter - The emitter that created this particle
|
|
6781
|
+
* @param {Vector2} pos - World or local space position
|
|
6782
|
+
* @param {number} angle - Angle of the particle
|
|
6783
|
+
* @param {Color} colorStart - Color at start of life
|
|
6784
|
+
* @param {Color} colorEnd - Color at end of life
|
|
6785
|
+
* @param {number} lifeTime - How long to live for
|
|
6786
|
+
* @param {number} sizeStart - Size at start of life
|
|
6787
|
+
* @param {number} sizeEnd - Size at end of life
|
|
6788
|
+
* @param {Vector2} [velocity] - Velocity of the particle
|
|
6789
|
+
* @param {number} [angleVelocity] - Angular speed of the particle
|
|
6727
6790
|
*/
|
|
6728
|
-
constructor(
|
|
6729
|
-
)
|
|
6791
|
+
constructor(emitter, pos, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, velocity = vec2(), angleVelocity = 0)
|
|
6730
6792
|
{
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
/** @property {
|
|
6793
|
+
/** @property {ParticleEmitter} */
|
|
6794
|
+
this.emitter = emitter;
|
|
6795
|
+
/** @property {Vector2} */
|
|
6796
|
+
this.pos = pos;
|
|
6797
|
+
/** @property {number} */
|
|
6798
|
+
this.angle = angle;
|
|
6799
|
+
/** @property {Vector2} */
|
|
6800
|
+
this.size = vec2(sizeStart);
|
|
6801
|
+
/** @property {Color} */
|
|
6802
|
+
this.color = colorStart.copy();
|
|
6803
|
+
/** @property {Color} */
|
|
6734
6804
|
this.colorStart = colorStart;
|
|
6735
|
-
/** @property {Color}
|
|
6805
|
+
/** @property {Color} */
|
|
6736
6806
|
this.colorEnd = colorEnd;
|
|
6737
|
-
/** @property {number}
|
|
6807
|
+
/** @property {number} */
|
|
6738
6808
|
this.lifeTime = lifeTime;
|
|
6739
|
-
/** @property {number}
|
|
6809
|
+
/** @property {number} */
|
|
6740
6810
|
this.sizeStart = sizeStart;
|
|
6741
|
-
/** @property {number}
|
|
6811
|
+
/** @property {number} */
|
|
6742
6812
|
this.sizeEnd = sizeEnd;
|
|
6743
|
-
/** @property {
|
|
6744
|
-
this.
|
|
6745
|
-
/** @property {
|
|
6746
|
-
this.
|
|
6747
|
-
/** @property {number}
|
|
6748
|
-
this.
|
|
6749
|
-
/** @property {
|
|
6750
|
-
this.
|
|
6751
|
-
/** @property {
|
|
6752
|
-
this.
|
|
6753
|
-
|
|
6754
|
-
this.
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6813
|
+
/** @property {Vector2} */
|
|
6814
|
+
this.velocity = velocity;
|
|
6815
|
+
/** @property {number} */
|
|
6816
|
+
this.angleVelocity = angleVelocity;
|
|
6817
|
+
/** @property {number} */
|
|
6818
|
+
this.spawnTime = time;
|
|
6819
|
+
/** @property {boolean} */
|
|
6820
|
+
this.mirror = randBool();
|
|
6821
|
+
/** @property {EngineObject} */
|
|
6822
|
+
this.groundObject = undefined;
|
|
6823
|
+
/** @property {boolean} */
|
|
6824
|
+
this.destroyed = false;
|
|
6825
|
+
/** @property {TileInfo} */
|
|
6826
|
+
this.tileInfo = emitter.tileInfo;
|
|
6827
|
+
}
|
|
6828
|
+
|
|
6829
|
+
/** Update the particle */
|
|
6758
6830
|
update()
|
|
6759
6831
|
{
|
|
6760
|
-
|
|
6832
|
+
// emitter properties
|
|
6833
|
+
const emitter = this.emitter;
|
|
6834
|
+
const damping = emitter.damping;
|
|
6835
|
+
const angleDamping = emitter.angleDamping;
|
|
6836
|
+
const restitution = emitter.restitution;
|
|
6837
|
+
const friction = emitter.friction;
|
|
6838
|
+
const gravityScale = emitter.gravityScale;
|
|
6839
|
+
const collideTiles = emitter.collideTiles;
|
|
6840
|
+
const collideCallback = emitter.particleCollideCallback;
|
|
6841
|
+
|
|
6842
|
+
// destroy particle when its time runs out
|
|
6843
|
+
if (this.lifeTime > 0 && time - this.spawnTime > this.lifeTime)
|
|
6761
6844
|
{
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
if (length2 > objectMaxSpeed*objectMaxSpeed)
|
|
6765
|
-
{
|
|
6766
|
-
const s = objectMaxSpeed / length2**.5;
|
|
6767
|
-
this.velocity.x *= s;
|
|
6768
|
-
this.velocity.y *= s;
|
|
6769
|
-
}
|
|
6845
|
+
this.destroy();
|
|
6846
|
+
return;
|
|
6770
6847
|
}
|
|
6771
6848
|
|
|
6772
|
-
|
|
6849
|
+
// apply physics
|
|
6850
|
+
const oldPos = this.pos.copy();
|
|
6851
|
+
this.velocity.x *= damping;
|
|
6852
|
+
this.velocity.y *= damping;
|
|
6853
|
+
this.pos.x += this.velocity.x += gravity.x * gravityScale;
|
|
6854
|
+
this.pos.y += this.velocity.y += gravity.y * gravityScale;
|
|
6855
|
+
this.angle += this.angleVelocity *= angleDamping;
|
|
6856
|
+
|
|
6857
|
+
// don't do collision if solver disabled
|
|
6858
|
+
if (!enablePhysicsSolver || !collideTiles) return;
|
|
6859
|
+
|
|
6860
|
+
// apply max circular speed to prevent going through collision
|
|
6861
|
+
const length2 = this.velocity.lengthSquared();
|
|
6862
|
+
if (length2 > objectMaxSpeed*objectMaxSpeed)
|
|
6773
6863
|
{
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
this.
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6864
|
+
const s = objectMaxSpeed / length2**.5;
|
|
6865
|
+
this.velocity.x *= s;
|
|
6866
|
+
this.velocity.y *= s;
|
|
6867
|
+
}
|
|
6868
|
+
|
|
6869
|
+
// check collision against tiles
|
|
6870
|
+
this.groundObject = undefined;
|
|
6871
|
+
const testCollision = collideCallback ? (pos)=>
|
|
6872
|
+
{
|
|
6873
|
+
const data = tileCollisionGetData(pos);
|
|
6874
|
+
return data && collideCallback(this, data, pos);
|
|
6875
|
+
} : (pos)=> tileCollisionGetData(pos) > 0;
|
|
6876
|
+
|
|
6877
|
+
if (testCollision(this.pos))
|
|
6878
|
+
{
|
|
6879
|
+
// if already was stuck in collision, don't do anything
|
|
6880
|
+
const hitLayer = tileCollisionTest(this.pos);
|
|
6881
|
+
if (!testCollision(oldPos))
|
|
6882
|
+
{
|
|
6883
|
+
if (!collideCallback || collideCallback?.(this, hitLayer))
|
|
6884
|
+
{
|
|
6885
|
+
// test which side we bounced off (or both if a corner)
|
|
6886
|
+
const isBlockedX = testCollision(vec2(this.pos.x, oldPos.y));
|
|
6887
|
+
const isBlockedY = testCollision(vec2(oldPos.x, this.pos.y));
|
|
6888
|
+
const hitRestitution = max(restitution, hitLayer.restitution);
|
|
6889
|
+
const hitFriction = max(friction, hitLayer.friction);
|
|
6890
|
+
if (isBlockedX)
|
|
6891
|
+
{
|
|
6892
|
+
// move to previous X position and bounce
|
|
6893
|
+
this.pos.x = oldPos.x;
|
|
6894
|
+
this.velocity.x *= -hitRestitution;
|
|
6895
|
+
this.velocity.y *= hitFriction;
|
|
6896
|
+
}
|
|
6897
|
+
if (isBlockedY || !isBlockedX)
|
|
6898
|
+
{
|
|
6899
|
+
const wasFalling = this.velocity.y < 0 && gravity.y < 0 || this.velocity.y > 0 && gravity.y > 0;
|
|
6900
|
+
if (wasFalling)
|
|
6901
|
+
this.groundObject = hitLayer;
|
|
6902
|
+
|
|
6903
|
+
// move to previous Y position and bounce
|
|
6904
|
+
this.pos.y = oldPos.y;
|
|
6905
|
+
this.velocity.y *= -hitRestitution;
|
|
6906
|
+
this.velocity.x *= hitFriction;
|
|
6907
|
+
}
|
|
6908
|
+
debugPhysics && debugRect(this.pos, this.size, '#f00');
|
|
6909
|
+
}
|
|
6910
|
+
}
|
|
6780
6911
|
}
|
|
6781
6912
|
}
|
|
6782
6913
|
|
|
6783
|
-
/**
|
|
6914
|
+
/** Destroy this particle */
|
|
6915
|
+
destroy()
|
|
6916
|
+
{
|
|
6917
|
+
const destroyCallback = this.emitter.particleDestroyCallback;
|
|
6918
|
+
const c = this.colorEnd;
|
|
6919
|
+
this.color.set(c.r, c.g, c.b, c.a);
|
|
6920
|
+
this.size.set(this.sizeEnd, this.sizeEnd);
|
|
6921
|
+
this.destroyed = true;
|
|
6922
|
+
destroyCallback?.(this);
|
|
6923
|
+
}
|
|
6924
|
+
|
|
6925
|
+
/** Render the particle, automatically called each frame */
|
|
6784
6926
|
render()
|
|
6785
6927
|
{
|
|
6928
|
+
// emitter properties
|
|
6929
|
+
const emitter = this.emitter;
|
|
6930
|
+
const localSpace = emitter.localSpace;
|
|
6931
|
+
const additive = emitter.additive;
|
|
6932
|
+
const trailScale = emitter.trailScale;
|
|
6933
|
+
const fadeRate = emitter.fadeRate / 2;
|
|
6934
|
+
|
|
6786
6935
|
// lerp color and size
|
|
6787
6936
|
const p1 = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1, p2 = 1-p1;
|
|
6788
6937
|
const radius = p2 * this.sizeStart + p1 * this.sizeEnd;
|
|
6789
6938
|
const size = vec2(radius);
|
|
6939
|
+
const alphaFade = p1 < fadeRate ? p1/fadeRate :
|
|
6940
|
+
p1 > 1-fadeRate ? (1-p1)/fadeRate : 1;
|
|
6790
6941
|
this.color.r = p2 * this.colorStart.r + p1 * this.colorEnd.r;
|
|
6791
6942
|
this.color.g = p2 * this.colorStart.g + p1 * this.colorEnd.g;
|
|
6792
6943
|
this.color.b = p2 * this.colorStart.b + p1 * this.colorEnd.b;
|
|
6793
|
-
this.color.a = p2 * this.colorStart.a + p1 * this.colorEnd.a;
|
|
6794
|
-
|
|
6795
|
-
// fade alpha
|
|
6796
|
-
const fadeRate = this.fadeRate/2;
|
|
6797
|
-
this.color.a *= p1 < fadeRate ? p1/fadeRate :
|
|
6798
|
-
p1 > 1-fadeRate ? (1-p1)/fadeRate : 1;
|
|
6944
|
+
this.color.a = (p2 * this.colorStart.a + p1 * this.colorEnd.a) * alphaFade;
|
|
6799
6945
|
|
|
6800
6946
|
// draw the particle
|
|
6801
|
-
|
|
6947
|
+
additive && setBlendMode(true);
|
|
6802
6948
|
|
|
6803
6949
|
// update the position and angle for drawing
|
|
6804
|
-
|
|
6805
|
-
|
|
6950
|
+
const pos = this.pos.copy();
|
|
6951
|
+
let angle = this.angle;
|
|
6952
|
+
if (localSpace)
|
|
6806
6953
|
{
|
|
6807
6954
|
// in local space of emitter
|
|
6808
|
-
const a =
|
|
6955
|
+
const a = emitter.angle;
|
|
6809
6956
|
const c = cos(a), s = sin(a);
|
|
6810
|
-
pos
|
|
6811
|
-
|
|
6812
|
-
angle +=
|
|
6957
|
+
pos.set(emitter.pos.x + pos.x*c - pos.y*s,
|
|
6958
|
+
emitter.pos.y + pos.x*s + pos.y*c);
|
|
6959
|
+
angle += a;
|
|
6813
6960
|
}
|
|
6814
|
-
if (
|
|
6961
|
+
if (trailScale)
|
|
6815
6962
|
{
|
|
6816
6963
|
// trail style particles
|
|
6817
|
-
const
|
|
6818
|
-
this.velocity.rotate(-
|
|
6819
|
-
|
|
6820
|
-
const speed = direction.length();
|
|
6964
|
+
const velocity = localSpace ?
|
|
6965
|
+
this.velocity.rotate(-emitter.angle) : this.velocity;
|
|
6966
|
+
const speed = velocity.length();
|
|
6821
6967
|
if (speed)
|
|
6822
6968
|
{
|
|
6823
6969
|
// stretch in direction of motion
|
|
6824
|
-
const trailLength = speed *
|
|
6970
|
+
const trailLength = speed * trailScale;
|
|
6825
6971
|
size.y = max(size.x, trailLength);
|
|
6826
|
-
angle = atan2(
|
|
6972
|
+
angle = atan2(velocity.x, velocity.y);
|
|
6827
6973
|
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
6828
6974
|
}
|
|
6829
6975
|
}
|
|
6830
6976
|
else
|
|
6831
6977
|
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
6832
|
-
|
|
6978
|
+
additive && setBlendMode();
|
|
6833
6979
|
debugParticles && debugRect(pos, size, '#f005', 0, angle);
|
|
6834
6980
|
}
|
|
6835
6981
|
}
|
|
@@ -8109,7 +8255,7 @@ class PostProcessPlugin
|
|
|
8109
8255
|
{
|
|
8110
8256
|
/** Create global post processing shader
|
|
8111
8257
|
* @param {string} shaderCode
|
|
8112
|
-
* @param {boolean} [includeMainCanvas] - combine
|
|
8258
|
+
* @param {boolean} [includeMainCanvas] - combine mainCanvas onto glCanvas
|
|
8113
8259
|
* @param {boolean} [feedbackTexture] - use glCanvas from previous frame as the texture
|
|
8114
8260
|
* @example
|
|
8115
8261
|
* // create the post process plugin object
|
|
@@ -8502,7 +8648,7 @@ class UISystemPlugin
|
|
|
8502
8648
|
// navigation properties
|
|
8503
8649
|
/** @property {UIObject} - Object currently selected by navigation (gamepad or keyboard) */
|
|
8504
8650
|
this.navigationObject = undefined;
|
|
8505
|
-
/** @property {Timer} -
|
|
8651
|
+
/** @property {Timer} - Cool down timer for navigation inputs */
|
|
8506
8652
|
this.navigationTimer = new Timer(undefined, true);
|
|
8507
8653
|
/** @property {number} - Time between navigation inputs in seconds */
|
|
8508
8654
|
this.navigationDelay = .2;
|
|
@@ -8968,7 +9114,7 @@ class UISystemPlugin
|
|
|
8968
9114
|
|
|
8969
9115
|
const savedNavigationDirection = uiSystem.navigationDirection;
|
|
8970
9116
|
|
|
8971
|
-
// allow both
|
|
9117
|
+
// allow both axes for navigation
|
|
8972
9118
|
uiSystem.navigationDirection = 2;
|
|
8973
9119
|
|
|
8974
9120
|
// confirm menu
|
|
@@ -9063,7 +9209,7 @@ class UIObject
|
|
|
9063
9209
|
this.lineWidth = uiSystem.defaultLineWidth;
|
|
9064
9210
|
/** @property {number} - Corner radius for rounded rects */
|
|
9065
9211
|
this.cornerRadius = uiSystem.defaultCornerRadius;
|
|
9066
|
-
/** @property {string} - Font for this
|
|
9212
|
+
/** @property {string} - Font for this object */
|
|
9067
9213
|
this.font = uiSystem.defaultFont;
|
|
9068
9214
|
/** @property {string} - Font style for this object or undefined */
|
|
9069
9215
|
this.fontStyle = undefined;
|
|
@@ -9551,6 +9697,8 @@ class UIScrollbar extends UIObject
|
|
|
9551
9697
|
this.value = value;
|
|
9552
9698
|
/** @property {Color} - Color for the handle part of the scrollbar */
|
|
9553
9699
|
this.handleColor = handleColor.copy();
|
|
9700
|
+
/** @property {boolean} - Should it fill up like a progress bar? */
|
|
9701
|
+
this.fillMode = false;
|
|
9554
9702
|
|
|
9555
9703
|
// set properties
|
|
9556
9704
|
this.text = text;
|
|
@@ -9596,19 +9744,30 @@ class UIScrollbar extends UIObject
|
|
|
9596
9744
|
|
|
9597
9745
|
// handle horizontal or vertical scrollbar
|
|
9598
9746
|
const isHorizontal = this.size.x > this.size.y;
|
|
9599
|
-
const
|
|
9600
|
-
const
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9747
|
+
const barWidth = isHorizontal ? this.size.x : this.size.y;
|
|
9748
|
+
const handleWidth = isHorizontal ? this.size.y : this.size.x;
|
|
9749
|
+
if (this.fillMode)
|
|
9750
|
+
{
|
|
9751
|
+
// draw progress bar
|
|
9752
|
+
const minWidth = min(handleWidth, this.cornerRadius * 2);
|
|
9753
|
+
const progressWidth = lerp(minWidth, barWidth, this.value);
|
|
9754
|
+
const p = (progressWidth - barWidth) * (isHorizontal ? .5 : -.5);
|
|
9755
|
+
const pos = this.pos.add(isHorizontal ? vec2(p, 0) : vec2(0, p));
|
|
9756
|
+
const color = this.disabled ? this.disabledColor : this.handleColor;
|
|
9757
|
+
const drawSize = isHorizontal ?
|
|
9758
|
+
vec2(progressWidth, this.size.y) : vec2(this.size.x, progressWidth);
|
|
9759
|
+
uiSystem.drawRect(pos, drawSize, color, this.lineWidth, this.lineColor, this.cornerRadius, this.gradientColor);
|
|
9760
|
+
}
|
|
9761
|
+
else
|
|
9762
|
+
{
|
|
9763
|
+
// draw the scrollbar handle
|
|
9764
|
+
const value = isHorizontal ? this.value : 1 - this.value;
|
|
9765
|
+
const p = (barWidth - handleWidth) * (value - .5);
|
|
9766
|
+
const pos = this.pos.add(isHorizontal ? vec2(p, 0) : vec2(0, p));
|
|
9767
|
+
const color = this.disabled ? this.disabledColor : this.handleColor;
|
|
9768
|
+
const drawSize = vec2(handleWidth);
|
|
9769
|
+
uiSystem.drawRect(pos, drawSize, color, this.lineWidth, this.lineColor, this.cornerRadius, this.gradientColor);
|
|
9770
|
+
}
|
|
9612
9771
|
|
|
9613
9772
|
// draw the text scaled to fit on the scrollbar
|
|
9614
9773
|
const textSize = this.getTextSize();
|
|
@@ -9630,7 +9789,7 @@ class UIScrollbar extends UIObject
|
|
|
9630
9789
|
* @extends UIObject
|
|
9631
9790
|
* @example
|
|
9632
9791
|
* // Create a video player UI object
|
|
9633
|
-
* const video = new VideoPlayerUIObject(vec2(400, 300), vec2(320, 240), '
|
|
9792
|
+
* const video = new VideoPlayerUIObject(vec2(400, 300), vec2(320, 240), 'video.mp4', true);
|
|
9634
9793
|
* video.play();
|
|
9635
9794
|
* @memberof UISystem
|
|
9636
9795
|
*/
|
|
@@ -10379,11 +10538,11 @@ class Box2dStaticObject extends Box2dObject
|
|
|
10379
10538
|
|
|
10380
10539
|
///////////////////////////////////////////////////////////////////////////////
|
|
10381
10540
|
/**
|
|
10382
|
-
* Box2D
|
|
10541
|
+
* Box2D Kinematic Object - Box2d with a kinematic physics body
|
|
10383
10542
|
* @extends Box2dObject
|
|
10384
10543
|
* @memberof Box2D
|
|
10385
10544
|
*/
|
|
10386
|
-
class
|
|
10545
|
+
class Box2dKinematicObject extends Box2dObject
|
|
10387
10546
|
{
|
|
10388
10547
|
/** Create a LittleJS object with Box2d physics
|
|
10389
10548
|
* @param {Vector2} [pos]
|
|
@@ -10435,20 +10594,19 @@ class Box2dTileLayer extends Box2dStaticObject
|
|
|
10435
10594
|
this.destroyAllFixtures();
|
|
10436
10595
|
|
|
10437
10596
|
// create box2d object for this layer
|
|
10438
|
-
const size = this.tileLayer.size;
|
|
10439
10597
|
this.pos = this.tileLayer.pos.copy();
|
|
10440
|
-
this.size = size.copy();
|
|
10598
|
+
this.size = this.tileLayer.size.copy();
|
|
10441
10599
|
|
|
10442
10600
|
// track which tiles have been processed
|
|
10443
10601
|
const processed = [];
|
|
10444
|
-
const getIndex = (x, y)=> x + y * size.x;
|
|
10602
|
+
const getIndex = (x, y)=> x + y * this.size.x;
|
|
10445
10603
|
const isSolidUnprocessed = (x, y)=>
|
|
10446
10604
|
!processed[getIndex(x, y)] &&
|
|
10447
10605
|
this.tileLayer.getCollisionData(vec2(x, y)) > 0;
|
|
10448
10606
|
|
|
10449
10607
|
// combine tiles into larger boxes
|
|
10450
|
-
for (let x = 0; x < size.x; ++x)
|
|
10451
|
-
for (let y = 0; y < size.y; ++y)
|
|
10608
|
+
for (let x = 0; x < this.size.x; ++x)
|
|
10609
|
+
for (let y = 0; y < this.size.y; ++y)
|
|
10452
10610
|
{
|
|
10453
10611
|
if (!isSolidUnprocessed(x, y)) continue;
|
|
10454
10612
|
|