littlejsengine 1.17.1 → 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 +254 -171
- package/dist/littlejs.esm.js +892 -628
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +889 -626
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +885 -622
- package/examples/box2d/gameObjects.js +3 -3
- package/examples/electron/index.html +2 -2
- package/examples/index.html +10 -9
- package/examples/logo.png +0 -0
- package/examples/logo2.png +0 -0
- package/examples/module/build.bat +7 -0
- package/examples/module/build.js +121 -0
- package/examples/particles/index.html +1 -1
- package/examples/platformer/game.js +1 -1
- package/examples/platformer/gameEffects.js +5 -10
- package/examples/platformer/gameLevel.js +3 -3
- package/examples/puzzle/game.js +2 -2
- package/examples/shorts/base.html +4 -1
- package/examples/shorts/box2dPool.js +2 -2
- package/examples/shorts/box2dTileLayer.js +48 -0
- package/examples/shorts/colors.js +1 -1
- package/examples/shorts/fps.js +1 -1
- package/examples/shorts/music.js +5 -4
- package/examples/shorts/musicPlayer.js +4 -3
- package/examples/shorts/parallax.js +2 -2
- package/examples/shorts/tiltedView.js +1 -1
- package/examples/starter/index.html +2 -2
- package/package.json +5 -5
- package/plugins/box2d.js +166 -63
- package/plugins/pluginExport.js +1 -1
- package/plugins/postProcess.js +1 -1
- package/plugins/uiSystem.js +33 -20
- package/plugins/zzfxm.js +2 -2
- package/reference.md +9 -8
- package/src/engine.js +111 -134
- package/src/engineAudio.js +44 -58
- package/src/engineDebug.js +4 -4
- package/src/engineDraw.js +7 -4
- package/src/engineExport.js +2 -1
- package/src/engineInput.js +5 -5
- package/src/engineMath.js +9 -9
- package/src/engineObject.js +25 -25
- package/src/engineParticles.js +241 -118
- package/src/engineTileLayer.js +117 -88
- package/src/engineUtilities.js +105 -78
- package/src/engineWebGL.js +2 -2
- 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}
|
|
@@ -88,8 +88,9 @@ function getPaused() { return paused; }
|
|
|
88
88
|
* @memberof Engine */
|
|
89
89
|
function setPaused(isPaused=true) { paused = isPaused; }
|
|
90
90
|
|
|
91
|
-
//
|
|
91
|
+
// Engine internal variables
|
|
92
92
|
let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
|
|
93
|
+
let showEngineVersion = true;
|
|
93
94
|
|
|
94
95
|
///////////////////////////////////////////////////////////////////////////////
|
|
95
96
|
// plugin hooks
|
|
@@ -153,16 +154,17 @@ function engineAddPlugin(update, render, glContextLost, glContextRestored)
|
|
|
153
154
|
* @example
|
|
154
155
|
* // Basic engine startup
|
|
155
156
|
* engineInit(
|
|
156
|
-
* ()
|
|
157
|
-
* ()
|
|
158
|
-
* ()
|
|
159
|
-
* ()
|
|
160
|
-
* ()
|
|
157
|
+
* ()=> { LOG('Game initialized!'); }, // gameInit
|
|
158
|
+
* ()=> { updateGameLogic(); }, // gameUpdate
|
|
159
|
+
* ()=> { updateUI(); }, // gameUpdatePost
|
|
160
|
+
* ()=> { drawBackground(); }, // gameRender
|
|
161
|
+
* ()=> { drawHUD(); }, // gameRenderPost
|
|
161
162
|
* ['tiles.png', 'tilesLevel.png'] // images to load
|
|
162
163
|
* );
|
|
163
164
|
* @memberof Engine */
|
|
164
165
|
async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
165
166
|
{
|
|
167
|
+
showEngineVersion && console.log(`${engineName} Engine v${engineVersion}`);
|
|
166
168
|
ASSERT(!mainContext, 'engine already initialized');
|
|
167
169
|
ASSERT(isArray(imageSources), 'pass in images as array');
|
|
168
170
|
|
|
@@ -431,7 +433,6 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
431
433
|
promises.push(new Promise(resolve =>
|
|
432
434
|
{
|
|
433
435
|
let t = 0;
|
|
434
|
-
console.log(`${engineName} Engine v${engineVersion}`);
|
|
435
436
|
updateSplash();
|
|
436
437
|
function updateSplash()
|
|
437
438
|
{
|
|
@@ -463,13 +464,13 @@ function engineObjectsUpdate()
|
|
|
463
464
|
engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
|
|
464
465
|
|
|
465
466
|
// recursive object update
|
|
466
|
-
function
|
|
467
|
+
function updateChildObject(o)
|
|
467
468
|
{
|
|
468
469
|
if (o.destroyed) return;
|
|
469
470
|
|
|
470
471
|
o.update();
|
|
471
472
|
for (const child of o.children)
|
|
472
|
-
|
|
473
|
+
updateChildObject(child);
|
|
473
474
|
}
|
|
474
475
|
for (const o of engineObjects)
|
|
475
476
|
{
|
|
@@ -479,7 +480,7 @@ function engineObjectsUpdate()
|
|
|
479
480
|
o.update();
|
|
480
481
|
o.updatePhysics();
|
|
481
482
|
for (const child of o.children)
|
|
482
|
-
|
|
483
|
+
updateChildObject(child);
|
|
483
484
|
o.updateTransforms();
|
|
484
485
|
}
|
|
485
486
|
|
|
@@ -488,11 +489,14 @@ function engineObjectsUpdate()
|
|
|
488
489
|
}
|
|
489
490
|
|
|
490
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?
|
|
491
495
|
* @memberof Engine */
|
|
492
|
-
function engineObjectsDestroy()
|
|
496
|
+
function engineObjectsDestroy(immediate=true)
|
|
493
497
|
{
|
|
494
498
|
for (const o of engineObjects)
|
|
495
|
-
o.parent || o.destroy();
|
|
499
|
+
o.parent || o.destroy(immediate);
|
|
496
500
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
497
501
|
}
|
|
498
502
|
|
|
@@ -567,165 +571,140 @@ function engineObjectsRaycast(start, end, objects=engineObjects)
|
|
|
567
571
|
///////////////////////////////////////////////////////////////////////////////
|
|
568
572
|
function drawEngineLogo(t)
|
|
569
573
|
{
|
|
574
|
+
const blackAndWhite = 0;
|
|
575
|
+
const showName = 1;
|
|
576
|
+
|
|
570
577
|
// LittleJS Logo and Splash Screen
|
|
571
578
|
const x = mainContext;
|
|
572
579
|
const w = mainCanvas.width = innerWidth;
|
|
573
580
|
const h = mainCanvas.height = innerHeight;
|
|
574
|
-
|
|
575
581
|
{
|
|
576
582
|
// background
|
|
577
583
|
const p3 = percent(t, 1, .8);
|
|
578
584
|
const p4 = percent(t, 0, .5);
|
|
579
|
-
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);
|
|
580
586
|
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
581
587
|
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
582
588
|
x.save();
|
|
583
589
|
x.fillStyle = g;
|
|
584
590
|
x.fillRect(0,0,w,h);
|
|
585
591
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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)=>
|
|
589
610
|
{
|
|
590
611
|
x.beginPath();
|
|
591
|
-
x.
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
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)=>
|
|
596
616
|
{
|
|
597
617
|
x.beginPath();
|
|
598
|
-
x.
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
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)=>
|
|
603
622
|
{
|
|
604
|
-
const D = (A+B)/2, E = p*(B-A)/2;
|
|
605
623
|
x.beginPath();
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
x.
|
|
609
|
-
|
|
610
|
-
}
|
|
611
|
-
const color = (c
|
|
612
|
-
|
|
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
|
|
613
632
|
const alpha = wave(1,1,t);
|
|
614
633
|
const p = percent(alpha, .1, .5);
|
|
615
|
-
|
|
616
|
-
// setup
|
|
634
|
+
const size = min(6, min(w,h)/99);
|
|
617
635
|
x.translate(w/2,h/2);
|
|
618
|
-
const size = min(6, min(w,h)/99); // fit to screen
|
|
619
636
|
x.scale(size,size);
|
|
620
637
|
x.translate(-40,-35);
|
|
638
|
+
p < 1 && x.setLineDash([99*p,99]);
|
|
621
639
|
x.lineJoin = x.lineCap = 'round';
|
|
622
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();
|
|
623
654
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
rect(
|
|
641
|
-
|
|
642
|
-
// cab
|
|
643
|
-
rect(
|
|
644
|
-
|
|
645
|
-
rect(21,21,-6,9);
|
|
646
|
-
|
|
647
|
-
// little stack
|
|
648
|
-
rect(37,14,9,6,color(3,2));
|
|
649
|
-
rect(37,14,4.5,6,color(3,3));
|
|
650
|
-
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
|
|
651
676
|
|
|
652
677
|
// big stack
|
|
653
|
-
rect(
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
rect(
|
|
657
|
-
circle(55,2,11.4,.5,PI-.5,color(3,3));
|
|
658
|
-
circle(55,2,11.4,.5,PI/2,color(3,2),1);
|
|
659
|
-
circle(55,2,11.4,.5,PI-.5);
|
|
660
|
-
rect(45,7,20,-7,color(0,2));
|
|
661
|
-
rect(45,-1,20,4,color(0,3));
|
|
662
|
-
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);
|
|
663
682
|
|
|
664
683
|
// engine
|
|
665
|
-
for (let i=5;
|
|
666
|
-
|
|
667
|
-
// stagger radius to fix slight seam
|
|
668
|
-
circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
|
|
669
|
-
circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
|
|
670
|
-
circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
|
|
671
|
-
}
|
|
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
|
|
672
686
|
|
|
673
687
|
// engine outline
|
|
674
|
-
|
|
675
|
-
circle(
|
|
676
|
-
circle(
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
//
|
|
680
|
-
circle(60,30,4,PI,3*PI,color(3,2));
|
|
681
|
-
circle(60,30,4,PI,2*PI,color(3,3));
|
|
682
|
-
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
|
|
683
694
|
|
|
684
|
-
//
|
|
685
|
-
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--;)
|
|
686
702
|
{
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
x.lineTo(53+(1+i*2.9)*p,40);
|
|
691
|
-
x.lineTo(53+(4+i*3.5)*p,54);
|
|
692
|
-
x.fillStyle = color(0,i%2+2);
|
|
693
|
-
x.fill();
|
|
694
|
-
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);
|
|
695
706
|
}
|
|
696
707
|
|
|
697
|
-
// wheels
|
|
698
|
-
rect(6,40,5,5);
|
|
699
|
-
rect(6,40,5,5,color());
|
|
700
|
-
rect(15,54,38,-14,color());
|
|
701
|
-
for (let i=3; i--;)
|
|
702
|
-
for (let j=2; j--;)
|
|
703
|
-
{
|
|
704
|
-
circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
|
|
705
|
-
x.stroke();
|
|
706
|
-
circle(15*i+15,47,j?7:1,0,PI,color(i,2));
|
|
707
|
-
x.stroke();
|
|
708
|
-
}
|
|
709
|
-
line(6,40,68,40); // center
|
|
710
|
-
line(77,54,4,54); // bottom
|
|
711
|
-
|
|
712
|
-
// draw engine name
|
|
713
|
-
const s = engineName;
|
|
714
|
-
x.font = '900 16px arial';
|
|
715
|
-
x.textAlign = 'center';
|
|
716
|
-
x.textBaseline = 'top';
|
|
717
|
-
x.lineWidth = .1+p*3.9;
|
|
718
|
-
let w2 = 0;
|
|
719
|
-
for (let i=0; i<s.length; ++i)
|
|
720
|
-
w2 += x.measureText(s[i]).width;
|
|
721
|
-
for (let j=2; j--;)
|
|
722
|
-
for (let i=0, X=41-w2/2; i<s.length; ++i)
|
|
723
|
-
{
|
|
724
|
-
x.fillStyle = color(i,2);
|
|
725
|
-
const w = x.measureText(s[i]).width;
|
|
726
|
-
x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
|
|
727
|
-
X += w;
|
|
728
|
-
}
|
|
729
708
|
x.restore();
|
|
730
709
|
}
|
|
731
710
|
/**
|
|
@@ -782,25 +761,25 @@ function debugProtectConstant(o){ return o; }
|
|
|
782
761
|
const PI = Math.PI;
|
|
783
762
|
|
|
784
763
|
/** Returns absolute value of value passed in
|
|
785
|
-
* @param {number}
|
|
764
|
+
* @param {number} x
|
|
786
765
|
* @return {number}
|
|
787
766
|
* @memberof Math */
|
|
788
767
|
const abs = Math.abs;
|
|
789
768
|
|
|
790
769
|
/** Returns floored value of value passed in
|
|
791
|
-
* @param {number}
|
|
770
|
+
* @param {number} x
|
|
792
771
|
* @return {number}
|
|
793
772
|
* @memberof Math */
|
|
794
773
|
const floor = Math.floor;
|
|
795
774
|
|
|
796
775
|
/** Returns ceiled value of value passed in
|
|
797
|
-
* @param {number}
|
|
776
|
+
* @param {number} x
|
|
798
777
|
* @return {number}
|
|
799
778
|
* @memberof Math */
|
|
800
779
|
const ceil = Math.ceil;
|
|
801
780
|
|
|
802
781
|
/** Returns rounded value passed in
|
|
803
|
-
* @param {number}
|
|
782
|
+
* @param {number} x
|
|
804
783
|
* @return {number}
|
|
805
784
|
* @memberof Math */
|
|
806
785
|
const round = Math.round;
|
|
@@ -818,7 +797,7 @@ const min = Math.min;
|
|
|
818
797
|
const max = Math.max;
|
|
819
798
|
|
|
820
799
|
/** Returns the sign of value passed in
|
|
821
|
-
* @param {number}
|
|
800
|
+
* @param {number} x
|
|
822
801
|
* @return {number}
|
|
823
802
|
* @memberof Math */
|
|
824
803
|
const sign = Math.sign;
|
|
@@ -830,25 +809,25 @@ const sign = Math.sign;
|
|
|
830
809
|
const hypot = Math.hypot;
|
|
831
810
|
|
|
832
811
|
/** Returns log2 of value passed in
|
|
833
|
-
* @param {number}
|
|
812
|
+
* @param {number} x
|
|
834
813
|
* @return {number}
|
|
835
814
|
* @memberof Math */
|
|
836
815
|
const log2 = Math.log2;
|
|
837
816
|
|
|
838
817
|
/** Returns sin of value passed in
|
|
839
|
-
* @param {number}
|
|
818
|
+
* @param {number} x
|
|
840
819
|
* @return {number}
|
|
841
820
|
* @memberof Math */
|
|
842
821
|
const sin = Math.sin;
|
|
843
822
|
|
|
844
823
|
/** Returns cos of value passed in
|
|
845
|
-
* @param {number}
|
|
824
|
+
* @param {number} x
|
|
846
825
|
* @return {number}
|
|
847
826
|
* @memberof Math */
|
|
848
827
|
const cos = Math.cos;
|
|
849
828
|
|
|
850
829
|
/** Returns tan of value passed in
|
|
851
|
-
* @param {number}
|
|
830
|
+
* @param {number} x
|
|
852
831
|
* @return {number}
|
|
853
832
|
* @memberof Math */
|
|
854
833
|
const tan = Math.tan;
|
|
@@ -1883,6 +1862,90 @@ const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
|
1883
1862
|
* @namespace Utilities
|
|
1884
1863
|
*/
|
|
1885
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
|
+
|
|
1886
1949
|
/** Formats seconds to mm:ss style for display purposes
|
|
1887
1950
|
* @param {number} t - time in seconds
|
|
1888
1951
|
* @return {string}
|
|
@@ -1969,86 +2032,29 @@ function shareURL(title, url, callback)
|
|
|
1969
2032
|
|
|
1970
2033
|
///////////////////////////////////////////////////////////////////////////////
|
|
1971
2034
|
|
|
1972
|
-
/**
|
|
1973
|
-
*
|
|
1974
|
-
*
|
|
1975
|
-
*
|
|
1976
|
-
*
|
|
1977
|
-
|
|
1978
|
-
*
|
|
1979
|
-
* let b = new Timer(1); // creates a timer with 1 second left
|
|
1980
|
-
* b.unset(); // unset the timer
|
|
1981
|
-
*/
|
|
1982
|
-
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)
|
|
1983
2041
|
{
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
{
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
const globalTime = this.getGlobalTime();
|
|
1992
|
-
this.time = timeLeft === undefined ? undefined : globalTime + timeLeft;
|
|
1993
|
-
this.setTime = timeLeft;
|
|
1994
|
-
}
|
|
1995
|
-
|
|
1996
|
-
/** Set the timer with seconds passed in
|
|
1997
|
-
* @param {number} [timeLeft] - How much time left before the timer is elapsed in seconds */
|
|
1998
|
-
set(timeLeft=0)
|
|
1999
|
-
{
|
|
2000
|
-
ASSERT(isNumber(timeLeft), 'Timer is invalid.', timeLeft);
|
|
2001
|
-
const globalTime = this.getGlobalTime();
|
|
2002
|
-
this.time = globalTime + timeLeft;
|
|
2003
|
-
this.setTime = timeLeft;
|
|
2004
|
-
}
|
|
2005
|
-
|
|
2006
|
-
/** Set if the timer should keep running even when the game is paused
|
|
2007
|
-
* @param {boolean} [useRealTime] */
|
|
2008
|
-
setUseRealTime(useRealTime=true)
|
|
2009
|
-
{
|
|
2010
|
-
ASSERT(!this.isSet(), 'Cannot change global time setting while timer is set.');
|
|
2011
|
-
this.useRealTime = useRealTime;
|
|
2012
|
-
}
|
|
2013
|
-
|
|
2014
|
-
/** Unset the timer */
|
|
2015
|
-
unset() { this.time = undefined; }
|
|
2016
|
-
|
|
2017
|
-
/** Returns true if set
|
|
2018
|
-
* @return {boolean} */
|
|
2019
|
-
isSet() { return this.time !== undefined; }
|
|
2020
|
-
|
|
2021
|
-
/** Returns true if set and has not elapsed
|
|
2022
|
-
* @return {boolean} */
|
|
2023
|
-
active() { return this.getGlobalTime() < this.time; }
|
|
2024
|
-
|
|
2025
|
-
/** Returns true if set and elapsed
|
|
2026
|
-
* @return {boolean} */
|
|
2027
|
-
elapsed() { return this.getGlobalTime() >= this.time; }
|
|
2028
|
-
|
|
2029
|
-
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
2030
|
-
* @return {number} */
|
|
2031
|
-
get() { return this.isSet()? this.getGlobalTime() - this.time : 0; }
|
|
2032
|
-
|
|
2033
|
-
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
2034
|
-
* @return {number} */
|
|
2035
|
-
getPercent() { return this.isSet()? 1-percent(this.time - this.getGlobalTime(), 0, this.setTime) : 0; }
|
|
2036
|
-
|
|
2037
|
-
/** Get the time this timer was set to, returns 0 if not set
|
|
2038
|
-
* @return {number} */
|
|
2039
|
-
getSetTime() { return this.isSet() ? this.setTime : 0; }
|
|
2040
|
-
|
|
2041
|
-
/** Get the current global time this timer is based on
|
|
2042
|
-
* @return {number} */
|
|
2043
|
-
getGlobalTime() { return this.useRealTime ? timeReal : time; }
|
|
2044
|
-
|
|
2045
|
-
/** Returns this timer expressed as a string
|
|
2046
|
-
* @return {string} */
|
|
2047
|
-
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
|
+
}
|
|
2048
2049
|
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
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);
|
|
2052
2058
|
}
|
|
2053
2059
|
/**
|
|
2054
2060
|
* LittleJS Engine Settings
|
|
@@ -2709,17 +2715,19 @@ class EngineObject
|
|
|
2709
2715
|
this.additiveColor = undefined;
|
|
2710
2716
|
/** @property {boolean} - Should it flip along y axis when rendered */
|
|
2711
2717
|
this.mirror = false;
|
|
2718
|
+
/** @property {boolean} - Has object been destroyed? */
|
|
2719
|
+
this.destroyed = false;
|
|
2712
2720
|
|
|
2713
2721
|
// physical properties
|
|
2714
|
-
/** @property {number}
|
|
2722
|
+
/** @property {number} - How heavy the object is, static if 0 */
|
|
2715
2723
|
this.mass = objectDefaultMass;
|
|
2716
|
-
/** @property {number}
|
|
2724
|
+
/** @property {number} - How much to slow down velocity each frame (0-1) */
|
|
2717
2725
|
this.damping = objectDefaultDamping;
|
|
2718
|
-
/** @property {number}
|
|
2726
|
+
/** @property {number} - How much to slow down rotation each frame (0-1) */
|
|
2719
2727
|
this.angleDamping = objectDefaultAngleDamping;
|
|
2720
|
-
/** @property {number}
|
|
2728
|
+
/** @property {number} - How bouncy the object is when colliding (0-1) */
|
|
2721
2729
|
this.restitution = objectDefaultRestitution;
|
|
2722
|
-
/** @property {number}
|
|
2730
|
+
/** @property {number} - How much friction to apply when sliding (0-1) */
|
|
2723
2731
|
this.friction = objectDefaultFriction;
|
|
2724
2732
|
/** @property {number} - How much to scale gravity by for this object */
|
|
2725
2733
|
this.gravityScale = 1;
|
|
@@ -2920,7 +2928,7 @@ class EngineObject
|
|
|
2920
2928
|
if (this.collideTiles)
|
|
2921
2929
|
{
|
|
2922
2930
|
// check collision against tiles
|
|
2923
|
-
const hitLayer = tileCollisionTest(this.pos, this.size, this)
|
|
2931
|
+
const hitLayer = tileCollisionTest(this.pos, this.size, this);
|
|
2924
2932
|
if (hitLayer)
|
|
2925
2933
|
{
|
|
2926
2934
|
// if already was stuck in collision, don't do anything
|
|
@@ -2928,10 +2936,10 @@ class EngineObject
|
|
|
2928
2936
|
if (!tileCollisionTest(oldPos, this.size, this))
|
|
2929
2937
|
{
|
|
2930
2938
|
// test which side we bounced off (or both if a corner)
|
|
2931
|
-
const
|
|
2932
|
-
const
|
|
2933
|
-
|
|
2934
|
-
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)
|
|
2935
2943
|
{
|
|
2936
2944
|
// try to move up a tiny bit
|
|
2937
2945
|
const epsilon = 1e-3;
|
|
@@ -2947,16 +2955,12 @@ class EngineObject
|
|
|
2947
2955
|
return;
|
|
2948
2956
|
}
|
|
2949
2957
|
|
|
2950
|
-
// move to previous position and bounce
|
|
2958
|
+
// move to previous X position and bounce
|
|
2951
2959
|
this.pos.x = oldPos.x;
|
|
2952
|
-
this.velocity.x *= -
|
|
2960
|
+
this.velocity.x *= -restitution;
|
|
2953
2961
|
}
|
|
2954
|
-
if (
|
|
2962
|
+
if (isBlockedY || !isBlockedX)
|
|
2955
2963
|
{
|
|
2956
|
-
// bounce velocity
|
|
2957
|
-
const restitution = max(this.restitution, hitLayer.restitution);
|
|
2958
|
-
this.velocity.y *= -restitution;
|
|
2959
|
-
|
|
2960
2964
|
if (wasFalling)
|
|
2961
2965
|
{
|
|
2962
2966
|
// adjust position to slightly away from nearest tile
|
|
@@ -2972,10 +2976,12 @@ class EngineObject
|
|
|
2972
2976
|
}
|
|
2973
2977
|
else
|
|
2974
2978
|
{
|
|
2975
|
-
// move to previous position
|
|
2979
|
+
// move to previous Y position
|
|
2976
2980
|
this.pos.y = oldPos.y;
|
|
2977
2981
|
this.groundObject = undefined;
|
|
2978
2982
|
}
|
|
2983
|
+
// bounce velocity
|
|
2984
|
+
this.velocity.y *= -restitution;
|
|
2979
2985
|
}
|
|
2980
2986
|
debugPhysics && debugRect(this.pos, this.size, '#f00');
|
|
2981
2987
|
}
|
|
@@ -2993,19 +2999,19 @@ class EngineObject
|
|
|
2993
2999
|
drawTile(this.pos, this.drawSize || this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
|
|
2994
3000
|
}
|
|
2995
3001
|
|
|
2996
|
-
/** Destroy this object, destroy its children, detach its parent, and mark it for removal
|
|
2997
|
-
|
|
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)
|
|
2998
3005
|
{
|
|
2999
|
-
if (this.destroyed)
|
|
3000
|
-
return;
|
|
3006
|
+
if (this.destroyed) return;
|
|
3001
3007
|
|
|
3002
3008
|
// disconnect from parent and destroy children
|
|
3003
|
-
this.destroyed =
|
|
3009
|
+
this.destroyed = true;
|
|
3004
3010
|
this.parent?.removeChild(this);
|
|
3005
3011
|
for (const child of this.children)
|
|
3006
3012
|
{
|
|
3007
3013
|
child.parent = undefined;
|
|
3008
|
-
child.destroy();
|
|
3014
|
+
child.destroy(immediate);
|
|
3009
3015
|
}
|
|
3010
3016
|
}
|
|
3011
3017
|
|
|
@@ -3104,7 +3110,7 @@ class EngineObject
|
|
|
3104
3110
|
}
|
|
3105
3111
|
|
|
3106
3112
|
/** Check if overlapping another engine object
|
|
3107
|
-
* Collisions are
|
|
3113
|
+
* Collisions are resolved to prevent overlaps
|
|
3108
3114
|
* @param {EngineObject} object
|
|
3109
3115
|
* @return {boolean} */
|
|
3110
3116
|
isOverlappingObject(object)
|
|
@@ -3356,7 +3362,7 @@ class TileInfo
|
|
|
3356
3362
|
}
|
|
3357
3363
|
|
|
3358
3364
|
/**
|
|
3359
|
-
* Returns a tile info for an index using this tile as
|
|
3365
|
+
* Returns a tile info for an index using this tile as reference
|
|
3360
3366
|
* @param {Vector2|number} [index=0]
|
|
3361
3367
|
* @return {TileInfo}
|
|
3362
3368
|
*/
|
|
@@ -3424,7 +3430,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=WHITE,
|
|
|
3424
3430
|
ASSERT(!additiveColor || isColor(additiveColor), 'additiveColor must be a color');
|
|
3425
3431
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3426
3432
|
|
|
3427
|
-
const textureInfo = tileInfo
|
|
3433
|
+
const textureInfo = tileInfo?.textureInfo;
|
|
3428
3434
|
const bleed = tileInfo?.bleed ?? 0;
|
|
3429
3435
|
if (useWebGL && glEnable)
|
|
3430
3436
|
{
|
|
@@ -3730,9 +3736,12 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
|
|
|
3730
3736
|
ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
|
|
3731
3737
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
3732
3738
|
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
3733
|
-
ASSERT(lineWidth >= 0
|
|
3739
|
+
ASSERT(lineWidth >= 0, 'lineWidth must be a positive value or 0');
|
|
3734
3740
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3735
3741
|
|
|
3742
|
+
// clamp line width to prevent artifacts
|
|
3743
|
+
lineWidth = clamp(lineWidth, 0, Math.min(size.x, size.y));
|
|
3744
|
+
|
|
3736
3745
|
if (useWebGL && glEnable)
|
|
3737
3746
|
{
|
|
3738
3747
|
// draw as a regular polygon
|
|
@@ -4183,7 +4192,7 @@ let engineFontImage;
|
|
|
4183
4192
|
class FontImage
|
|
4184
4193
|
{
|
|
4185
4194
|
/** Create an image font
|
|
4186
|
-
* @param {TileInfo} tileInfo - Tile info of first
|
|
4195
|
+
* @param {TileInfo} tileInfo - Tile info of first character in font
|
|
4187
4196
|
*/
|
|
4188
4197
|
constructor(tileInfo)
|
|
4189
4198
|
{
|
|
@@ -4698,9 +4707,9 @@ function inputInit()
|
|
|
4698
4707
|
function touchInputInit()
|
|
4699
4708
|
{
|
|
4700
4709
|
// add non passive touch event listeners
|
|
4701
|
-
document.addEventListener('touchstart', (e)
|
|
4702
|
-
document.addEventListener('touchmove', (e)
|
|
4703
|
-
document.addEventListener('touchend', (e)
|
|
4710
|
+
document.addEventListener('touchstart', (e)=> handleTouch(e), { passive: false });
|
|
4711
|
+
document.addEventListener('touchmove', (e)=> handleTouch(e), { passive: false });
|
|
4712
|
+
document.addEventListener('touchend', (e)=> handleTouch(e), { passive: false });
|
|
4704
4713
|
|
|
4705
4714
|
// handle all touch events the same way
|
|
4706
4715
|
let wasTouching;
|
|
@@ -4906,7 +4915,7 @@ function inputUpdate()
|
|
|
4906
4915
|
// poll gamepads
|
|
4907
4916
|
const maxGamepads = 8;
|
|
4908
4917
|
const gamepads = navigator.getGamepads();
|
|
4909
|
-
const gamepadCount = min(maxGamepads, gamepads.length)
|
|
4918
|
+
const gamepadCount = min(maxGamepads, gamepads.length);
|
|
4910
4919
|
for (let i=0; i<gamepadCount; ++i)
|
|
4911
4920
|
{
|
|
4912
4921
|
// get or create gamepad data
|
|
@@ -5059,7 +5068,7 @@ function inputRender()
|
|
|
5059
5068
|
}
|
|
5060
5069
|
}
|
|
5061
5070
|
|
|
5062
|
-
// center position for right
|
|
5071
|
+
// center position for right touch pad face buttons
|
|
5063
5072
|
function touchGamepadButtonCenter()
|
|
5064
5073
|
{
|
|
5065
5074
|
const center = mainCanvasSize.subtract(vec2(touchGamepadSize));
|
|
@@ -5111,29 +5120,46 @@ function audioInit()
|
|
|
5111
5120
|
///////////////////////////////////////////////////////////////////////////////
|
|
5112
5121
|
|
|
5113
5122
|
/**
|
|
5114
|
-
* Sound Object - Stores a sound for later
|
|
5123
|
+
* Sound Object - Stores a sound for later
|
|
5124
|
+
* - this can be used to load and play wave, mp3, and ogg files
|
|
5125
|
+
* - it can also create sounds using the ZzFX sound generator
|
|
5126
|
+
* - can attenuate and apply stereo panning to sounds
|
|
5127
|
+
* - sound instance control with pause/resume capability
|
|
5115
5128
|
*
|
|
5116
5129
|
* <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
|
|
5117
5130
|
* @memberof Audio
|
|
5118
5131
|
* @example
|
|
5119
|
-
* //
|
|
5132
|
+
* // load an audio asset file
|
|
5133
|
+
* const sound_example = new Sound('sound.mp3');
|
|
5134
|
+
*
|
|
5135
|
+
* // create a zzfx sound
|
|
5120
5136
|
* const sound_example = new Sound([.5,.5]);
|
|
5121
5137
|
*
|
|
5122
|
-
* // play
|
|
5138
|
+
* // play a sound
|
|
5123
5139
|
* sound_example.play();
|
|
5124
5140
|
*/
|
|
5125
5141
|
class Sound
|
|
5126
5142
|
{
|
|
5127
|
-
/**
|
|
5128
|
-
*
|
|
5143
|
+
/**
|
|
5144
|
+
* @callback SoundLoadCallback - Function called when sound is loaded
|
|
5145
|
+
* @param {Sound} sound
|
|
5146
|
+
* @memberof Audio
|
|
5147
|
+
*/
|
|
5148
|
+
|
|
5149
|
+
/** Create a sound object and cache the audio for later use
|
|
5150
|
+
* @param {string|Array} [asset] - Filename of audio file or zzfx array
|
|
5151
|
+
* @param {number} [randomness] - How much to randomize frequency each time sound plays, for zzfx sounds the zzfx default is used if undefined
|
|
5129
5152
|
* @param {number} [range=soundDefaultRange] - World space max range of sound
|
|
5130
5153
|
* @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
|
|
5154
|
+
* @param {SoundLoadCallback} [onloadCallback] - callback function to call when sound is loaded
|
|
5131
5155
|
*/
|
|
5132
|
-
constructor(
|
|
5156
|
+
constructor(asset, randomness, range=soundDefaultRange, taper=soundDefaultTaper, onloadCallback)
|
|
5133
5157
|
{
|
|
5134
5158
|
if (!soundEnable || headlessMode) return;
|
|
5135
5159
|
|
|
5136
|
-
ASSERT(!
|
|
5160
|
+
ASSERT(!asset || isArray(asset) || isString(asset), 'asset must be a file name or zzfx array');
|
|
5161
|
+
ASSERT(randomness === undefined || isNumber(randomness), 'randomness must be a number');
|
|
5162
|
+
ASSERT(randomness === undefined || randomness >= 0 && randomness <=1, 'randomness must be between 0 and 1');
|
|
5137
5163
|
ASSERT(isNumber(range), 'range must be a number');
|
|
5138
5164
|
ASSERT(isNumber(taper), 'taper must be a number');
|
|
5139
5165
|
|
|
@@ -5142,23 +5168,35 @@ class Sound
|
|
|
5142
5168
|
/** @property {number} - At what percentage of range should it start tapering */
|
|
5143
5169
|
this.taper = taper;
|
|
5144
5170
|
/** @property {number} - How much to randomize frequency each time sound plays */
|
|
5145
|
-
this.randomness = 0;
|
|
5171
|
+
this.randomness = randomness ?? 0;
|
|
5146
5172
|
/** @property {number} - Sample rate for this sound */
|
|
5147
5173
|
this.sampleRate = audioDefaultSampleRate;
|
|
5148
5174
|
/** @property {number} - Percentage of this sound currently loaded */
|
|
5149
5175
|
this.loadedPercent = 0;
|
|
5176
|
+
/** @property {SoundLoadCallback} - function to call when sound is loaded */
|
|
5177
|
+
this.onloadCallback = onloadCallback;
|
|
5150
5178
|
|
|
5151
|
-
|
|
5152
|
-
if (zzfxSound)
|
|
5179
|
+
if (Array.isArray(asset))
|
|
5153
5180
|
{
|
|
5181
|
+
// generate zzfx sound
|
|
5182
|
+
const zzfxSound = asset;
|
|
5183
|
+
|
|
5154
5184
|
// remove randomness so it can be applied on playback
|
|
5155
|
-
const
|
|
5185
|
+
const defaultRandomness = randomness ?? .05;
|
|
5186
|
+
const randomnessIndex = 1;
|
|
5156
5187
|
this.randomness = zzfxSound[randomnessIndex] ?? defaultRandomness;
|
|
5157
5188
|
zzfxSound[randomnessIndex] = 0;
|
|
5158
5189
|
|
|
5159
5190
|
// generate the zzfx samples
|
|
5160
5191
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
5161
5192
|
this.loadedPercent = 1;
|
|
5193
|
+
onloadCallback?.(this);
|
|
5194
|
+
}
|
|
5195
|
+
else if (typeof asset === 'string')
|
|
5196
|
+
{
|
|
5197
|
+
// load the audio file
|
|
5198
|
+
const filename = asset;
|
|
5199
|
+
this.loadSound(filename);
|
|
5162
5200
|
}
|
|
5163
5201
|
}
|
|
5164
5202
|
|
|
@@ -5210,7 +5248,7 @@ class Sound
|
|
|
5210
5248
|
* @param {number} [volume] - Volume to play the music at
|
|
5211
5249
|
* @param {boolean} [loop] - Should the music loop?
|
|
5212
5250
|
* @param {boolean} [paused] - Should the music start paused
|
|
5213
|
-
* @return {SoundInstance} - The
|
|
5251
|
+
* @return {SoundInstance} - The sound instance
|
|
5214
5252
|
*/
|
|
5215
5253
|
playMusic(volume=1, loop=true, paused=false)
|
|
5216
5254
|
{ return this.play(undefined, volume, 1, 0, loop, paused); }
|
|
@@ -5220,70 +5258,27 @@ class Sound
|
|
|
5220
5258
|
* @param {number} [semitoneOffset=0] - How many semitones to offset pitch
|
|
5221
5259
|
* @param {Vector2} [pos] - World space position to play the sound if any
|
|
5222
5260
|
* @param {number} [volume=1] - How much to scale volume by
|
|
5223
|
-
* @return {SoundInstance} - The
|
|
5261
|
+
* @return {SoundInstance} - The sound instance
|
|
5224
5262
|
*/
|
|
5225
5263
|
playNote(semitoneOffset=0, pos, volume)
|
|
5226
|
-
{
|
|
5227
|
-
ASSERT(isNumber(semitoneOffset), 'semitoneOffset must be a number');
|
|
5228
|
-
const pitch = getNoteFrequency(semitoneOffset, 1);
|
|
5229
|
-
return this.play(pos, volume, pitch, 0);
|
|
5230
|
-
}
|
|
5231
|
-
|
|
5232
|
-
/** Get how long this sound is in seconds
|
|
5233
|
-
* @return {number} - How long the sound is in seconds (undefined if loading)
|
|
5234
|
-
*/
|
|
5235
|
-
getDuration()
|
|
5236
|
-
{ return this.sampleChannels?.[0].length / this.sampleRate || 0; }
|
|
5237
|
-
|
|
5238
|
-
/** Check if sound is loaded, for sounds fetched from a url
|
|
5239
|
-
* @return {boolean} - True if sound is loaded and ready to play
|
|
5240
|
-
*/
|
|
5241
|
-
isLoaded() { return this.loadedPercent === 1; }
|
|
5242
|
-
}
|
|
5243
|
-
|
|
5244
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
5245
|
-
|
|
5246
|
-
/**
|
|
5247
|
-
* Sound Wave Object - Loads and stores an audio file for later use
|
|
5248
|
-
* - this can be used to load and play wave, mp3, and ogg files
|
|
5249
|
-
* @extends Sound
|
|
5250
|
-
* @memberof Audio
|
|
5251
|
-
* @example
|
|
5252
|
-
* // load an audio asset file
|
|
5253
|
-
* const sound_example = new SoundWave('sound.mp3');
|
|
5254
|
-
*
|
|
5255
|
-
* // play the sound
|
|
5256
|
-
* sound_example.play();
|
|
5257
|
-
*/
|
|
5258
|
-
class SoundWave extends Sound
|
|
5259
|
-
{
|
|
5260
|
-
/**
|
|
5261
|
-
* @callback SoundLoadCallback - Function called when sound is loaded
|
|
5262
|
-
* @param {SoundWave} sound
|
|
5263
|
-
* @memberof Audio
|
|
5264
|
-
*/
|
|
5265
|
-
|
|
5266
|
-
/** Create a sound object and cache the wave file for later use
|
|
5267
|
-
* @param {string} filename - Filename of audio file to load
|
|
5268
|
-
* @param {number} [randomness] - How much to randomize frequency each time sound plays
|
|
5269
|
-
* @param {number} [range=soundDefaultRange] - World space max range of sound
|
|
5270
|
-
* @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
|
|
5271
|
-
* @param {SoundLoadCallback} [onloadCallback] - callback function to call when sound is loaded
|
|
5272
|
-
*/
|
|
5273
|
-
constructor(filename, randomness=0, range, taper, onloadCallback)
|
|
5274
|
-
{
|
|
5275
|
-
super(undefined, range, taper);
|
|
5276
|
-
if (!soundEnable || headlessMode) return;
|
|
5277
|
-
ASSERT(!filename || isString(filename), 'filename must be a string');
|
|
5278
|
-
ASSERT(isNumber(randomness), 'randomness must be a number');
|
|
5279
|
-
|
|
5280
|
-
/** @property {SoundLoadCallback} - callback function to call when sound is loaded */
|
|
5281
|
-
this.onloadCallback = onloadCallback;
|
|
5282
|
-
this.randomness = randomness;
|
|
5283
|
-
filename && this.loadSound(filename);
|
|
5264
|
+
{
|
|
5265
|
+
ASSERT(isNumber(semitoneOffset), 'semitoneOffset must be a number');
|
|
5266
|
+
const pitch = getNoteFrequency(semitoneOffset, 1);
|
|
5267
|
+
return this.play(pos, volume, pitch, 0);
|
|
5284
5268
|
}
|
|
5285
5269
|
|
|
5286
|
-
/**
|
|
5270
|
+
/** Get how long this sound is in seconds
|
|
5271
|
+
* @return {number} - How long the sound is in seconds (undefined if loading)
|
|
5272
|
+
*/
|
|
5273
|
+
getDuration()
|
|
5274
|
+
{ return this.sampleChannels?.[0]?.length / this.sampleRate || 0; }
|
|
5275
|
+
|
|
5276
|
+
/** Check if sound is loaded, for sounds fetched from a url
|
|
5277
|
+
* @return {boolean} - True if sound is loaded and ready to play
|
|
5278
|
+
*/
|
|
5279
|
+
isLoaded() { return this.loadedPercent === 1; }
|
|
5280
|
+
|
|
5281
|
+
/** Loads a sound from a URL and decodes it into sample data.
|
|
5287
5282
|
* @param {string} filename
|
|
5288
5283
|
* @return {Promise<void>} */
|
|
5289
5284
|
async loadSound(filename)
|
|
@@ -5774,50 +5769,62 @@ const tileCollisionLayers = [];
|
|
|
5774
5769
|
|
|
5775
5770
|
/** Get tile collision data for a given cell in the grid
|
|
5776
5771
|
* @param {Vector2} pos
|
|
5772
|
+
* @param {boolean} [solidOnly] - Only check solid layers?
|
|
5777
5773
|
* @return {number}
|
|
5778
5774
|
* @memberof TileLayers */
|
|
5779
|
-
function tileCollisionGetData(pos)
|
|
5775
|
+
function tileCollisionGetData(pos, solidOnly=true)
|
|
5780
5776
|
{
|
|
5781
5777
|
// check all tile collision layers
|
|
5782
5778
|
for (const layer of tileCollisionLayers)
|
|
5779
|
+
if (!solidOnly || layer.isSolid)
|
|
5783
5780
|
if (pos.arrayCheck(layer.size))
|
|
5784
|
-
|
|
5781
|
+
{
|
|
5782
|
+
const data = layer.getCollisionData(pos);
|
|
5783
|
+
if (data) return data;
|
|
5784
|
+
}
|
|
5785
5785
|
return 0;
|
|
5786
5786
|
}
|
|
5787
5787
|
|
|
5788
5788
|
/** Check if a tile layer collides with another object
|
|
5789
|
-
* @param {Vector2}
|
|
5790
|
-
* @param {Vector2}
|
|
5791
|
-
* @param {EngineObject} [
|
|
5792
|
-
* @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?
|
|
5793
5793
|
* @return {TileCollisionLayer}
|
|
5794
5794
|
* @memberof TileLayers */
|
|
5795
|
-
function tileCollisionTest(pos, size=vec2(),
|
|
5795
|
+
function tileCollisionTest(pos, size=vec2(), callbackObject, solidOnly=true)
|
|
5796
5796
|
{
|
|
5797
5797
|
for (const layer of tileCollisionLayers)
|
|
5798
5798
|
{
|
|
5799
5799
|
if (!solidOnly || layer.isSolid)
|
|
5800
|
-
if (layer.collisionTest(pos, size,
|
|
5800
|
+
if (layer.collisionTest(pos, size, callbackObject))
|
|
5801
5801
|
return layer;
|
|
5802
5802
|
}
|
|
5803
5803
|
}
|
|
5804
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
|
+
|
|
5805
5812
|
/** Return the exact position of the boundary of first tile hit, undefined if nothing was hit.
|
|
5806
|
-
* The point will be inside the colliding tile if it hits
|
|
5807
|
-
* @param {Vector2}
|
|
5808
|
-
* @param {Vector2}
|
|
5809
|
-
* @param {EngineObject} [
|
|
5810
|
-
* @param {Vector2}
|
|
5811
|
-
* @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?
|
|
5812
5819
|
* @return {Vector2|undefined} - position of the center of the tile hit or undefined if no hit
|
|
5813
5820
|
* @memberof TileLayers */
|
|
5814
|
-
function tileCollisionRaycast(posStart, posEnd,
|
|
5821
|
+
function tileCollisionRaycast(posStart, posEnd, callbackObject, normal, solidOnly=true)
|
|
5815
5822
|
{
|
|
5816
5823
|
for (const layer of tileCollisionLayers)
|
|
5817
5824
|
{
|
|
5818
5825
|
if (!solidOnly || layer.isSolid)
|
|
5819
5826
|
{
|
|
5820
|
-
const hitPos = layer.collisionRaycast(posStart, posEnd,
|
|
5827
|
+
const hitPos = layer.collisionRaycast(posStart, posEnd, callbackObject, normal)
|
|
5821
5828
|
if (hitPos) return hitPos;
|
|
5822
5829
|
}
|
|
5823
5830
|
}
|
|
@@ -5938,17 +5945,17 @@ class TileLayerData
|
|
|
5938
5945
|
class CanvasLayer extends EngineObject
|
|
5939
5946
|
{
|
|
5940
5947
|
/** Create a canvas layer object
|
|
5941
|
-
* @param {Vector2} [
|
|
5948
|
+
* @param {Vector2} [pos] - World space position of the layer
|
|
5942
5949
|
* @param {Vector2} [size] - World space size of the layer
|
|
5943
5950
|
* @param {number} [angle] - Angle the layer is rotated by
|
|
5944
5951
|
* @param {number} [renderOrder] - Objects sorted by renderOrder
|
|
5945
5952
|
* @param {Vector2} [canvasSize] - Default size of canvas, can be changed later
|
|
5946
5953
|
* @param {boolean} [useWebGL] - Should this layer use WebGL for rendering
|
|
5947
5954
|
*/
|
|
5948
|
-
constructor(
|
|
5955
|
+
constructor(pos, size, angle=0, renderOrder=0, canvasSize=vec2(512), useWebGL=true)
|
|
5949
5956
|
{
|
|
5950
5957
|
ASSERT(isVector2(canvasSize), 'canvasSize must be a Vector2');
|
|
5951
|
-
super(
|
|
5958
|
+
super(pos, size, undefined, angle, WHITE, renderOrder);
|
|
5952
5959
|
|
|
5953
5960
|
/** @property {HTMLCanvasElement} - The canvas used by this layer */
|
|
5954
5961
|
this.canvas = headlessMode ? undefined : new OffscreenCanvas(canvasSize.x, canvasSize.y);
|
|
@@ -6052,16 +6059,16 @@ class CanvasLayer extends EngineObject
|
|
|
6052
6059
|
class TileLayer extends CanvasLayer
|
|
6053
6060
|
{
|
|
6054
6061
|
/** Create a tile layer object
|
|
6055
|
-
* @param {Vector2}
|
|
6062
|
+
* @param {Vector2} pos - World space position
|
|
6056
6063
|
* @param {Vector2} size - World space size
|
|
6057
6064
|
* @param {TileInfo} [tileInfo] - Default tile info for layer (used for size and texture)
|
|
6058
6065
|
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
6066
|
+
* @param {boolean} [useWebGL] - Should this layer use WebGL for rendering
|
|
6059
6067
|
*/
|
|
6060
|
-
constructor(
|
|
6068
|
+
constructor(pos, size, tileInfo=tile(), renderOrder=0, useWebGL=true)
|
|
6061
6069
|
{
|
|
6062
6070
|
const canvasSize = tileInfo ? size.multiply(tileInfo.size) : size;
|
|
6063
|
-
|
|
6064
|
-
super(position, size, 0, renderOrder, canvasSize, useWebGL);
|
|
6071
|
+
super(pos, size, 0, renderOrder, canvasSize, useWebGL);
|
|
6065
6072
|
|
|
6066
6073
|
/** @property {TileInfo} - Default tile info for layer */
|
|
6067
6074
|
this.tileInfo = undefined;
|
|
@@ -6073,15 +6080,15 @@ class TileLayer extends CanvasLayer
|
|
|
6073
6080
|
if (headlessMode)
|
|
6074
6081
|
{
|
|
6075
6082
|
// disable rendering in headless mode
|
|
6076
|
-
this.render = ()
|
|
6077
|
-
this.redraw = ()
|
|
6078
|
-
this.redrawStart = ()
|
|
6079
|
-
this.redrawEnd = ()
|
|
6080
|
-
this.drawTileData = ()
|
|
6081
|
-
this.redrawTileData = ()
|
|
6082
|
-
this.drawLayerTile = ()
|
|
6083
|
-
this.drawLayerRect = ()
|
|
6084
|
-
this.clearLayerRect = ()
|
|
6083
|
+
this.render = ()=> {};
|
|
6084
|
+
this.redraw = ()=> {};
|
|
6085
|
+
this.redrawStart = ()=> {};
|
|
6086
|
+
this.redrawEnd = ()=> {};
|
|
6087
|
+
this.drawTileData = ()=> {};
|
|
6088
|
+
this.redrawTileData = ()=> {};
|
|
6089
|
+
this.drawLayerTile = ()=> {};
|
|
6090
|
+
this.drawLayerRect = ()=> {};
|
|
6091
|
+
this.clearLayerRect = ()=> {};
|
|
6085
6092
|
return;
|
|
6086
6093
|
}
|
|
6087
6094
|
|
|
@@ -6115,6 +6122,12 @@ class TileLayer extends CanvasLayer
|
|
|
6115
6122
|
isRedraw ? this.drawTileData(layerPos) : this.redrawTileData(layerPos);
|
|
6116
6123
|
}
|
|
6117
6124
|
|
|
6125
|
+
/** Clear data at a given position in the array
|
|
6126
|
+
* @param {Vector2} layerPos - Local position in array
|
|
6127
|
+
* @param {boolean} [redraw] - Force the tile to redraw if true */
|
|
6128
|
+
clearData(layerPos, redraw=false)
|
|
6129
|
+
{ this.setData(layerPos, new TileLayerData, redraw) }
|
|
6130
|
+
|
|
6118
6131
|
/** Get data at a given position in the array
|
|
6119
6132
|
* @param {Vector2} layerPos - Local position in array
|
|
6120
6133
|
* @return {TileLayerData} */
|
|
@@ -6156,7 +6169,7 @@ class TileLayer extends CanvasLayer
|
|
|
6156
6169
|
for (let x = this.size.x; x--;)
|
|
6157
6170
|
for (let y = this.size.y; y--;)
|
|
6158
6171
|
this.drawTileData(vec2(x,y), false);
|
|
6159
|
-
this.
|
|
6172
|
+
this.isUsingWebGL && glFlush();
|
|
6160
6173
|
this.onRedraw();
|
|
6161
6174
|
this.redrawEnd();
|
|
6162
6175
|
}
|
|
@@ -6205,14 +6218,13 @@ class TileLayer extends CanvasLayer
|
|
|
6205
6218
|
if (!this.context) return;
|
|
6206
6219
|
ASSERT(drawContext === this.context);
|
|
6207
6220
|
|
|
6208
|
-
if (glEnable && this.textureInfo.glTexture)
|
|
6209
|
-
glSetRenderTarget();
|
|
6210
|
-
|
|
6211
6221
|
// set stuff back to normal
|
|
6222
|
+
if (this.isUsingWebGL)
|
|
6223
|
+
glSetRenderTarget();
|
|
6212
6224
|
[drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor] = this.savedRenderSettings;
|
|
6213
6225
|
}
|
|
6214
6226
|
|
|
6215
|
-
/** Draw the tile at a given position in the tile
|
|
6227
|
+
/** Draw the tile at a given position in the tile layer
|
|
6216
6228
|
* This can be used to clear out tiles when they are destroyed
|
|
6217
6229
|
* Tiles can also be redrawn if inside a redrawStart/End block
|
|
6218
6230
|
* @param {Vector2} layerPos
|
|
@@ -6236,7 +6248,7 @@ class TileLayer extends CanvasLayer
|
|
|
6236
6248
|
this.drawLayerTile(drawPos, drawSize, tileInfo, d.color, d.direction*PI/2, d.mirror);
|
|
6237
6249
|
}
|
|
6238
6250
|
|
|
6239
|
-
/** Draw the tile at a given position in the tile
|
|
6251
|
+
/** Draw the tile at a given position in the tile layer
|
|
6240
6252
|
* This can be used to clear tiles when they are destroyed
|
|
6241
6253
|
* For better performance use drawTileData inside a redrawStart/End block
|
|
6242
6254
|
* @param {Vector2} layerPos
|
|
@@ -6264,7 +6276,7 @@ class TileLayer extends CanvasLayer
|
|
|
6264
6276
|
angle=0, mirror, additiveColor)
|
|
6265
6277
|
{
|
|
6266
6278
|
const drawPos = pos.add(size.scale(.5));
|
|
6267
|
-
drawTile(drawPos, size, tileInfo, color, angle, mirror, additiveColor);
|
|
6279
|
+
drawTile(drawPos, size, tileInfo, color, angle, mirror, additiveColor, this.isUsingWebGL);
|
|
6268
6280
|
}
|
|
6269
6281
|
|
|
6270
6282
|
/** Clear a rectangle in layer space
|
|
@@ -6304,14 +6316,15 @@ class TileLayer extends CanvasLayer
|
|
|
6304
6316
|
class TileCollisionLayer extends TileLayer
|
|
6305
6317
|
{
|
|
6306
6318
|
/** Create a tile layer object
|
|
6307
|
-
* @param {Vector2}
|
|
6308
|
-
* @param {Vector2} size
|
|
6309
|
-
* @param {TileInfo} [tileInfo]
|
|
6319
|
+
* @param {Vector2} pos - World space position
|
|
6320
|
+
* @param {Vector2} size - World space size
|
|
6321
|
+
* @param {TileInfo} [tileInfo] - Tile info for layer
|
|
6310
6322
|
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
6323
|
+
* @param {boolean} [useWebGL] - Should this layer use WebGL for rendering
|
|
6311
6324
|
*/
|
|
6312
|
-
constructor(
|
|
6325
|
+
constructor(pos, size, tileInfo=tile(), renderOrder=0, useWebGL=true)
|
|
6313
6326
|
{
|
|
6314
|
-
super(
|
|
6327
|
+
super(pos, size.floor(), tileInfo, renderOrder, useWebGL);
|
|
6315
6328
|
|
|
6316
6329
|
/** @property {Array<number>} - The tile collision grid */
|
|
6317
6330
|
this.collisionData = [];
|
|
@@ -6347,41 +6360,50 @@ class TileCollisionLayer extends TileLayer
|
|
|
6347
6360
|
this.collisionData.fill(0);
|
|
6348
6361
|
}
|
|
6349
6362
|
|
|
6350
|
-
/** Set tile collision data for a given cell in the
|
|
6351
|
-
* @param {Vector2}
|
|
6363
|
+
/** Set tile collision data for a given cell in the layer
|
|
6364
|
+
* @param {Vector2} layerPos
|
|
6352
6365
|
* @param {number} [data] */
|
|
6353
|
-
setCollisionData(
|
|
6366
|
+
setCollisionData(layerPos, data=1)
|
|
6354
6367
|
{
|
|
6355
|
-
ASSERT(isVector2(
|
|
6356
|
-
const i = (
|
|
6357
|
-
|
|
6368
|
+
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
6369
|
+
const i = (layerPos.y|0)*this.size.x + layerPos.x|0;
|
|
6370
|
+
layerPos.arrayCheck(this.size) && (this.collisionData[i] = data);
|
|
6358
6371
|
}
|
|
6359
6372
|
|
|
6360
|
-
/**
|
|
6361
|
-
* @param {Vector2}
|
|
6373
|
+
/** Clear tile collision data for a given cell in the layer
|
|
6374
|
+
* @param {Vector2} layerPos */
|
|
6375
|
+
clearCollisionData(layerPos)
|
|
6376
|
+
{ this.setCollisionData(layerPos, 0); }
|
|
6377
|
+
|
|
6378
|
+
/** Get tile collision data for a given cell in the layer
|
|
6379
|
+
* @param {Vector2} layerPos
|
|
6362
6380
|
* @return {number} */
|
|
6363
|
-
getCollisionData(
|
|
6381
|
+
getCollisionData(layerPos)
|
|
6364
6382
|
{
|
|
6365
|
-
ASSERT(isVector2(
|
|
6366
|
-
const i = (
|
|
6367
|
-
return
|
|
6383
|
+
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
6384
|
+
const i = (layerPos.y|0)*this.size.x + layerPos.x|0;
|
|
6385
|
+
return layerPos.arrayCheck(this.size) ? this.collisionData[i] : 0;
|
|
6368
6386
|
}
|
|
6369
6387
|
|
|
6370
6388
|
/** Check if collision with another object should occur
|
|
6371
6389
|
* @param {Vector2} pos
|
|
6372
6390
|
* @param {Vector2} [size=vec2()]
|
|
6373
|
-
* @param {EngineObject} [object
|
|
6391
|
+
* @param {EngineObject|TileCollisionCallback} [callbackObject] - Callback, engine object, or undefined
|
|
6374
6392
|
* @return {boolean} */
|
|
6375
|
-
collisionTest(pos, size=new Vector2,
|
|
6393
|
+
collisionTest(pos, size=new Vector2, callbackObject)
|
|
6376
6394
|
{
|
|
6377
6395
|
ASSERT(isVector2(pos) && isVector2(size), 'pos and size must be Vector2s');
|
|
6378
|
-
ASSERT(!
|
|
6379
|
-
|
|
6380
|
-
//
|
|
6381
|
-
const
|
|
6382
|
-
|
|
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;
|
|
6383
6403
|
|
|
6384
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;
|
|
6385
6407
|
const minX = max(posX - size.x/2|0, 0);
|
|
6386
6408
|
const minY = max(posY - size.y/2|0, 0);
|
|
6387
6409
|
const maxX = min(posX + size.x/2, this.size.x);
|
|
@@ -6392,9 +6414,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
6392
6414
|
{
|
|
6393
6415
|
// check if the object should collide with this tile
|
|
6394
6416
|
const tileData = this.collisionData[y*this.size.x+x];
|
|
6395
|
-
if (tileData)
|
|
6396
|
-
if (!object || object.collideWithTile(tileData,
|
|
6397
|
-
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)))
|
|
6398
6418
|
return true;
|
|
6399
6419
|
}
|
|
6400
6420
|
return false;
|
|
@@ -6402,52 +6422,68 @@ class TileCollisionLayer extends TileLayer
|
|
|
6402
6422
|
|
|
6403
6423
|
/** Return the exact position of the boundary of first tile hit, undefined if nothing was hit.
|
|
6404
6424
|
* The point will be inside the colliding tile if it hits (may have a tiny shift)
|
|
6405
|
-
* @param {Vector2}
|
|
6406
|
-
* @param {Vector2}
|
|
6407
|
-
* @param {EngineObject} [
|
|
6408
|
-
* @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
|
|
6409
6429
|
* @return {Vector2|undefined} */
|
|
6410
|
-
collisionRaycast(posStart, posEnd,
|
|
6430
|
+
collisionRaycast(posStart, posEnd, callbackObject, normal)
|
|
6411
6431
|
{
|
|
6412
6432
|
ASSERT(isVector2(posStart) && isVector2(posEnd), 'positions must be Vector2s');
|
|
6413
|
-
ASSERT(!
|
|
6433
|
+
ASSERT(!callbackObject || typeof callbackObject === 'function' || callbackObject instanceof EngineObject, 'callbackObject must be a funtion or EngineObject');
|
|
6414
6434
|
|
|
6415
|
-
|
|
6416
|
-
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)=>
|
|
6417
6441
|
{
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
const tileData = this.getCollisionData(localPos);
|
|
6421
|
-
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);
|
|
6422
6444
|
}
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
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)
|
|
6426
6450
|
{
|
|
6427
6451
|
const tilePos = hitPos.floor().add(vec2(.5));
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
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);
|
|
6434
6457
|
}
|
|
6458
|
+
return hitPos;
|
|
6435
6459
|
}
|
|
6436
6460
|
}
|
|
6437
6461
|
/**
|
|
6438
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
|
|
6439
6467
|
*/
|
|
6440
6468
|
|
|
6441
6469
|
/**
|
|
6442
|
-
* @callback
|
|
6470
|
+
* @callback ParticleCallback - Function that processes a particle
|
|
6443
6471
|
* @param {Particle} particle
|
|
6444
|
-
* @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
|
|
6445
6481
|
*/
|
|
6446
6482
|
|
|
6447
6483
|
/**
|
|
6448
6484
|
* Particle Emitter - Spawns particles with the given settings
|
|
6449
6485
|
* @extends EngineObject
|
|
6450
|
-
* @memberof
|
|
6486
|
+
* @memberof Particles
|
|
6451
6487
|
* @example
|
|
6452
6488
|
* // create a particle emitter
|
|
6453
6489
|
* let pos = vec2(2,3);
|
|
@@ -6465,7 +6501,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
6465
6501
|
class ParticleEmitter extends EngineObject
|
|
6466
6502
|
{
|
|
6467
6503
|
/** Create a particle system with the given settings
|
|
6468
|
-
* @param {Vector2}
|
|
6504
|
+
* @param {Vector2} pos - World space position of the emitter
|
|
6469
6505
|
* @param {number} [angle] - Angle to emit the particles
|
|
6470
6506
|
* @param {number|Vector2} [emitSize] - World space size of the emitter (float for circle diameter, vec2 for rect)
|
|
6471
6507
|
* @param {number} [emitTime] - How long to stay alive (0 is forever)
|
|
@@ -6495,7 +6531,7 @@ class ParticleEmitter extends EngineObject
|
|
|
6495
6531
|
*/
|
|
6496
6532
|
constructor
|
|
6497
6533
|
(
|
|
6498
|
-
|
|
6534
|
+
pos,
|
|
6499
6535
|
angle,
|
|
6500
6536
|
emitSize = 0,
|
|
6501
6537
|
emitTime = 0,
|
|
@@ -6524,12 +6560,13 @@ class ParticleEmitter extends EngineObject
|
|
|
6524
6560
|
localSpace = false
|
|
6525
6561
|
)
|
|
6526
6562
|
{
|
|
6527
|
-
super(
|
|
6563
|
+
super(pos, vec2(), tileInfo, angle, undefined, renderOrder);
|
|
6528
6564
|
|
|
6529
6565
|
// emitter settings
|
|
6566
|
+
/** @property {boolean} - Should particles be emitted in a circle */
|
|
6567
|
+
this.emitCircle = typeof emitSize === 'number';
|
|
6530
6568
|
/** @property {number|Vector2} - World space size of the emitter (float for circle diameter, vec2 for rect) */
|
|
6531
|
-
this.emitSize = emitSize
|
|
6532
|
-
emitSize.copy() : emitSize;
|
|
6569
|
+
this.emitSize = typeof emitSize === 'number' ? vec2(emitSize) : emitSize.copy();
|
|
6533
6570
|
/** @property {number} - How long to stay alive (0 is forever) */
|
|
6534
6571
|
this.emitTime = emitTime;
|
|
6535
6572
|
/** @property {number} - How many particles per second to spawn, does not emit if 0 */
|
|
@@ -6580,26 +6617,31 @@ class ParticleEmitter extends EngineObject
|
|
|
6580
6617
|
this.localSpace = localSpace;
|
|
6581
6618
|
/** @property {number} - If non zero the particle is drawn as a trail, stretched in the direction of velocity */
|
|
6582
6619
|
this.trailScale = 0;
|
|
6583
|
-
/** @property {
|
|
6584
|
-
this.particleDestroyCallback = undefined;
|
|
6585
|
-
/** @property {ParticleCallbackFunction} - Callback when particle is created */
|
|
6620
|
+
/** @property {ParticleCallback} - Callback when particle is created */
|
|
6586
6621
|
this.particleCreateCallback = undefined;
|
|
6587
|
-
/** @property {
|
|
6588
|
-
this.
|
|
6622
|
+
/** @property {ParticleCallback} - Callback when particle is destroyed */
|
|
6623
|
+
this.particleDestroyCallback = undefined;
|
|
6624
|
+
/** @property {ParticleCollideCallback} - Callback when particle collides */
|
|
6625
|
+
this.particleCollideCallback = undefined;
|
|
6589
6626
|
/** @property {number} - Percentage of velocity to pass to particles (0-1) */
|
|
6590
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 = [];
|
|
6591
6632
|
|
|
6592
6633
|
// track previous position and angle
|
|
6593
6634
|
this.previousAngle = this.angle;
|
|
6594
6635
|
this.previousPos = this.pos.copy();
|
|
6595
6636
|
}
|
|
6596
6637
|
|
|
6597
|
-
/** Emitters do not have physics */
|
|
6598
|
-
updatePhysics() {}
|
|
6599
|
-
|
|
6600
6638
|
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
6601
6639
|
update()
|
|
6602
6640
|
{
|
|
6641
|
+
// physics sanity checks
|
|
6642
|
+
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
6643
|
+
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
6644
|
+
|
|
6603
6645
|
if (this.velocityInheritance)
|
|
6604
6646
|
{
|
|
6605
6647
|
// pass emitter velocity to particles
|
|
@@ -6613,7 +6655,7 @@ class ParticleEmitter extends EngineObject
|
|
|
6613
6655
|
}
|
|
6614
6656
|
|
|
6615
6657
|
// update emitter
|
|
6616
|
-
if (
|
|
6658
|
+
if (this.isActive())
|
|
6617
6659
|
{
|
|
6618
6660
|
// emit particles
|
|
6619
6661
|
if (this.emitRate && particleEmitRateScale)
|
|
@@ -6623,14 +6665,21 @@ class ParticleEmitter extends EngineObject
|
|
|
6623
6665
|
this.emitParticle();
|
|
6624
6666
|
}
|
|
6625
6667
|
}
|
|
6626
|
-
else
|
|
6627
|
-
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
|
+
});
|
|
6628
6677
|
|
|
6629
6678
|
if (debugParticles)
|
|
6630
6679
|
{
|
|
6631
6680
|
// show emitter bounds
|
|
6632
|
-
if (
|
|
6633
|
-
debugCircle(this.pos, this.emitSize/2, '#0f0');
|
|
6681
|
+
if (this.emitCircle)
|
|
6682
|
+
debugCircle(this.pos, this.emitSize.x/2, '#0f0');
|
|
6634
6683
|
else
|
|
6635
6684
|
debugRect(this.pos, this.emitSize, '#0f0', 0, this.angle);
|
|
6636
6685
|
}
|
|
@@ -6641,14 +6690,15 @@ class ParticleEmitter extends EngineObject
|
|
|
6641
6690
|
emitParticle()
|
|
6642
6691
|
{
|
|
6643
6692
|
// spawn a particle
|
|
6644
|
-
let pos =
|
|
6645
|
-
randInCircle(this.emitSize/2)
|
|
6646
|
-
: 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
|
|
6647
6696
|
.multiply(this.emitSize).rotate(this.angle)
|
|
6648
6697
|
let angle = rand(this.particleConeAngle, -this.particleConeAngle);
|
|
6649
6698
|
if (!this.localSpace)
|
|
6650
6699
|
{
|
|
6651
|
-
pos
|
|
6700
|
+
pos.x += this.pos.x;
|
|
6701
|
+
pos.y += this.pos.y;
|
|
6652
6702
|
angle += this.angle;
|
|
6653
6703
|
}
|
|
6654
6704
|
|
|
@@ -6668,25 +6718,17 @@ class ParticleEmitter extends EngineObject
|
|
|
6668
6718
|
const velocityAngle = this.localSpace ? coneAngle : this.angle + coneAngle;
|
|
6669
6719
|
|
|
6670
6720
|
// build particle
|
|
6671
|
-
const
|
|
6672
|
-
|
|
6673
|
-
particle.angleVelocity = angleSpeed;
|
|
6721
|
+
const velocity = vec2(speed*sin(velocityAngle), speed*cos(velocityAngle));
|
|
6722
|
+
let angleVelocity = angleSpeed;
|
|
6674
6723
|
if (!this.localSpace && this.velocityInheritance > 0)
|
|
6675
6724
|
{
|
|
6676
6725
|
// apply emitter velocity to particle
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6726
|
+
velocity.x += this.velocity.x;
|
|
6727
|
+
velocity.y += this.velocity.y;
|
|
6728
|
+
angleVelocity += this.angleVelocity;
|
|
6680
6729
|
}
|
|
6681
|
-
particle
|
|
6682
|
-
|
|
6683
|
-
particle.angleDamping = this.angleDamping;
|
|
6684
|
-
particle.restitution = this.restitution;
|
|
6685
|
-
particle.friction = this.friction;
|
|
6686
|
-
particle.gravityScale = this.gravityScale;
|
|
6687
|
-
particle.collideTiles = this.collideTiles;
|
|
6688
|
-
particle.renderOrder = this.renderOrder;
|
|
6689
|
-
particle.mirror = randBool();
|
|
6730
|
+
const particle = new Particle(this, pos, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, velocity, angleVelocity);
|
|
6731
|
+
this.particles.push(particle);
|
|
6690
6732
|
|
|
6691
6733
|
// call particle create callback
|
|
6692
6734
|
this.particleCreateCallback?.(particle);
|
|
@@ -6695,140 +6737,245 @@ class ParticleEmitter extends EngineObject
|
|
|
6695
6737
|
return particle;
|
|
6696
6738
|
}
|
|
6697
6739
|
|
|
6698
|
-
|
|
6699
|
-
|
|
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
|
+
}
|
|
6700
6768
|
}
|
|
6701
6769
|
|
|
6702
6770
|
///////////////////////////////////////////////////////////////////////////////
|
|
6703
6771
|
/**
|
|
6704
6772
|
* Particle Object - Created automatically by Particle Emitters
|
|
6705
|
-
* @
|
|
6706
|
-
* @memberof Engine
|
|
6773
|
+
* @memberof Particles
|
|
6707
6774
|
*/
|
|
6708
|
-
class Particle
|
|
6775
|
+
class Particle
|
|
6709
6776
|
{
|
|
6710
6777
|
/**
|
|
6711
6778
|
* Create a particle with the passed in settings
|
|
6712
6779
|
* Typically this is created automatically by a ParticleEmitter
|
|
6713
|
-
* @param {
|
|
6714
|
-
* @param {
|
|
6715
|
-
* @param {number}
|
|
6716
|
-
* @param {Color}
|
|
6717
|
-
* @param {Color}
|
|
6718
|
-
* @param {number}
|
|
6719
|
-
* @param {number}
|
|
6720
|
-
* @param {number}
|
|
6721
|
-
* @param {
|
|
6722
|
-
* @param {
|
|
6723
|
-
* @param {number} trailScale - If a trail, how long to make it
|
|
6724
|
-
* @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
|
|
6725
|
-
* @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
|
|
6726
6790
|
*/
|
|
6727
|
-
constructor(
|
|
6728
|
-
)
|
|
6791
|
+
constructor(emitter, pos, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, velocity = vec2(), angleVelocity = 0)
|
|
6729
6792
|
{
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
/** @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} */
|
|
6733
6804
|
this.colorStart = colorStart;
|
|
6734
|
-
/** @property {Color}
|
|
6805
|
+
/** @property {Color} */
|
|
6735
6806
|
this.colorEnd = colorEnd;
|
|
6736
|
-
/** @property {number}
|
|
6807
|
+
/** @property {number} */
|
|
6737
6808
|
this.lifeTime = lifeTime;
|
|
6738
|
-
/** @property {number}
|
|
6809
|
+
/** @property {number} */
|
|
6739
6810
|
this.sizeStart = sizeStart;
|
|
6740
|
-
/** @property {number}
|
|
6811
|
+
/** @property {number} */
|
|
6741
6812
|
this.sizeEnd = sizeEnd;
|
|
6742
|
-
/** @property {
|
|
6743
|
-
this.
|
|
6744
|
-
/** @property {
|
|
6745
|
-
this.
|
|
6746
|
-
/** @property {number}
|
|
6747
|
-
this.
|
|
6748
|
-
/** @property {
|
|
6749
|
-
this.
|
|
6750
|
-
/** @property {
|
|
6751
|
-
this.
|
|
6752
|
-
|
|
6753
|
-
this.
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
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 */
|
|
6757
6830
|
update()
|
|
6758
6831
|
{
|
|
6759
|
-
|
|
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)
|
|
6760
6844
|
{
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
if (length2 > objectMaxSpeed*objectMaxSpeed)
|
|
6764
|
-
{
|
|
6765
|
-
const s = objectMaxSpeed / length2**.5;
|
|
6766
|
-
this.velocity.x *= s;
|
|
6767
|
-
this.velocity.y *= s;
|
|
6768
|
-
}
|
|
6845
|
+
this.destroy();
|
|
6846
|
+
return;
|
|
6769
6847
|
}
|
|
6770
6848
|
|
|
6771
|
-
|
|
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)
|
|
6863
|
+
{
|
|
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))
|
|
6772
6878
|
{
|
|
6773
|
-
//
|
|
6774
|
-
const
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
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
|
+
}
|
|
6779
6911
|
}
|
|
6780
6912
|
}
|
|
6781
6913
|
|
|
6782
|
-
/**
|
|
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 */
|
|
6783
6926
|
render()
|
|
6784
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
|
+
|
|
6785
6935
|
// lerp color and size
|
|
6786
6936
|
const p1 = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1, p2 = 1-p1;
|
|
6787
6937
|
const radius = p2 * this.sizeStart + p1 * this.sizeEnd;
|
|
6788
6938
|
const size = vec2(radius);
|
|
6939
|
+
const alphaFade = p1 < fadeRate ? p1/fadeRate :
|
|
6940
|
+
p1 > 1-fadeRate ? (1-p1)/fadeRate : 1;
|
|
6789
6941
|
this.color.r = p2 * this.colorStart.r + p1 * this.colorEnd.r;
|
|
6790
6942
|
this.color.g = p2 * this.colorStart.g + p1 * this.colorEnd.g;
|
|
6791
6943
|
this.color.b = p2 * this.colorStart.b + p1 * this.colorEnd.b;
|
|
6792
|
-
this.color.a = p2 * this.colorStart.a + p1 * this.colorEnd.a;
|
|
6793
|
-
|
|
6794
|
-
// fade alpha
|
|
6795
|
-
const fadeRate = this.fadeRate/2;
|
|
6796
|
-
this.color.a *= p1 < fadeRate ? p1/fadeRate :
|
|
6797
|
-
p1 > 1-fadeRate ? (1-p1)/fadeRate : 1;
|
|
6944
|
+
this.color.a = (p2 * this.colorStart.a + p1 * this.colorEnd.a) * alphaFade;
|
|
6798
6945
|
|
|
6799
6946
|
// draw the particle
|
|
6800
|
-
|
|
6947
|
+
additive && setBlendMode(true);
|
|
6801
6948
|
|
|
6802
6949
|
// update the position and angle for drawing
|
|
6803
|
-
|
|
6804
|
-
|
|
6950
|
+
const pos = this.pos.copy();
|
|
6951
|
+
let angle = this.angle;
|
|
6952
|
+
if (localSpace)
|
|
6805
6953
|
{
|
|
6806
6954
|
// in local space of emitter
|
|
6807
|
-
const a =
|
|
6955
|
+
const a = emitter.angle;
|
|
6808
6956
|
const c = cos(a), s = sin(a);
|
|
6809
|
-
pos
|
|
6810
|
-
|
|
6811
|
-
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;
|
|
6812
6960
|
}
|
|
6813
|
-
if (
|
|
6961
|
+
if (trailScale)
|
|
6814
6962
|
{
|
|
6815
6963
|
// trail style particles
|
|
6816
|
-
const
|
|
6817
|
-
this.velocity.rotate(-
|
|
6818
|
-
|
|
6819
|
-
const speed = direction.length();
|
|
6964
|
+
const velocity = localSpace ?
|
|
6965
|
+
this.velocity.rotate(-emitter.angle) : this.velocity;
|
|
6966
|
+
const speed = velocity.length();
|
|
6820
6967
|
if (speed)
|
|
6821
6968
|
{
|
|
6822
6969
|
// stretch in direction of motion
|
|
6823
|
-
const trailLength = speed *
|
|
6970
|
+
const trailLength = speed * trailScale;
|
|
6824
6971
|
size.y = max(size.x, trailLength);
|
|
6825
|
-
angle = atan2(
|
|
6972
|
+
angle = atan2(velocity.x, velocity.y);
|
|
6826
6973
|
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
6827
6974
|
}
|
|
6828
6975
|
}
|
|
6829
6976
|
else
|
|
6830
6977
|
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
6831
|
-
|
|
6978
|
+
additive && setBlendMode();
|
|
6832
6979
|
debugParticles && debugRect(pos, size, '#f005', 0, angle);
|
|
6833
6980
|
}
|
|
6834
6981
|
}
|
|
@@ -7282,7 +7429,7 @@ function glPreRender(clear=true)
|
|
|
7282
7429
|
p.x, p.y, 0, 1];
|
|
7283
7430
|
|
|
7284
7431
|
// set the same transform matrix for both shaders
|
|
7285
|
-
const initUniform = (program, uniform, value)
|
|
7432
|
+
const initUniform = (program, uniform, value)=>
|
|
7286
7433
|
{
|
|
7287
7434
|
glContext.useProgram(program);
|
|
7288
7435
|
const location = glContext.getUniformLocation(program, uniform);
|
|
@@ -7676,7 +7823,7 @@ function glSetRenderTarget(texture, clear=false)
|
|
|
7676
7823
|
glContext.bindFramebuffer(glContext.FRAMEBUFFER, glFramebuffer);
|
|
7677
7824
|
glContext.framebufferTexture2D(glContext.FRAMEBUFFER,
|
|
7678
7825
|
glContext.COLOR_ATTACHMENT0, glContext.TEXTURE_2D, texture, 0);
|
|
7679
|
-
|
|
7826
|
+
glPreRender(clear);
|
|
7680
7827
|
}
|
|
7681
7828
|
else
|
|
7682
7829
|
{
|
|
@@ -8108,7 +8255,7 @@ class PostProcessPlugin
|
|
|
8108
8255
|
{
|
|
8109
8256
|
/** Create global post processing shader
|
|
8110
8257
|
* @param {string} shaderCode
|
|
8111
|
-
* @param {boolean} [includeMainCanvas] - combine
|
|
8258
|
+
* @param {boolean} [includeMainCanvas] - combine mainCanvas onto glCanvas
|
|
8112
8259
|
* @param {boolean} [feedbackTexture] - use glCanvas from previous frame as the texture
|
|
8113
8260
|
* @example
|
|
8114
8261
|
* // create the post process plugin object
|
|
@@ -8295,7 +8442,7 @@ class ZzFXMusic extends Sound
|
|
|
8295
8442
|
/** Play the music that loops by default
|
|
8296
8443
|
* @param {number} [volume] - Volume to play the music at
|
|
8297
8444
|
* @param {boolean} [loop] - Should the music loop?
|
|
8298
|
-
* @return {
|
|
8445
|
+
* @return {SoundInstance} - The sound instance
|
|
8299
8446
|
*/
|
|
8300
8447
|
playMusic(volume=1, loop=true)
|
|
8301
8448
|
{ return super.play(undefined, volume, 1, 0, loop); }
|
|
@@ -8342,7 +8489,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
8342
8489
|
sampleBuffer = [hasMore = notFirstBeat = outSampleOffset = 0];
|
|
8343
8490
|
|
|
8344
8491
|
// for each pattern in sequence
|
|
8345
|
-
sequence.forEach((patternIndex, sequenceIndex)
|
|
8492
|
+
sequence.forEach((patternIndex, sequenceIndex)=> {
|
|
8346
8493
|
// get pattern for current channel, use empty 1 note pattern if none found
|
|
8347
8494
|
patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
|
|
8348
8495
|
|
|
@@ -8435,7 +8582,7 @@ let uiDebug = 0;
|
|
|
8435
8582
|
|
|
8436
8583
|
/** Enable UI system debug drawing
|
|
8437
8584
|
* 0=off, 1=normal, 2=show invisible
|
|
8438
|
-
* @param {number|boolean}
|
|
8585
|
+
* @param {number|boolean} debugMode
|
|
8439
8586
|
* @memberof UISystem */
|
|
8440
8587
|
function uiSetDebug(debugMode)
|
|
8441
8588
|
{ uiDebug = typeof debugMode === 'boolean' ? (debugMode ? 1 : 0) : debugMode; }
|
|
@@ -8501,7 +8648,7 @@ class UISystemPlugin
|
|
|
8501
8648
|
// navigation properties
|
|
8502
8649
|
/** @property {UIObject} - Object currently selected by navigation (gamepad or keyboard) */
|
|
8503
8650
|
this.navigationObject = undefined;
|
|
8504
|
-
/** @property {Timer} -
|
|
8651
|
+
/** @property {Timer} - Cool down timer for navigation inputs */
|
|
8505
8652
|
this.navigationTimer = new Timer(undefined, true);
|
|
8506
8653
|
/** @property {number} - Time between navigation inputs in seconds */
|
|
8507
8654
|
this.navigationDelay = .2;
|
|
@@ -8967,7 +9114,7 @@ class UISystemPlugin
|
|
|
8967
9114
|
|
|
8968
9115
|
const savedNavigationDirection = uiSystem.navigationDirection;
|
|
8969
9116
|
|
|
8970
|
-
// allow both
|
|
9117
|
+
// allow both axes for navigation
|
|
8971
9118
|
uiSystem.navigationDirection = 2;
|
|
8972
9119
|
|
|
8973
9120
|
// confirm menu
|
|
@@ -9062,7 +9209,7 @@ class UIObject
|
|
|
9062
9209
|
this.lineWidth = uiSystem.defaultLineWidth;
|
|
9063
9210
|
/** @property {number} - Corner radius for rounded rects */
|
|
9064
9211
|
this.cornerRadius = uiSystem.defaultCornerRadius;
|
|
9065
|
-
/** @property {string} - Font for this
|
|
9212
|
+
/** @property {string} - Font for this object */
|
|
9066
9213
|
this.font = uiSystem.defaultFont;
|
|
9067
9214
|
/** @property {string} - Font style for this object or undefined */
|
|
9068
9215
|
this.fontStyle = undefined;
|
|
@@ -9550,6 +9697,8 @@ class UIScrollbar extends UIObject
|
|
|
9550
9697
|
this.value = value;
|
|
9551
9698
|
/** @property {Color} - Color for the handle part of the scrollbar */
|
|
9552
9699
|
this.handleColor = handleColor.copy();
|
|
9700
|
+
/** @property {boolean} - Should it fill up like a progress bar? */
|
|
9701
|
+
this.fillMode = false;
|
|
9553
9702
|
|
|
9554
9703
|
// set properties
|
|
9555
9704
|
this.text = text;
|
|
@@ -9595,19 +9744,30 @@ class UIScrollbar extends UIObject
|
|
|
9595
9744
|
|
|
9596
9745
|
// handle horizontal or vertical scrollbar
|
|
9597
9746
|
const isHorizontal = this.size.x > this.size.y;
|
|
9598
|
-
const
|
|
9599
|
-
const
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
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
|
+
}
|
|
9611
9771
|
|
|
9612
9772
|
// draw the text scaled to fit on the scrollbar
|
|
9613
9773
|
const textSize = this.getTextSize();
|
|
@@ -9629,15 +9789,15 @@ class UIScrollbar extends UIObject
|
|
|
9629
9789
|
* @extends UIObject
|
|
9630
9790
|
* @example
|
|
9631
9791
|
* // Create a video player UI object
|
|
9632
|
-
* const video = new VideoPlayerUIObject(vec2(400, 300), vec2(320, 240), '
|
|
9792
|
+
* const video = new VideoPlayerUIObject(vec2(400, 300), vec2(320, 240), 'video.mp4', true);
|
|
9633
9793
|
* video.play();
|
|
9634
9794
|
* @memberof UISystem
|
|
9635
9795
|
*/
|
|
9636
9796
|
class UIVideo extends UIObject
|
|
9637
9797
|
{
|
|
9638
9798
|
/** Create a video player UI object
|
|
9639
|
-
* @param {Vector2}
|
|
9640
|
-
* @param {Vector2}
|
|
9799
|
+
* @param {Vector2} pos
|
|
9800
|
+
* @param {Vector2} size
|
|
9641
9801
|
* @param {string} src - Video file path or URL
|
|
9642
9802
|
* @param {boolean} [autoplay=false] - Start playing immediately?
|
|
9643
9803
|
* @param {boolean} [loop=false] - Loop the video?
|
|
@@ -9777,6 +9937,7 @@ class UIVideo extends UIObject
|
|
|
9777
9937
|
* - Contact begin and end callbacks
|
|
9778
9938
|
* - Wraps b2Vec2 type to/from Vector2
|
|
9779
9939
|
* - Raycasting and querying
|
|
9940
|
+
* - Box2dTileLayer for grid based collision
|
|
9780
9941
|
* - Every type of joint
|
|
9781
9942
|
* - Debug physics drawing
|
|
9782
9943
|
* @namespace Box2D
|
|
@@ -9826,21 +9987,24 @@ class Box2dObject extends EngineObject
|
|
|
9826
9987
|
bodyDef.set_type(bodyType);
|
|
9827
9988
|
bodyDef.set_position(box2d.vec2dTo(pos));
|
|
9828
9989
|
bodyDef.set_angle(-angle);
|
|
9990
|
+
|
|
9991
|
+
/** @property {Object} - The Box2d body */
|
|
9829
9992
|
this.body = box2d.world.CreateBody(bodyDef);
|
|
9830
|
-
|
|
9993
|
+
/** @property {Color} - Line color used for default box2d drawing */
|
|
9831
9994
|
this.lineColor = BLACK;
|
|
9832
|
-
box2d
|
|
9833
|
-
|
|
9834
|
-
// edge lists and loops for drawing
|
|
9995
|
+
/** @property {Array<Object>} - List of all edges for default box2d drawing */
|
|
9835
9996
|
this.edgeLists = [];
|
|
9997
|
+
/** @property {Array<Object>} - List of all edge loops for default box2d drawing */
|
|
9836
9998
|
this.edgeLoops = [];
|
|
9999
|
+
|
|
10000
|
+
this.body.object = this; // link body to this object
|
|
10001
|
+
box2d.objects.push(this); // keep track of all box2d objects
|
|
9837
10002
|
}
|
|
9838
10003
|
|
|
9839
10004
|
/** Destroy this object and its physics body */
|
|
9840
10005
|
destroy()
|
|
9841
10006
|
{
|
|
9842
|
-
if (this.destroyed)
|
|
9843
|
-
return;
|
|
10007
|
+
if (this.destroyed) return;
|
|
9844
10008
|
|
|
9845
10009
|
// destroy physics body, fixtures, and joints
|
|
9846
10010
|
ASSERT(this.body, 'Box2dObject has no body to destroy');
|
|
@@ -9871,11 +10035,12 @@ class Box2dObject extends EngineObject
|
|
|
9871
10035
|
}
|
|
9872
10036
|
|
|
9873
10037
|
/** Draws all this object's fixtures
|
|
9874
|
-
* @param {Color}
|
|
9875
|
-
* @param {Color}
|
|
9876
|
-
* @param {number}
|
|
10038
|
+
* @param {Color} [color]
|
|
10039
|
+
* @param {Color} [lineColor]
|
|
10040
|
+
* @param {number} [lineWidth]
|
|
10041
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
9877
10042
|
* @param {CanvasRenderingContext2D} [context] */
|
|
9878
|
-
drawFixtures(color=WHITE, lineColor=BLACK, lineWidth=.1, context)
|
|
10043
|
+
drawFixtures(color=WHITE, lineColor=BLACK, lineWidth=.1, useWebGL, context)
|
|
9879
10044
|
{
|
|
9880
10045
|
// draw non-edge fixtures
|
|
9881
10046
|
this.getFixtureList().forEach((fixture)=>
|
|
@@ -9883,7 +10048,7 @@ class Box2dObject extends EngineObject
|
|
|
9883
10048
|
const shape = box2d.castObjectType(fixture.GetShape());
|
|
9884
10049
|
if (shape.GetType() !== box2d.instance.b2Shape.e_edge)
|
|
9885
10050
|
{
|
|
9886
|
-
box2d.drawFixture(fixture, this.pos, this.angle, color, lineColor, lineWidth, context);
|
|
10051
|
+
box2d.drawFixture(fixture, this.pos, this.angle, color, lineColor, lineWidth, useWebGL, context);
|
|
9887
10052
|
}
|
|
9888
10053
|
});
|
|
9889
10054
|
|
|
@@ -10109,6 +10274,14 @@ class Box2dObject extends EngineObject
|
|
|
10109
10274
|
return fixtures;
|
|
10110
10275
|
}
|
|
10111
10276
|
|
|
10277
|
+
/** Destroy a fixture from the body
|
|
10278
|
+
* @param {Object} [fixture] */
|
|
10279
|
+
destroyFixture(fixture) { this.body.DestroyFixture(fixture); }
|
|
10280
|
+
|
|
10281
|
+
/** Destroy all fixture from the body */
|
|
10282
|
+
destroyAllFixtures()
|
|
10283
|
+
{ this.getFixtureList().forEach(fixture=>this.destroyFixture(fixture)); }
|
|
10284
|
+
|
|
10112
10285
|
///////////////////////////////////////////////////////////////////////////////
|
|
10113
10286
|
// physics get functions
|
|
10114
10287
|
|
|
@@ -10341,6 +10514,135 @@ class Box2dObject extends EngineObject
|
|
|
10341
10514
|
}
|
|
10342
10515
|
}
|
|
10343
10516
|
|
|
10517
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
10518
|
+
/**
|
|
10519
|
+
* Box2D Static Object - Box2d with a static physics body
|
|
10520
|
+
* @extends Box2dObject
|
|
10521
|
+
* @memberof Box2D
|
|
10522
|
+
*/
|
|
10523
|
+
class Box2dStaticObject extends Box2dObject
|
|
10524
|
+
{
|
|
10525
|
+
/** Create a LittleJS object with Box2d physics
|
|
10526
|
+
* @param {Vector2} [pos]
|
|
10527
|
+
* @param {Vector2} [size]
|
|
10528
|
+
* @param {TileInfo} [tileInfo]
|
|
10529
|
+
* @param {number} [angle]
|
|
10530
|
+
* @param {Color} [color]
|
|
10531
|
+
* @param {number} [renderOrder] */
|
|
10532
|
+
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
10533
|
+
{
|
|
10534
|
+
const bodyType = box2d.bodyTypeStatic;
|
|
10535
|
+
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
10536
|
+
}
|
|
10537
|
+
}
|
|
10538
|
+
|
|
10539
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
10540
|
+
/**
|
|
10541
|
+
* Box2D Kinematic Object - Box2d with a kinematic physics body
|
|
10542
|
+
* @extends Box2dObject
|
|
10543
|
+
* @memberof Box2D
|
|
10544
|
+
*/
|
|
10545
|
+
class Box2dKinematicObject extends Box2dObject
|
|
10546
|
+
{
|
|
10547
|
+
/** Create a LittleJS object with Box2d physics
|
|
10548
|
+
* @param {Vector2} [pos]
|
|
10549
|
+
* @param {Vector2} [size]
|
|
10550
|
+
* @param {TileInfo} [tileInfo]
|
|
10551
|
+
* @param {number} [angle]
|
|
10552
|
+
* @param {Color} [color]
|
|
10553
|
+
* @param {number} [renderOrder] */
|
|
10554
|
+
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
10555
|
+
{
|
|
10556
|
+
const bodyType = box2d.bodyTypeKinematic;
|
|
10557
|
+
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
10558
|
+
}
|
|
10559
|
+
}
|
|
10560
|
+
|
|
10561
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
10562
|
+
/**
|
|
10563
|
+
* Box2d Tile Layer
|
|
10564
|
+
* - adds Box2d support to tile layers
|
|
10565
|
+
* - creates static box2d fixtures for solid tiles
|
|
10566
|
+
* @extends Box2dObject
|
|
10567
|
+
* @memberof Box2D
|
|
10568
|
+
*/
|
|
10569
|
+
class Box2dTileLayer extends Box2dStaticObject
|
|
10570
|
+
{
|
|
10571
|
+
/** Create a Box2d tile layer object
|
|
10572
|
+
* @param {TileCollisionLayer} tileLayer - Tile layer for this object */
|
|
10573
|
+
constructor(tileLayer)
|
|
10574
|
+
{
|
|
10575
|
+
ASSERT(tileLayer instanceof TileCollisionLayer, 'tileLayer must be a TileCollisionLayer');
|
|
10576
|
+
super(tileLayer.pos, tileLayer.size);
|
|
10577
|
+
|
|
10578
|
+
/** @property {TileLayer} - The tile layer */
|
|
10579
|
+
this.tileLayer = tileLayer;
|
|
10580
|
+
this.addChild(tileLayer);
|
|
10581
|
+
}
|
|
10582
|
+
|
|
10583
|
+
render()
|
|
10584
|
+
{
|
|
10585
|
+
// do not render fixtures, tile layer handles rendering
|
|
10586
|
+
}
|
|
10587
|
+
|
|
10588
|
+
/** Create box2d collision fixtures for solid tiles
|
|
10589
|
+
* @param {number} [friction]
|
|
10590
|
+
* @param {number} [restitution] */
|
|
10591
|
+
buildCollision(friction=.2, restitution=0)
|
|
10592
|
+
{
|
|
10593
|
+
// destroy all fixtures and create new ones
|
|
10594
|
+
this.destroyAllFixtures();
|
|
10595
|
+
|
|
10596
|
+
// create box2d object for this layer
|
|
10597
|
+
this.pos = this.tileLayer.pos.copy();
|
|
10598
|
+
this.size = this.tileLayer.size.copy();
|
|
10599
|
+
|
|
10600
|
+
// track which tiles have been processed
|
|
10601
|
+
const processed = [];
|
|
10602
|
+
const getIndex = (x, y)=> x + y * this.size.x;
|
|
10603
|
+
const isSolidUnprocessed = (x, y)=>
|
|
10604
|
+
!processed[getIndex(x, y)] &&
|
|
10605
|
+
this.tileLayer.getCollisionData(vec2(x, y)) > 0;
|
|
10606
|
+
|
|
10607
|
+
// combine tiles into larger boxes
|
|
10608
|
+
for (let x = 0; x < this.size.x; ++x)
|
|
10609
|
+
for (let y = 0; y < this.size.y; ++y)
|
|
10610
|
+
{
|
|
10611
|
+
if (!isSolidUnprocessed(x, y)) continue;
|
|
10612
|
+
|
|
10613
|
+
// find max width by scanning right
|
|
10614
|
+
let width = 1, height = 1, canExpand = true;
|
|
10615
|
+
while (isSolidUnprocessed(x + width, y))
|
|
10616
|
+
++width;
|
|
10617
|
+
|
|
10618
|
+
// find max height by scanning up, ensuring all rows have the same width
|
|
10619
|
+
while (canExpand)
|
|
10620
|
+
{
|
|
10621
|
+
for (let checkX = 0; checkX < width; ++checkX)
|
|
10622
|
+
{
|
|
10623
|
+
if (!isSolidUnprocessed(x + checkX, y + height))
|
|
10624
|
+
{
|
|
10625
|
+
canExpand = false;
|
|
10626
|
+
break;
|
|
10627
|
+
}
|
|
10628
|
+
}
|
|
10629
|
+
if (canExpand)
|
|
10630
|
+
++height;
|
|
10631
|
+
}
|
|
10632
|
+
|
|
10633
|
+
// mark all tiles in this rectangle as processed
|
|
10634
|
+
for (let rectX = width; rectX--;)
|
|
10635
|
+
for (let rectY = height; rectY--;)
|
|
10636
|
+
processed[getIndex(x + rectX, y + rectY)] = true;
|
|
10637
|
+
|
|
10638
|
+
// create a single fixture for the entire rectangle
|
|
10639
|
+
const shapeSize = vec2(width, height);
|
|
10640
|
+
const offset = vec2(x + width/2, y + height/2);
|
|
10641
|
+
this.addBox(shapeSize, offset, 0, 0, friction, restitution);
|
|
10642
|
+
}
|
|
10643
|
+
}
|
|
10644
|
+
}
|
|
10645
|
+
|
|
10344
10646
|
///////////////////////////////////////////////////////////////////////////////
|
|
10345
10647
|
/**
|
|
10346
10648
|
* Box2D Raycast Result
|
|
@@ -10382,6 +10684,7 @@ class Box2dJoint
|
|
|
10382
10684
|
* @param {Object} jointDef */
|
|
10383
10685
|
constructor(jointDef)
|
|
10384
10686
|
{
|
|
10687
|
+
/** @property {Object} - The Box2d joint */
|
|
10385
10688
|
this.box2dJoint = box2d.castObjectType(box2d.world.CreateJoint(jointDef));
|
|
10386
10689
|
}
|
|
10387
10690
|
|
|
@@ -10728,7 +11031,7 @@ class Box2dGearJoint extends Box2dJoint
|
|
|
10728
11031
|
* @param {Box2dObject} objectB
|
|
10729
11032
|
* @param {Box2dJoint} joint1
|
|
10730
11033
|
* @param {Box2dJoint} joint2
|
|
10731
|
-
* @param {
|
|
11034
|
+
* @param {number} [ratio] */
|
|
10732
11035
|
constructor(objectA, objectB, joint1, joint2, ratio=1)
|
|
10733
11036
|
{
|
|
10734
11037
|
const jointDef = new box2d.instance.b2GearJointDef();
|
|
@@ -10870,50 +11173,6 @@ class Box2dPrismaticJoint extends Box2dJoint
|
|
|
10870
11173
|
getMotorForce(time) { return this.box2dJoint.GetMotorForce(1/time); }
|
|
10871
11174
|
}
|
|
10872
11175
|
|
|
10873
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
10874
|
-
/**
|
|
10875
|
-
* Box2D Static Object - Box2d with a static physics body
|
|
10876
|
-
* @extends Box2dObject
|
|
10877
|
-
* @memberof Box2D
|
|
10878
|
-
*/
|
|
10879
|
-
class Box2dStaticObject extends Box2dObject
|
|
10880
|
-
{
|
|
10881
|
-
/** Create a LittleJS object with Box2d physics
|
|
10882
|
-
* @param {Vector2} [pos]
|
|
10883
|
-
* @param {Vector2} [size]
|
|
10884
|
-
* @param {TileInfo} [tileInfo]
|
|
10885
|
-
* @param {number} [angle]
|
|
10886
|
-
* @param {Color} [color]
|
|
10887
|
-
* @param {number} [renderOrder] */
|
|
10888
|
-
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
10889
|
-
{
|
|
10890
|
-
const bodyType = box2d.bodyTypeStatic;
|
|
10891
|
-
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
10892
|
-
}
|
|
10893
|
-
}
|
|
10894
|
-
|
|
10895
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
10896
|
-
/**
|
|
10897
|
-
* Box2D Kiematic Object - Box2d with a kinematic physics body
|
|
10898
|
-
* @extends Box2dObject
|
|
10899
|
-
* @memberof Box2D
|
|
10900
|
-
*/
|
|
10901
|
-
class Box2dKiematicObject extends Box2dObject
|
|
10902
|
-
{
|
|
10903
|
-
/** Create a LittleJS object with Box2d physics
|
|
10904
|
-
* @param {Vector2} [pos]
|
|
10905
|
-
* @param {Vector2} [size]
|
|
10906
|
-
* @param {TileInfo} [tileInfo]
|
|
10907
|
-
* @param {number} [angle]
|
|
10908
|
-
* @param {Color} [color]
|
|
10909
|
-
* @param {number} [renderOrder] */
|
|
10910
|
-
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
10911
|
-
{
|
|
10912
|
-
const bodyType = box2d.bodyTypeKinematic;
|
|
10913
|
-
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
10914
|
-
}
|
|
10915
|
-
}
|
|
10916
|
-
|
|
10917
11176
|
///////////////////////////////////////////////////////////////////////////////
|
|
10918
11177
|
/**
|
|
10919
11178
|
* Box2D Wheel Joint
|
|
@@ -11274,10 +11533,13 @@ class Box2dPlugin
|
|
|
11274
11533
|
{
|
|
11275
11534
|
ASSERT(!box2d, 'Box2D already initialized');
|
|
11276
11535
|
box2d = this;
|
|
11536
|
+
|
|
11537
|
+
/** @property {Object} - The Box2d instance */
|
|
11277
11538
|
this.instance = instance;
|
|
11539
|
+
/** @property {Object} - The Box2d world */
|
|
11278
11540
|
this.world = new box2d.instance.b2World();
|
|
11541
|
+
/** @property {Array<Box2dObject>} - List of all Box2d objects */
|
|
11279
11542
|
this.objects = [];
|
|
11280
|
-
|
|
11281
11543
|
/** @property {number} - Velocity iterations per update*/
|
|
11282
11544
|
this.velocityIterations = 8;
|
|
11283
11545
|
/** @property {number} - Position iterations per update*/
|
|
@@ -11476,8 +11738,9 @@ class Box2dPlugin
|
|
|
11476
11738
|
* @param {Color} [color]
|
|
11477
11739
|
* @param {Color} [lineColor]
|
|
11478
11740
|
* @param {number} [lineWidth]
|
|
11741
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
11479
11742
|
* @param {CanvasRenderingContext2D} [context] */
|
|
11480
|
-
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, context)
|
|
11743
|
+
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, useWebgl, context)
|
|
11481
11744
|
{
|
|
11482
11745
|
const shape = box2d.castObjectType(fixture.GetShape());
|
|
11483
11746
|
switch (shape.GetType())
|
|
@@ -11487,20 +11750,20 @@ class Box2dPlugin
|
|
|
11487
11750
|
let points = [];
|
|
11488
11751
|
for (let i=shape.GetVertexCount(); i--;)
|
|
11489
11752
|
points.push(box2d.vec2From(shape.GetVertex(i)));
|
|
11490
|
-
drawPoly(points, color, lineWidth, lineColor, pos, angle);
|
|
11753
|
+
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebgl, false, context);
|
|
11491
11754
|
break;
|
|
11492
11755
|
}
|
|
11493
11756
|
case box2d.instance.b2Shape.e_circle:
|
|
11494
11757
|
{
|
|
11495
11758
|
const radius = shape.get_m_radius();
|
|
11496
|
-
drawCircle(pos, radius*2, color, lineWidth, lineColor);
|
|
11759
|
+
drawCircle(pos, radius*2, color, lineWidth, lineColor, useWebgl, false, context);
|
|
11497
11760
|
break;
|
|
11498
11761
|
}
|
|
11499
11762
|
case box2d.instance.b2Shape.e_edge:
|
|
11500
11763
|
{
|
|
11501
11764
|
const v1 = box2d.vec2From(shape.get_m_vertex1());
|
|
11502
11765
|
const v2 = box2d.vec2From(shape.get_m_vertex2());
|
|
11503
|
-
drawLine(v1, v2, lineWidth, lineColor, pos, angle);
|
|
11766
|
+
drawLine(v1, v2, lineWidth, lineColor, pos, angle, useWebgl, false, context);
|
|
11504
11767
|
break;
|
|
11505
11768
|
}
|
|
11506
11769
|
}
|
|
@@ -11518,7 +11781,7 @@ class Box2dPlugin
|
|
|
11518
11781
|
}
|
|
11519
11782
|
|
|
11520
11783
|
/** converts a box2d vec2 pointer to a Vector2
|
|
11521
|
-
* @param {Object}
|
|
11784
|
+
* @param {Object} vp */
|
|
11522
11785
|
vec2FromPointer(vp)
|
|
11523
11786
|
{
|
|
11524
11787
|
const v = box2d.instance.wrapPointer(vp, box2d.instance.b2Vec2);
|
|
@@ -11630,7 +11893,7 @@ async function box2dInit()
|
|
|
11630
11893
|
const box2dColorPointer = (c)=>
|
|
11631
11894
|
box2dColor(box2d.instance.wrapPointer(c, box2d.instance.b2Color));
|
|
11632
11895
|
const getDebugColor = (color)=>box2dColorPointer(color).scale(1,.8);
|
|
11633
|
-
const getPointsList = (vertices, vertexCount)
|
|
11896
|
+
const getPointsList = (vertices, vertexCount)=>
|
|
11634
11897
|
{
|
|
11635
11898
|
const points = [];
|
|
11636
11899
|
for (let i=vertexCount; i--;)
|