littlejsengine 1.17.5 → 1.17.11
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 +189 -97
- package/dist/littlejs.esm.js +637 -477
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +633 -476
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +631 -474
- package/examples/electron/index.html +2 -2
- package/examples/games.jpg +0 -0
- package/examples/index.html +2 -2
- package/examples/logo.png +0 -0
- package/examples/logo2.png +0 -0
- package/examples/particles/index.html +1 -1
- package/examples/platformer/gameEffects.js +3 -3
- package/examples/screenshot.jpg +0 -0
- package/examples/shorts/base.html +1 -1
- package/examples/shorts/colors.js +1 -1
- package/examples/shorts/fontImage.js +1 -1
- package/examples/shorts/fps.js +1 -1
- package/examples/shorts/music.js +3 -2
- package/examples/shorts/musicPlayer.js +2 -1
- package/examples/shorts/parallax.js +2 -2
- package/examples/shorts/tiltedView.js +1 -1
- package/examples/starter/index.html +2 -2
- package/package.json +1 -1
- package/plugins/box2d.js +6 -7
- package/plugins/pluginExport.js +2 -1
- package/plugins/postProcess.js +1 -1
- package/plugins/uiSystem.js +33 -21
- package/reference.md +6 -6
- package/src/engine.js +106 -152
- package/src/engineAudio.js +5 -5
- package/src/engineDebug.js +1 -1
- package/src/engineDraw.js +36 -14
- package/src/engineExport.js +2 -0
- package/src/engineInput.js +1 -1
- package/src/engineMath.js +6 -4
- package/src/engineObject.js +24 -24
- package/src/engineParticles.js +241 -118
- package/src/engineTileLayer.js +66 -48
- package/src/engineUtilities.js +105 -78
- package/plugins/Box2D_License.txt +0 -17
package/dist/littlejs.esm.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.11';
|
|
37
37
|
|
|
38
38
|
/** Frames per second to update
|
|
39
39
|
* @type {number}
|
|
@@ -396,33 +396,11 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
396
396
|
workReadContext = workReadCanvas.getContext('2d', { willReadFrequently: true });
|
|
397
397
|
|
|
398
398
|
// create promises for loading images
|
|
399
|
-
const promises = imageSources.map((src,
|
|
400
|
-
new Promise(resolve =>
|
|
401
|
-
{
|
|
402
|
-
ASSERT(isString(src), 'imageSources must be an array of strings');
|
|
403
|
-
|
|
404
|
-
const image = new Image;
|
|
405
|
-
image.onerror = image.onload = ()=>
|
|
406
|
-
{
|
|
407
|
-
const textureInfo = new TextureInfo(image);
|
|
408
|
-
textureInfos[textureIndex] = textureInfo;
|
|
409
|
-
resolve();
|
|
410
|
-
}
|
|
411
|
-
image.crossOrigin = 'anonymous';
|
|
412
|
-
image.src = src;
|
|
413
|
-
})
|
|
414
|
-
);
|
|
399
|
+
const promises = imageSources.map((src, i)=> loadTexture(i, src));
|
|
415
400
|
|
|
401
|
+
// no images to load
|
|
416
402
|
if (!imageSources.length)
|
|
417
|
-
|
|
418
|
-
// no images to load
|
|
419
|
-
promises.push(new Promise(resolve =>
|
|
420
|
-
{
|
|
421
|
-
const textureInfo = new TextureInfo(new Image);
|
|
422
|
-
textureInfos[0] = textureInfo;
|
|
423
|
-
resolve();
|
|
424
|
-
}));
|
|
425
|
-
}
|
|
403
|
+
promises.push(loadTexture(0));
|
|
426
404
|
|
|
427
405
|
// load engine font image
|
|
428
406
|
promises.push(fontImageInit());
|
|
@@ -464,13 +442,13 @@ function engineObjectsUpdate()
|
|
|
464
442
|
engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
|
|
465
443
|
|
|
466
444
|
// recursive object update
|
|
467
|
-
function
|
|
445
|
+
function updateChildObject(o)
|
|
468
446
|
{
|
|
469
447
|
if (o.destroyed) return;
|
|
470
448
|
|
|
471
449
|
o.update();
|
|
472
450
|
for (const child of o.children)
|
|
473
|
-
|
|
451
|
+
updateChildObject(child);
|
|
474
452
|
}
|
|
475
453
|
for (const o of engineObjects)
|
|
476
454
|
{
|
|
@@ -480,7 +458,7 @@ function engineObjectsUpdate()
|
|
|
480
458
|
o.update();
|
|
481
459
|
o.updatePhysics();
|
|
482
460
|
for (const child of o.children)
|
|
483
|
-
|
|
461
|
+
updateChildObject(child);
|
|
484
462
|
o.updateTransforms();
|
|
485
463
|
}
|
|
486
464
|
|
|
@@ -489,11 +467,14 @@ function engineObjectsUpdate()
|
|
|
489
467
|
}
|
|
490
468
|
|
|
491
469
|
/** Destroy and remove all objects
|
|
470
|
+
* - This can be used to clear out all objects when restarting a level
|
|
471
|
+
* - Objects can override their destroy function to do cleanup or stick around
|
|
472
|
+
* @param {boolean} [immediate] - should attached effects be allowed to die off?
|
|
492
473
|
* @memberof Engine */
|
|
493
|
-
function engineObjectsDestroy()
|
|
474
|
+
function engineObjectsDestroy(immediate=true)
|
|
494
475
|
{
|
|
495
476
|
for (const o of engineObjects)
|
|
496
|
-
o.parent || o.destroy();
|
|
477
|
+
o.parent || o.destroy(immediate);
|
|
497
478
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
498
479
|
}
|
|
499
480
|
|
|
@@ -568,165 +549,138 @@ function engineObjectsRaycast(start, end, objects=engineObjects)
|
|
|
568
549
|
///////////////////////////////////////////////////////////////////////////////
|
|
569
550
|
function drawEngineLogo(t)
|
|
570
551
|
{
|
|
552
|
+
const blackAndWhite = 0;
|
|
553
|
+
const showName = 1;
|
|
554
|
+
|
|
571
555
|
// LittleJS Logo and Splash Screen
|
|
572
556
|
const x = mainContext;
|
|
573
557
|
const w = mainCanvas.width = innerWidth;
|
|
574
558
|
const h = mainCanvas.height = innerHeight;
|
|
575
|
-
|
|
576
559
|
{
|
|
577
560
|
// background
|
|
578
561
|
const p3 = percent(t, 1, .8);
|
|
579
562
|
const p4 = percent(t, 0, .5);
|
|
580
|
-
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.
|
|
563
|
+
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.6);
|
|
581
564
|
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
582
565
|
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
583
566
|
x.save();
|
|
584
567
|
x.fillStyle = g;
|
|
585
568
|
x.fillRect(0,0,w,h);
|
|
586
569
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
570
|
+
const gradient = (X1,Y1,X2,Y2,C,S=1)=>
|
|
571
|
+
{
|
|
572
|
+
if (C >= 0)
|
|
573
|
+
{
|
|
574
|
+
if (blackAndWhite)
|
|
575
|
+
x.fillStyle = '#fff';
|
|
576
|
+
else
|
|
577
|
+
{
|
|
578
|
+
const g = x.fillStyle = x.createLinearGradient(X1,Y1,X2,Y2);
|
|
579
|
+
g.addColorStop(0,color(C,2));
|
|
580
|
+
g.addColorStop(1,color(C,1));
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
else
|
|
584
|
+
x.fillStyle = '#000';
|
|
585
|
+
C >= -1 ? (x.fill(), S && x.stroke()) : x.stroke();
|
|
586
|
+
}
|
|
587
|
+
const circle = (X,Y,R,A=0,B=2*PI,C,S)=>
|
|
590
588
|
{
|
|
591
589
|
x.beginPath();
|
|
592
|
-
x.
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
const line = (X, Y, Z, W)=>
|
|
590
|
+
x.arc(X,Y,R,p*A,p*B);
|
|
591
|
+
gradient(X,Y-R,X,Y+R,C,S);
|
|
592
|
+
}
|
|
593
|
+
const rect = (X,Y,W,H,C)=>
|
|
597
594
|
{
|
|
598
595
|
x.beginPath();
|
|
599
|
-
x.
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
|
|
596
|
+
x.rect(X,Y,W,H*p);
|
|
597
|
+
gradient(X,Y+H,X+W,Y,C);
|
|
598
|
+
}
|
|
599
|
+
const poly = (points,C,Y,H)=>
|
|
604
600
|
{
|
|
605
|
-
const D = (A+B)/2, E = p*(B-A)/2;
|
|
606
601
|
x.beginPath();
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
x.
|
|
610
|
-
|
|
611
|
-
}
|
|
612
|
-
const color = (c
|
|
613
|
-
|
|
602
|
+
for (const p of points)
|
|
603
|
+
x.lineTo(p.x, p.y);
|
|
604
|
+
x.closePath();
|
|
605
|
+
gradient(0, Y, 0, Y+H,C);
|
|
606
|
+
}
|
|
607
|
+
const color = (c,l)=> l?`hsl(${[.95,.56,.13][c%3]*360} 99%${[0,50,75][l]}%`:'#000';
|
|
608
|
+
|
|
609
|
+
// center and fit tos screen
|
|
614
610
|
const alpha = wave(1,1,t);
|
|
615
611
|
const p = percent(alpha, .1, .5);
|
|
616
|
-
|
|
617
|
-
// setup
|
|
612
|
+
const size = min(6, min(w,h)/99);
|
|
618
613
|
x.translate(w/2,h/2);
|
|
619
|
-
const size = min(6, min(w,h)/99); // fit to screen
|
|
620
614
|
x.scale(size,size);
|
|
621
615
|
x.translate(-40,-35);
|
|
616
|
+
p < 1 && x.setLineDash([99*p,99]);
|
|
622
617
|
x.lineJoin = x.lineCap = 'round';
|
|
623
618
|
x.lineWidth = .1 + p*1.9;
|
|
619
|
+
//x.strokeStyle='#fff7';
|
|
620
|
+
|
|
621
|
+
if (showName)
|
|
622
|
+
{
|
|
623
|
+
// engine name text
|
|
624
|
+
const Y = 54;
|
|
625
|
+
const s = 'LittleJS';
|
|
626
|
+
x.font = '900 15.5px arial';
|
|
627
|
+
x.lineWidth = .1+p*3.9;
|
|
628
|
+
x.textAlign = 'center';
|
|
629
|
+
x.textBaseline = 'top';
|
|
630
|
+
rect(11,Y+1,59,8*p,-1);
|
|
631
|
+
x.beginPath();
|
|
632
|
+
|
|
633
|
+
let w2 = 0;
|
|
634
|
+
for (let i=0;i<s.length;++i)
|
|
635
|
+
w2 += x.measureText(s[i]).width;
|
|
636
|
+
for (let j=2;j--;)
|
|
637
|
+
for (let i=0,X=40-w2/2;i<s.length;++i)
|
|
638
|
+
{
|
|
639
|
+
const w = x.measureText(s[i]).width, X2 = X+w/2;
|
|
640
|
+
gradient(X2,Y,X2+2,Y+13,i>5?1:0);
|
|
641
|
+
x[j?'strokeText':'fillText'](s[i],X2,Y+.5,17*p);
|
|
642
|
+
X += w;
|
|
643
|
+
}
|
|
624
644
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
// cab top
|
|
630
|
-
rect(7,16,18,-8,color(2,2));
|
|
631
|
-
rect(7,8,18,4,color(2,3));
|
|
632
|
-
rect(25,8,8,8,color(2,1));
|
|
633
|
-
rect(25,8,-18,8);
|
|
634
|
-
rect(25,8,8,8);
|
|
635
|
-
|
|
636
|
-
// cab
|
|
637
|
-
rect(25,16,7,24,color());
|
|
638
|
-
rect(11,39,14,-23,color(1,1));
|
|
639
|
-
rect(11,16,14,18,color(1,2));
|
|
640
|
-
rect(11,16,14,8,color(1,3));
|
|
641
|
-
rect(25,16,-14,24);
|
|
642
|
-
|
|
643
|
-
// cab window
|
|
644
|
-
rect(15,29,6,-9,color(2,2));
|
|
645
|
-
circle(15,21,5,0,PI/2,color(2,4),1);
|
|
646
|
-
rect(21,21,-6,9);
|
|
647
|
-
|
|
648
|
-
// little stack
|
|
649
|
-
rect(37,14,9,6,color(3,2));
|
|
650
|
-
rect(37,14,4.5,6,color(3,3));
|
|
651
|
-
rect(37,14,9,6);
|
|
645
|
+
x.lineWidth = .1 + p*1.9;
|
|
646
|
+
rect(3,Y,73,0); // bottom
|
|
647
|
+
}
|
|
652
648
|
|
|
649
|
+
rect(7,15,26,-7,0); // cab top
|
|
650
|
+
rect(25,15,8,25,-1); // cab front
|
|
651
|
+
rect(10,40,15,-25,1); // cab back
|
|
652
|
+
rect(14,21,7,9,2); // cab window
|
|
653
|
+
rect(38,20,6,-6,2); // little stack
|
|
654
|
+
|
|
653
655
|
// big stack
|
|
654
|
-
rect(
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
rect(
|
|
658
|
-
circle(55,2,11.4,.5,PI-.5,color(3,3));
|
|
659
|
-
circle(55,2,11.4,.5,PI/2,color(3,2),1);
|
|
660
|
-
circle(55,2,11.4,.5,PI-.5);
|
|
661
|
-
rect(45,7,20,-7,color(0,2));
|
|
662
|
-
rect(45,-1,20,4,color(0,3));
|
|
663
|
-
rect(45,-1,20,8);
|
|
656
|
+
rect(49,20,10,-6,0);
|
|
657
|
+
const stackPoints = [vec2(44,8),vec2(64,8),vec2(59,8+6*p),vec2(49,8+6*p)];
|
|
658
|
+
poly(stackPoints,2,8,6*p);
|
|
659
|
+
rect(44,8,20,-7,0);
|
|
664
660
|
|
|
665
661
|
// engine
|
|
666
|
-
for (let i=5;
|
|
667
|
-
|
|
668
|
-
// stagger radius to fix slight seam
|
|
669
|
-
circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
|
|
670
|
-
circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
|
|
671
|
-
circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
|
|
672
|
-
}
|
|
662
|
+
for (let i=5;i--;) circle(59-i*6*p,30,10,0,2*PI,1,0);
|
|
663
|
+
circle(59,30,4,0,7,2); // light
|
|
673
664
|
|
|
674
665
|
// engine outline
|
|
675
|
-
|
|
676
|
-
circle(
|
|
677
|
-
circle(
|
|
678
|
-
|
|
666
|
+
rect(35,20,24,0); // top
|
|
667
|
+
circle(59,30,10); // front
|
|
668
|
+
circle(47,30,10,PI/2,PI*3/2); // middle
|
|
669
|
+
circle(35,30,10,PI/2,PI*3/2); // back
|
|
670
|
+
rect(7,40,13,7,-1); // bottom back
|
|
671
|
+
rect(17,40,43,14,-1); // bottom center
|
|
679
672
|
|
|
680
|
-
//
|
|
681
|
-
circle(
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
// front brush
|
|
686
|
-
for (let i=6; i--;)
|
|
673
|
+
// wheels
|
|
674
|
+
for (let i=3;i--;) for (let j=2;j--;) circle(17+15*i,47,j?7:1,0,2*PI,2);
|
|
675
|
+
|
|
676
|
+
// cowcatcher
|
|
677
|
+
for (let i=2;i--;)
|
|
687
678
|
{
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
x.lineTo(53+(1+i*2.9)*p,40);
|
|
692
|
-
x.lineTo(53+(4+i*3.5)*p,54);
|
|
693
|
-
x.fillStyle = color(0,i%2+2);
|
|
694
|
-
x.fill();
|
|
695
|
-
i%2 && x.stroke();
|
|
679
|
+
let w=6, s=7, o=53+w*p*i
|
|
680
|
+
const points = [vec2(o+s,54),vec2(o,40),vec2(o+w*p,40),vec2(o+s+w*p,54)];
|
|
681
|
+
poly(points,0,40,14);
|
|
696
682
|
}
|
|
697
683
|
|
|
698
|
-
// wheels
|
|
699
|
-
rect(6,40,5,5);
|
|
700
|
-
rect(6,40,5,5,color());
|
|
701
|
-
rect(15,54,38,-14,color());
|
|
702
|
-
for (let i=3; i--;)
|
|
703
|
-
for (let j=2; j--;)
|
|
704
|
-
{
|
|
705
|
-
circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
|
|
706
|
-
x.stroke();
|
|
707
|
-
circle(15*i+15,47,j?7:1,0,PI,color(i,2));
|
|
708
|
-
x.stroke();
|
|
709
|
-
}
|
|
710
|
-
line(6,40,68,40); // center
|
|
711
|
-
line(77,54,4,54); // bottom
|
|
712
|
-
|
|
713
|
-
// draw engine name
|
|
714
|
-
const s = engineName;
|
|
715
|
-
x.font = '900 16px arial';
|
|
716
|
-
x.textAlign = 'center';
|
|
717
|
-
x.textBaseline = 'top';
|
|
718
|
-
x.lineWidth = .1+p*3.9;
|
|
719
|
-
let w2 = 0;
|
|
720
|
-
for (let i=0; i<s.length; ++i)
|
|
721
|
-
w2 += x.measureText(s[i]).width;
|
|
722
|
-
for (let j=2; j--;)
|
|
723
|
-
for (let i=0, X=41-w2/2; i<s.length; ++i)
|
|
724
|
-
{
|
|
725
|
-
x.fillStyle = color(i,2);
|
|
726
|
-
const w = x.measureText(s[i]).width;
|
|
727
|
-
x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
|
|
728
|
-
X += w;
|
|
729
|
-
}
|
|
730
684
|
x.restore();
|
|
731
685
|
}
|
|
732
686
|
/**
|
|
@@ -887,7 +841,7 @@ function debugPoint(pos, color, time, angle, screenSpace=false)
|
|
|
887
841
|
function debugLine(posA, posB, color, width=.1, time, screenSpace=false)
|
|
888
842
|
{
|
|
889
843
|
ASSERT(isVector2(posA), 'posA must be a vec2');
|
|
890
|
-
ASSERT(isVector2(posB), 'posB must be
|
|
844
|
+
ASSERT(isVector2(posB), 'posB must be a vec2');
|
|
891
845
|
ASSERT(isNumber(width), 'width must be a number');
|
|
892
846
|
|
|
893
847
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
@@ -1715,7 +1669,9 @@ function wave(frequency=1, amplitude=1, t=time, offset=0)
|
|
|
1715
1669
|
function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
1716
1670
|
|
|
1717
1671
|
/**
|
|
1718
|
-
* Check if object
|
|
1672
|
+
* Check if object can be converted to a string (has a toString method)
|
|
1673
|
+
* - Returns true for strings, numbers, and most objects
|
|
1674
|
+
* - Returns false for null and undefined
|
|
1719
1675
|
* @param {any} s
|
|
1720
1676
|
* @return {boolean}
|
|
1721
1677
|
* @memberof Math */
|
|
@@ -1792,7 +1748,7 @@ function lineTest(posStart, posEnd, testFunction, normal)
|
|
|
1792
1748
|
if (stepX < 0)
|
|
1793
1749
|
hitPos.x -= e;
|
|
1794
1750
|
}
|
|
1795
|
-
if (stepY < 0)
|
|
1751
|
+
else if (stepY < 0)
|
|
1796
1752
|
hitPos.y -= e;
|
|
1797
1753
|
|
|
1798
1754
|
// set normal
|
|
@@ -2499,7 +2455,7 @@ class Color
|
|
|
2499
2455
|
* @memberof Math */
|
|
2500
2456
|
const WHITE = debugProtectConstant(rgb());
|
|
2501
2457
|
|
|
2502
|
-
/** Color - Clear White #
|
|
2458
|
+
/** Color - Clear White #ffffff00 with 0 alpha
|
|
2503
2459
|
* @type {Color}
|
|
2504
2460
|
* @memberof Math */
|
|
2505
2461
|
const CLEAR_WHITE = debugProtectConstant(rgb(1,1,1,0));
|
|
@@ -2509,7 +2465,7 @@ const CLEAR_WHITE = debugProtectConstant(rgb(1,1,1,0));
|
|
|
2509
2465
|
* @memberof Math */
|
|
2510
2466
|
const BLACK = debugProtectConstant(rgb(0,0,0));
|
|
2511
2467
|
|
|
2512
|
-
/** Color - Clear Black #
|
|
2468
|
+
/** Color - Clear Black #00000000 with 0 alpha
|
|
2513
2469
|
* @type {Color}
|
|
2514
2470
|
* @memberof Math */
|
|
2515
2471
|
const CLEAR_BLACK = debugProtectConstant(rgb(0,0,0,0));
|
|
@@ -2565,6 +2521,90 @@ const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
|
2565
2521
|
* @namespace Utilities
|
|
2566
2522
|
*/
|
|
2567
2523
|
|
|
2524
|
+
/**
|
|
2525
|
+
* Timer object tracks how long has passed since it was set
|
|
2526
|
+
* @memberof Engine
|
|
2527
|
+
* @example
|
|
2528
|
+
* let a = new Timer; // creates a timer that is not set
|
|
2529
|
+
* a.set(3); // sets the timer to 3 seconds
|
|
2530
|
+
*
|
|
2531
|
+
* let b = new Timer(1); // creates a timer with 1 second left
|
|
2532
|
+
* b.unset(); // unset the timer
|
|
2533
|
+
*/
|
|
2534
|
+
class Timer
|
|
2535
|
+
{
|
|
2536
|
+
/** Create a timer object set time passed in
|
|
2537
|
+
* @param {number} [timeLeft] - How much time left before the timer
|
|
2538
|
+
* @param {boolean} [useRealTime] - Should the timer keep running even when the game is paused? (useful for UI) */
|
|
2539
|
+
constructor(timeLeft, useRealTime=false)
|
|
2540
|
+
{
|
|
2541
|
+
ASSERT(timeLeft === undefined || isNumber(timeLeft), 'Constructed Timer is invalid.', timeLeft);
|
|
2542
|
+
this.useRealTime = useRealTime;
|
|
2543
|
+
const globalTime = this.getGlobalTime();
|
|
2544
|
+
this.time = timeLeft === undefined ? undefined : globalTime + timeLeft;
|
|
2545
|
+
this.setTime = timeLeft;
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
/** Set the timer with seconds passed in
|
|
2549
|
+
* @param {number} [timeLeft] - How much time left before the timer is elapsed in seconds */
|
|
2550
|
+
set(timeLeft=0)
|
|
2551
|
+
{
|
|
2552
|
+
ASSERT(isNumber(timeLeft), 'Timer is invalid.', timeLeft);
|
|
2553
|
+
const globalTime = this.getGlobalTime();
|
|
2554
|
+
this.time = globalTime + timeLeft;
|
|
2555
|
+
this.setTime = timeLeft;
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
/** Set if the timer should keep running even when the game is paused
|
|
2559
|
+
* @param {boolean} [useRealTime] */
|
|
2560
|
+
setUseRealTime(useRealTime=true)
|
|
2561
|
+
{
|
|
2562
|
+
ASSERT(!this.isSet(), 'Cannot change global time setting while timer is set.');
|
|
2563
|
+
this.useRealTime = useRealTime;
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
/** Unset the timer */
|
|
2567
|
+
unset() { this.time = undefined; }
|
|
2568
|
+
|
|
2569
|
+
/** Returns true if set
|
|
2570
|
+
* @return {boolean} */
|
|
2571
|
+
isSet() { return this.time !== undefined; }
|
|
2572
|
+
|
|
2573
|
+
/** Returns true if set and has not elapsed
|
|
2574
|
+
* @return {boolean} */
|
|
2575
|
+
active() { return this.getGlobalTime() < this.time; }
|
|
2576
|
+
|
|
2577
|
+
/** Returns true if set and elapsed
|
|
2578
|
+
* @return {boolean} */
|
|
2579
|
+
elapsed() { return this.getGlobalTime() >= this.time; }
|
|
2580
|
+
|
|
2581
|
+
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
2582
|
+
* @return {number} */
|
|
2583
|
+
get() { return this.isSet()? this.getGlobalTime() - this.time : 0; }
|
|
2584
|
+
|
|
2585
|
+
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
2586
|
+
* @return {number} */
|
|
2587
|
+
getPercent() { return this.isSet()? 1-percent(this.time - this.getGlobalTime(), 0, this.setTime) : 0; }
|
|
2588
|
+
|
|
2589
|
+
/** Get the time this timer was set to, returns 0 if not set
|
|
2590
|
+
* @return {number} */
|
|
2591
|
+
getSetTime() { return this.isSet() ? this.setTime : 0; }
|
|
2592
|
+
|
|
2593
|
+
/** Get the current global time this timer is based on
|
|
2594
|
+
* @return {number} */
|
|
2595
|
+
getGlobalTime() { return this.useRealTime ? timeReal : time; }
|
|
2596
|
+
|
|
2597
|
+
/** Returns this timer expressed as a string
|
|
2598
|
+
* @return {string} */
|
|
2599
|
+
toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
|
|
2600
|
+
|
|
2601
|
+
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
2602
|
+
* @return {number} */
|
|
2603
|
+
valueOf() { return this.get(); }
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2607
|
+
|
|
2568
2608
|
/** Formats seconds to mm:ss style for display purposes
|
|
2569
2609
|
* @param {number} t - time in seconds
|
|
2570
2610
|
* @return {string}
|
|
@@ -2651,86 +2691,29 @@ function shareURL(title, url, callback)
|
|
|
2651
2691
|
|
|
2652
2692
|
///////////////////////////////////////////////////////////////////////////////
|
|
2653
2693
|
|
|
2654
|
-
/**
|
|
2655
|
-
*
|
|
2656
|
-
*
|
|
2657
|
-
*
|
|
2658
|
-
*
|
|
2659
|
-
|
|
2660
|
-
*
|
|
2661
|
-
* let b = new Timer(1); // creates a timer with 1 second left
|
|
2662
|
-
* b.unset(); // unset the timer
|
|
2663
|
-
*/
|
|
2664
|
-
class Timer
|
|
2694
|
+
/** Read save data from local storage
|
|
2695
|
+
* @param {string} saveName - unique name for the game/save
|
|
2696
|
+
* @param {Object} [defaultSaveData] - default values for save
|
|
2697
|
+
* @return {Object}
|
|
2698
|
+
* @memberof Utilities */
|
|
2699
|
+
function readSaveData(saveName, defaultSaveData)
|
|
2665
2700
|
{
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
{
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
const globalTime = this.getGlobalTime();
|
|
2674
|
-
this.time = timeLeft === undefined ? undefined : globalTime + timeLeft;
|
|
2675
|
-
this.setTime = timeLeft;
|
|
2676
|
-
}
|
|
2677
|
-
|
|
2678
|
-
/** Set the timer with seconds passed in
|
|
2679
|
-
* @param {number} [timeLeft] - How much time left before the timer is elapsed in seconds */
|
|
2680
|
-
set(timeLeft=0)
|
|
2681
|
-
{
|
|
2682
|
-
ASSERT(isNumber(timeLeft), 'Timer is invalid.', timeLeft);
|
|
2683
|
-
const globalTime = this.getGlobalTime();
|
|
2684
|
-
this.time = globalTime + timeLeft;
|
|
2685
|
-
this.setTime = timeLeft;
|
|
2686
|
-
}
|
|
2687
|
-
|
|
2688
|
-
/** Set if the timer should keep running even when the game is paused
|
|
2689
|
-
* @param {boolean} [useRealTime] */
|
|
2690
|
-
setUseRealTime(useRealTime=true)
|
|
2691
|
-
{
|
|
2692
|
-
ASSERT(!this.isSet(), 'Cannot change global time setting while timer is set.');
|
|
2693
|
-
this.useRealTime = useRealTime;
|
|
2694
|
-
}
|
|
2695
|
-
|
|
2696
|
-
/** Unset the timer */
|
|
2697
|
-
unset() { this.time = undefined; }
|
|
2698
|
-
|
|
2699
|
-
/** Returns true if set
|
|
2700
|
-
* @return {boolean} */
|
|
2701
|
-
isSet() { return this.time !== undefined; }
|
|
2702
|
-
|
|
2703
|
-
/** Returns true if set and has not elapsed
|
|
2704
|
-
* @return {boolean} */
|
|
2705
|
-
active() { return this.getGlobalTime() < this.time; }
|
|
2706
|
-
|
|
2707
|
-
/** Returns true if set and elapsed
|
|
2708
|
-
* @return {boolean} */
|
|
2709
|
-
elapsed() { return this.getGlobalTime() >= this.time; }
|
|
2710
|
-
|
|
2711
|
-
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
2712
|
-
* @return {number} */
|
|
2713
|
-
get() { return this.isSet()? this.getGlobalTime() - this.time : 0; }
|
|
2714
|
-
|
|
2715
|
-
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
2716
|
-
* @return {number} */
|
|
2717
|
-
getPercent() { return this.isSet()? 1-percent(this.time - this.getGlobalTime(), 0, this.setTime) : 0; }
|
|
2718
|
-
|
|
2719
|
-
/** Get the time this timer was set to, returns 0 if not set
|
|
2720
|
-
* @return {number} */
|
|
2721
|
-
getSetTime() { return this.isSet() ? this.setTime : 0; }
|
|
2722
|
-
|
|
2723
|
-
/** Get the current global time this timer is based on
|
|
2724
|
-
* @return {number} */
|
|
2725
|
-
getGlobalTime() { return this.useRealTime ? timeReal : time; }
|
|
2726
|
-
|
|
2727
|
-
/** Returns this timer expressed as a string
|
|
2728
|
-
* @return {string} */
|
|
2729
|
-
toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
|
|
2701
|
+
ASSERT(isString(saveName), 'loadData requires saveName string');
|
|
2702
|
+
|
|
2703
|
+
// replace undefined values with defaults
|
|
2704
|
+
const data = localStorage[saveName];
|
|
2705
|
+
const loadedData = data ? JSON.parse(data) : {};
|
|
2706
|
+
return { ...defaultSaveData, ...loadedData };
|
|
2707
|
+
}
|
|
2730
2708
|
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2709
|
+
/** Write save data to local storage
|
|
2710
|
+
* @param {string} saveName - unique name for the game/save
|
|
2711
|
+
* @param {Object} saveData - object containing data to be saved
|
|
2712
|
+
* @memberof Utilities */
|
|
2713
|
+
function writeSaveData(saveName, saveData)
|
|
2714
|
+
{
|
|
2715
|
+
ASSERT(isString(saveName), 'saveData requires saveName string');
|
|
2716
|
+
localStorage[saveName] = JSON.stringify(saveData);
|
|
2734
2717
|
}
|
|
2735
2718
|
/**
|
|
2736
2719
|
* LittleJS Engine Settings
|
|
@@ -3391,17 +3374,19 @@ class EngineObject
|
|
|
3391
3374
|
this.additiveColor = undefined;
|
|
3392
3375
|
/** @property {boolean} - Should it flip along y axis when rendered */
|
|
3393
3376
|
this.mirror = false;
|
|
3377
|
+
/** @property {boolean} - Has object been destroyed? */
|
|
3378
|
+
this.destroyed = false;
|
|
3394
3379
|
|
|
3395
3380
|
// physical properties
|
|
3396
|
-
/** @property {number}
|
|
3381
|
+
/** @property {number} - How heavy the object is, static if 0 */
|
|
3397
3382
|
this.mass = objectDefaultMass;
|
|
3398
|
-
/** @property {number}
|
|
3383
|
+
/** @property {number} - How much to slow down velocity each frame (0-1) */
|
|
3399
3384
|
this.damping = objectDefaultDamping;
|
|
3400
|
-
/** @property {number}
|
|
3385
|
+
/** @property {number} - How much to slow down rotation each frame (0-1) */
|
|
3401
3386
|
this.angleDamping = objectDefaultAngleDamping;
|
|
3402
|
-
/** @property {number}
|
|
3387
|
+
/** @property {number} - How bouncy the object is when colliding (0-1) */
|
|
3403
3388
|
this.restitution = objectDefaultRestitution;
|
|
3404
|
-
/** @property {number}
|
|
3389
|
+
/** @property {number} - How much friction to apply when sliding (0-1) */
|
|
3405
3390
|
this.friction = objectDefaultFriction;
|
|
3406
3391
|
/** @property {number} - How much to scale gravity by for this object */
|
|
3407
3392
|
this.gravityScale = 1;
|
|
@@ -3610,10 +3595,10 @@ class EngineObject
|
|
|
3610
3595
|
if (!tileCollisionTest(oldPos, this.size, this))
|
|
3611
3596
|
{
|
|
3612
3597
|
// test which side we bounced off (or both if a corner)
|
|
3613
|
-
const
|
|
3614
|
-
const
|
|
3615
|
-
|
|
3616
|
-
if (
|
|
3598
|
+
const isBlockedX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
|
|
3599
|
+
const isBlockedY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
|
|
3600
|
+
const restitution = max(this.restitution, hitLayer.restitution);
|
|
3601
|
+
if (isBlockedX)
|
|
3617
3602
|
{
|
|
3618
3603
|
// try to move up a tiny bit
|
|
3619
3604
|
const epsilon = 1e-3;
|
|
@@ -3629,16 +3614,12 @@ class EngineObject
|
|
|
3629
3614
|
return;
|
|
3630
3615
|
}
|
|
3631
3616
|
|
|
3632
|
-
// move to previous position and bounce
|
|
3617
|
+
// move to previous X position and bounce
|
|
3633
3618
|
this.pos.x = oldPos.x;
|
|
3634
|
-
this.velocity.x *= -
|
|
3619
|
+
this.velocity.x *= -restitution;
|
|
3635
3620
|
}
|
|
3636
|
-
if (
|
|
3621
|
+
if (isBlockedY || !isBlockedX)
|
|
3637
3622
|
{
|
|
3638
|
-
// bounce velocity
|
|
3639
|
-
const restitution = max(this.restitution, hitLayer.restitution);
|
|
3640
|
-
this.velocity.y *= -restitution;
|
|
3641
|
-
|
|
3642
3623
|
if (wasFalling)
|
|
3643
3624
|
{
|
|
3644
3625
|
// adjust position to slightly away from nearest tile
|
|
@@ -3654,10 +3635,12 @@ class EngineObject
|
|
|
3654
3635
|
}
|
|
3655
3636
|
else
|
|
3656
3637
|
{
|
|
3657
|
-
// move to previous position
|
|
3638
|
+
// move to previous Y position
|
|
3658
3639
|
this.pos.y = oldPos.y;
|
|
3659
3640
|
this.groundObject = undefined;
|
|
3660
3641
|
}
|
|
3642
|
+
// bounce velocity
|
|
3643
|
+
this.velocity.y *= -restitution;
|
|
3661
3644
|
}
|
|
3662
3645
|
debugPhysics && debugRect(this.pos, this.size, '#f00');
|
|
3663
3646
|
}
|
|
@@ -3675,19 +3658,19 @@ class EngineObject
|
|
|
3675
3658
|
drawTile(this.pos, this.drawSize || this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
|
|
3676
3659
|
}
|
|
3677
3660
|
|
|
3678
|
-
/** Destroy this object, destroy its children, detach its parent, and mark it for removal
|
|
3679
|
-
|
|
3661
|
+
/** Destroy this object, destroy its children, detach its parent, and mark it for removal
|
|
3662
|
+
* @param {boolean} [immediate] - should attached effects be allowed to die off? */
|
|
3663
|
+
destroy(immediate=false)
|
|
3680
3664
|
{
|
|
3681
|
-
if (this.destroyed)
|
|
3682
|
-
return;
|
|
3665
|
+
if (this.destroyed) return;
|
|
3683
3666
|
|
|
3684
3667
|
// disconnect from parent and destroy children
|
|
3685
|
-
this.destroyed =
|
|
3668
|
+
this.destroyed = true;
|
|
3686
3669
|
this.parent?.removeChild(this);
|
|
3687
3670
|
for (const child of this.children)
|
|
3688
3671
|
{
|
|
3689
3672
|
child.parent = undefined;
|
|
3690
|
-
child.destroy();
|
|
3673
|
+
child.destroy(immediate);
|
|
3691
3674
|
}
|
|
3692
3675
|
}
|
|
3693
3676
|
|
|
@@ -3786,7 +3769,7 @@ class EngineObject
|
|
|
3786
3769
|
}
|
|
3787
3770
|
|
|
3788
3771
|
/** Check if overlapping another engine object
|
|
3789
|
-
* Collisions are
|
|
3772
|
+
* Collisions are resolved to prevent overlaps
|
|
3790
3773
|
* @param {EngineObject} object
|
|
3791
3774
|
* @return {boolean} */
|
|
3792
3775
|
isOverlappingObject(object)
|
|
@@ -4038,7 +4021,7 @@ class TileInfo
|
|
|
4038
4021
|
}
|
|
4039
4022
|
|
|
4040
4023
|
/**
|
|
4041
|
-
* Returns a tile info for an index using this tile as
|
|
4024
|
+
* Returns a tile info for an index using this tile as reference
|
|
4042
4025
|
* @param {Vector2|number} [index=0]
|
|
4043
4026
|
* @return {TileInfo}
|
|
4044
4027
|
*/
|
|
@@ -4577,6 +4560,31 @@ function drawTextScreen(text, pos, size, color=WHITE, lineWidth=0, lineColor=BLA
|
|
|
4577
4560
|
///////////////////////////////////////////////////////////////////////////////
|
|
4578
4561
|
// Drawing utilities
|
|
4579
4562
|
|
|
4563
|
+
/** Load a texture at a specific index
|
|
4564
|
+
* @param {number} textureIndex - Index to store the texture at
|
|
4565
|
+
* @param {string} [src] - Image source path
|
|
4566
|
+
* @return {Promise} Promise that resolves when texture is loaded
|
|
4567
|
+
* @memberof Draw */
|
|
4568
|
+
async function loadTexture(textureIndex, src)
|
|
4569
|
+
{
|
|
4570
|
+
ASSERT(isNumber(textureIndex), 'textureIndex must be a number');
|
|
4571
|
+
ASSERT(!textureInfos[textureIndex], 'textureIndex is already loaded!');
|
|
4572
|
+
ASSERT(!src || isString(src), 'image src must be a string');
|
|
4573
|
+
|
|
4574
|
+
const image = new Image;
|
|
4575
|
+
if (src)
|
|
4576
|
+
{
|
|
4577
|
+
await new Promise(resolve =>
|
|
4578
|
+
{
|
|
4579
|
+
image.onerror = image.onload = resolve;
|
|
4580
|
+
image.crossOrigin = 'anonymous';
|
|
4581
|
+
image.src = src;
|
|
4582
|
+
});
|
|
4583
|
+
}
|
|
4584
|
+
|
|
4585
|
+
textureInfos[textureIndex] = new TextureInfo(image);
|
|
4586
|
+
}
|
|
4587
|
+
|
|
4580
4588
|
/** Convert from screen to world space coordinates
|
|
4581
4589
|
* @param {Vector2} screenPos
|
|
4582
4590
|
* @return {Vector2}
|
|
@@ -4868,7 +4876,7 @@ let engineFontImage;
|
|
|
4868
4876
|
class FontImage
|
|
4869
4877
|
{
|
|
4870
4878
|
/** Create an image font
|
|
4871
|
-
* @param {TileInfo} tileInfo - Tile info of first
|
|
4879
|
+
* @param {TileInfo} tileInfo - Tile info of first character in font
|
|
4872
4880
|
*/
|
|
4873
4881
|
constructor(tileInfo)
|
|
4874
4882
|
{
|
|
@@ -4957,23 +4965,20 @@ class FontImage
|
|
|
4957
4965
|
}
|
|
4958
4966
|
|
|
4959
4967
|
// load engine font, called automatically on startup
|
|
4960
|
-
function fontImageInit()
|
|
4968
|
+
async function fontImageInit()
|
|
4961
4969
|
{
|
|
4962
|
-
|
|
4970
|
+
const image = new Image;
|
|
4971
|
+
await new Promise(resolve =>
|
|
4963
4972
|
{
|
|
4964
|
-
|
|
4965
|
-
const image = new Image;
|
|
4966
|
-
image.onerror = image.onload = ()=>
|
|
4967
|
-
{
|
|
4968
|
-
const tilePos=vec2(), tileSize=vec2(8), padding=1, bleed=0;
|
|
4969
|
-
const textureInfo = new TextureInfo(image);
|
|
4970
|
-
const tileInfo = new TileInfo(tilePos, tileSize, textureInfo, padding, bleed);
|
|
4971
|
-
engineFontImage = new FontImage(tileInfo);
|
|
4972
|
-
resolve();
|
|
4973
|
-
}
|
|
4973
|
+
image.onerror = image.onload = resolve;
|
|
4974
4974
|
image.crossOrigin = 'anonymous';
|
|
4975
4975
|
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAeAQMAAABnrVXaAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAjpJREFUOMu9kzFu2zAUhn+CAROgqrk+B2l0BWYxMjlXeYaAtFtbdA1sGgHqRQfI0CNkSG5AwYB0BQ8d5Bsomwah6CPVeGg6tEPzAxLwyI+P78cP4u9lNO9OoMKnLMOobG5020/yaj/MrRcCGh1gBbyiLTPJEYaIiom5KM9Jq7KgynMGtb6L4GL4MF2H4LQKCXTvDVw2I4MsgZT7QLExdiutH+D08VOP3INXRrWX1/mmpbkNgAPYRVANb4xpcegYvhiNbIXauQICEjBuYLfMakaakWQeXxiZ0VDtuJCKs3ztMV59QtsHJNcRxDzfdL21ty3PrfIcXTN+E+GFAv6T5nbT9jd50/WFxb5ksdAv49qS6ouymG66ji08UMT6moykYLAo+V0j23GN4m829ZySAD5K7QsBfQTvOG8eE+gTeGYRAmnNAubN3hf5Zv9tJWDHp/VTuaSm7SN4fyINQqaNO3RMVxvpSPXnOChnRNvFcGY0gnwiPswYwTKVPE0zVtX3mTEIOoFzaqLrGuJaV+Uqumb71fVk/VoOH3cdLNQP/FHi8hV0CQNoqBZsUPlLPMsdCJro9QAaQQ0woDy9BJm0eTxCFnO9srcYlhNVlfR2EyTrph1uUtbUtAJifwRgrKuYdXVHeb0YI3QpawohQHkloI3J5FuVwI5ORxC9k2Tuz9Ir1IjgeIPGMHYkAZe2RuYkmWFmt3gGbTPOmBUWVTmRmHtGrfpzG/yuQNOKa6gBB/WA9khitPgl6/GP+gl2Af6tCbvaygAAAABJRU5ErkJggg==';
|
|
4976
4976
|
});
|
|
4977
|
+
|
|
4978
|
+
const tilePos=vec2(), tileSize=vec2(8), padding=1, bleed=0;
|
|
4979
|
+
const textureInfo = new TextureInfo(image);
|
|
4980
|
+
const tileInfo = new TileInfo(tilePos, tileSize, textureInfo, padding, bleed);
|
|
4981
|
+
engineFontImage = new FontImage(tileInfo);
|
|
4977
4982
|
}
|
|
4978
4983
|
/**
|
|
4979
4984
|
* LittleJS Input System
|
|
@@ -5744,7 +5749,7 @@ function inputRender()
|
|
|
5744
5749
|
}
|
|
5745
5750
|
}
|
|
5746
5751
|
|
|
5747
|
-
// center position for right
|
|
5752
|
+
// center position for right touch pad face buttons
|
|
5748
5753
|
function touchGamepadButtonCenter()
|
|
5749
5754
|
{
|
|
5750
5755
|
const center = mainCanvasSize.subtract(vec2(touchGamepadSize));
|
|
@@ -5956,7 +5961,7 @@ class Sound
|
|
|
5956
5961
|
|
|
5957
5962
|
/** Loads a sound from a URL and decodes it into sample data.
|
|
5958
5963
|
* @param {string} filename
|
|
5959
|
-
* @return {Promise
|
|
5964
|
+
* @return {Promise} */
|
|
5960
5965
|
async loadSound(filename)
|
|
5961
5966
|
{
|
|
5962
5967
|
const response = await fetch(filename);
|
|
@@ -6014,7 +6019,7 @@ class Sound
|
|
|
6014
6019
|
* // Control the individual instance
|
|
6015
6020
|
* instance.setVolume(.5);
|
|
6016
6021
|
* instance.pause();
|
|
6017
|
-
* instance.
|
|
6022
|
+
* instance.resume();
|
|
6018
6023
|
* instance.stop();
|
|
6019
6024
|
*/
|
|
6020
6025
|
class SoundInstance
|
|
@@ -6128,7 +6133,7 @@ class SoundInstance
|
|
|
6128
6133
|
this.startTime = undefined;
|
|
6129
6134
|
}
|
|
6130
6135
|
|
|
6131
|
-
/**
|
|
6136
|
+
/** Resume this sound instance */
|
|
6132
6137
|
resume()
|
|
6133
6138
|
{
|
|
6134
6139
|
if (!this.isPaused()) return;
|
|
@@ -6142,8 +6147,8 @@ class SoundInstance
|
|
|
6142
6147
|
*/
|
|
6143
6148
|
isPlaying() { return !!this.source; }
|
|
6144
6149
|
|
|
6145
|
-
/** Check if this instance is paused
|
|
6146
|
-
* @return {boolean} - True if
|
|
6150
|
+
/** Check if this instance is paused or stopped (not currently playing)
|
|
6151
|
+
* @return {boolean} - True if not playing
|
|
6147
6152
|
*/
|
|
6148
6153
|
isPaused() { return !this.isPlaying(); }
|
|
6149
6154
|
|
|
@@ -6445,50 +6450,62 @@ const tileCollisionLayers = [];
|
|
|
6445
6450
|
|
|
6446
6451
|
/** Get tile collision data for a given cell in the grid
|
|
6447
6452
|
* @param {Vector2} pos
|
|
6453
|
+
* @param {boolean} [solidOnly] - Only check solid layers?
|
|
6448
6454
|
* @return {number}
|
|
6449
6455
|
* @memberof TileLayers */
|
|
6450
|
-
function tileCollisionGetData(pos)
|
|
6456
|
+
function tileCollisionGetData(pos, solidOnly=true)
|
|
6451
6457
|
{
|
|
6452
6458
|
// check all tile collision layers
|
|
6453
6459
|
for (const layer of tileCollisionLayers)
|
|
6460
|
+
if (!solidOnly || layer.isSolid)
|
|
6454
6461
|
if (pos.arrayCheck(layer.size))
|
|
6455
|
-
|
|
6462
|
+
{
|
|
6463
|
+
const data = layer.getCollisionData(pos);
|
|
6464
|
+
if (data) return data;
|
|
6465
|
+
}
|
|
6456
6466
|
return 0;
|
|
6457
6467
|
}
|
|
6458
6468
|
|
|
6459
6469
|
/** Check if a tile layer collides with another object
|
|
6460
|
-
* @param {Vector2}
|
|
6461
|
-
* @param {Vector2}
|
|
6462
|
-
* @param {EngineObject} [
|
|
6463
|
-
* @param {boolean}
|
|
6470
|
+
* @param {Vector2} pos
|
|
6471
|
+
* @param {Vector2} [size=vec2()]
|
|
6472
|
+
* @param {EngineObject|TileCollisionCallback} [callbackObject] - Callback, engine object, or undefined
|
|
6473
|
+
* @param {boolean} [solidOnly] - Only check solid layers?
|
|
6464
6474
|
* @return {TileCollisionLayer}
|
|
6465
6475
|
* @memberof TileLayers */
|
|
6466
|
-
function tileCollisionTest(pos, size=vec2(),
|
|
6476
|
+
function tileCollisionTest(pos, size=vec2(), callbackObject, solidOnly=true)
|
|
6467
6477
|
{
|
|
6468
6478
|
for (const layer of tileCollisionLayers)
|
|
6469
6479
|
{
|
|
6470
6480
|
if (!solidOnly || layer.isSolid)
|
|
6471
|
-
if (layer.collisionTest(pos, size,
|
|
6481
|
+
if (layer.collisionTest(pos, size, callbackObject))
|
|
6472
6482
|
return layer;
|
|
6473
6483
|
}
|
|
6474
6484
|
}
|
|
6475
6485
|
|
|
6486
|
+
/**
|
|
6487
|
+
* @callback TileCollisionCallback - Function to handle a tile collision test
|
|
6488
|
+
* @param {number} tileData - the value of the tile at the position
|
|
6489
|
+
* @param {Vector2} pos - world space position of tile where the collision occurred
|
|
6490
|
+
* @memberof TileLayers
|
|
6491
|
+
*/
|
|
6492
|
+
|
|
6476
6493
|
/** Return the exact position of the boundary of first tile hit, undefined if nothing was hit.
|
|
6477
|
-
* The point will be inside the colliding tile if it hits
|
|
6478
|
-
* @param {Vector2}
|
|
6479
|
-
* @param {Vector2}
|
|
6480
|
-
* @param {EngineObject} [
|
|
6481
|
-
* @param {Vector2}
|
|
6482
|
-
* @param {boolean}
|
|
6494
|
+
* The point will be inside the colliding tile if it hits
|
|
6495
|
+
* @param {Vector2} posStart
|
|
6496
|
+
* @param {Vector2} posEnd
|
|
6497
|
+
* @param {EngineObject|TileCollisionCallback} [callbackObject] - Callback, engine object, or undefined
|
|
6498
|
+
* @param {Vector2} [normal] - Optional normal of the surface hit
|
|
6499
|
+
* @param {boolean} [solidOnly=true] - Only check solid layers?
|
|
6483
6500
|
* @return {Vector2|undefined} - position of the center of the tile hit or undefined if no hit
|
|
6484
6501
|
* @memberof TileLayers */
|
|
6485
|
-
function tileCollisionRaycast(posStart, posEnd,
|
|
6502
|
+
function tileCollisionRaycast(posStart, posEnd, callbackObject, normal, solidOnly=true)
|
|
6486
6503
|
{
|
|
6487
6504
|
for (const layer of tileCollisionLayers)
|
|
6488
6505
|
{
|
|
6489
6506
|
if (!solidOnly || layer.isSolid)
|
|
6490
6507
|
{
|
|
6491
|
-
const hitPos = layer.collisionRaycast(posStart, posEnd,
|
|
6508
|
+
const hitPos = layer.collisionRaycast(posStart, posEnd, callbackObject, normal)
|
|
6492
6509
|
if (hitPos) return hitPos;
|
|
6493
6510
|
}
|
|
6494
6511
|
}
|
|
@@ -7052,18 +7069,22 @@ class TileCollisionLayer extends TileLayer
|
|
|
7052
7069
|
/** Check if collision with another object should occur
|
|
7053
7070
|
* @param {Vector2} pos
|
|
7054
7071
|
* @param {Vector2} [size=vec2()]
|
|
7055
|
-
* @param {EngineObject} [object
|
|
7072
|
+
* @param {EngineObject|TileCollisionCallback} [callbackObject] - Callback, engine object, or undefined
|
|
7056
7073
|
* @return {boolean} */
|
|
7057
|
-
collisionTest(pos, size=new Vector2,
|
|
7074
|
+
collisionTest(pos, size=new Vector2, callbackObject)
|
|
7058
7075
|
{
|
|
7059
7076
|
ASSERT(isVector2(pos) && isVector2(size), 'pos and size must be Vector2s');
|
|
7060
|
-
ASSERT(!
|
|
7061
|
-
|
|
7062
|
-
//
|
|
7063
|
-
const
|
|
7064
|
-
|
|
7077
|
+
ASSERT(!callbackObject || typeof callbackObject === 'function' || callbackObject instanceof EngineObject, 'callbackObject must be a function or EngineObject');
|
|
7078
|
+
|
|
7079
|
+
// make function to check for collision
|
|
7080
|
+
const collisionTest = callbackObject ? typeof callbackObject === 'function' ?
|
|
7081
|
+
(tileData, pos)=> callbackObject(tileData, pos) :
|
|
7082
|
+
(tileData, pos)=> callbackObject.collideWithTile(tileData, pos) :
|
|
7083
|
+
()=> true;
|
|
7065
7084
|
|
|
7066
7085
|
// check any tiles in the area for collision
|
|
7086
|
+
const posX = pos.x - this.pos.x;
|
|
7087
|
+
const posY = pos.y - this.pos.y;
|
|
7067
7088
|
const minX = max(posX - size.x/2|0, 0);
|
|
7068
7089
|
const minY = max(posY - size.y/2|0, 0);
|
|
7069
7090
|
const maxX = min(posX + size.x/2, this.size.x);
|
|
@@ -7074,9 +7095,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
7074
7095
|
{
|
|
7075
7096
|
// check if the object should collide with this tile
|
|
7076
7097
|
const tileData = this.collisionData[y*this.size.x+x];
|
|
7077
|
-
if (tileData)
|
|
7078
|
-
if (!object || object.collideWithTile(tileData,
|
|
7079
|
-
hitPos.set(x + this.pos.x, y + this.pos.y)))
|
|
7098
|
+
if (tileData && collisionTest(tileData, hitPos.set(x+this.pos.x, y+this.pos.y)))
|
|
7080
7099
|
return true;
|
|
7081
7100
|
}
|
|
7082
7101
|
return false;
|
|
@@ -7084,52 +7103,68 @@ class TileCollisionLayer extends TileLayer
|
|
|
7084
7103
|
|
|
7085
7104
|
/** Return the exact position of the boundary of first tile hit, undefined if nothing was hit.
|
|
7086
7105
|
* The point will be inside the colliding tile if it hits (may have a tiny shift)
|
|
7087
|
-
* @param {Vector2}
|
|
7088
|
-
* @param {Vector2}
|
|
7089
|
-
* @param {EngineObject} [
|
|
7090
|
-
* @param {Vector2}
|
|
7106
|
+
* @param {Vector2} posStart
|
|
7107
|
+
* @param {Vector2} posEnd
|
|
7108
|
+
* @param {EngineObject|TileCollisionCallback} [callbackObject] - Callback, engine object, or undefined
|
|
7109
|
+
* @param {Vector2} [normal] - Optional normal of the surface hit
|
|
7091
7110
|
* @return {Vector2|undefined} */
|
|
7092
|
-
collisionRaycast(posStart, posEnd,
|
|
7111
|
+
collisionRaycast(posStart, posEnd, callbackObject, normal)
|
|
7093
7112
|
{
|
|
7094
7113
|
ASSERT(isVector2(posStart) && isVector2(posEnd), 'positions must be Vector2s');
|
|
7095
|
-
ASSERT(!
|
|
7114
|
+
ASSERT(!callbackObject || typeof callbackObject === 'function' || callbackObject instanceof EngineObject, 'callbackObject must be a function or EngineObject');
|
|
7096
7115
|
|
|
7097
|
-
|
|
7098
|
-
const collisionTest =
|
|
7116
|
+
// make function to check for collision
|
|
7117
|
+
const collisionTest = callbackObject ? typeof callbackObject === 'function' ?
|
|
7118
|
+
(tileData, pos)=> callbackObject(tileData, pos) :
|
|
7119
|
+
(tileData, pos)=> callbackObject.collideWithTile(tileData, pos) :
|
|
7120
|
+
(tileData)=> tileData > 0;
|
|
7121
|
+
const testFunction = (pos)=>
|
|
7099
7122
|
{
|
|
7100
|
-
|
|
7101
|
-
|
|
7102
|
-
const tileData = this.getCollisionData(localPos);
|
|
7103
|
-
return tileData && (!object || object.collideWithTile(tileData, pos));
|
|
7123
|
+
const tileData = this.getCollisionData(localPos.set(pos.x - this.pos.x, pos.y - this.pos.y));
|
|
7124
|
+
return tileData && collisionTest(tileData, pos);
|
|
7104
7125
|
}
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7126
|
+
|
|
7127
|
+
// use line test against tile collision
|
|
7128
|
+
const localPos = new Vector2;
|
|
7129
|
+
const hitPos = lineTest(posStart, posEnd, testFunction, normal);
|
|
7130
|
+
if (debugRaycast && hitPos)
|
|
7108
7131
|
{
|
|
7109
7132
|
const tilePos = hitPos.floor().add(vec2(.5));
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
return hitPos;
|
|
7133
|
+
debugRect(tilePos, vec2(1), '#f008');
|
|
7134
|
+
debugLine(posStart, posEnd, '#00f', .02);
|
|
7135
|
+
debugLine(posStart, hitPos, '#f00', .02);
|
|
7136
|
+
debugPoint(hitPos, '#0f0');
|
|
7137
|
+
normal && debugLine(hitPos, hitPos.add(normal), '#ff0', .02);
|
|
7116
7138
|
}
|
|
7139
|
+
return hitPos;
|
|
7117
7140
|
}
|
|
7118
7141
|
}
|
|
7119
7142
|
/**
|
|
7120
7143
|
* LittleJS Particle System
|
|
7144
|
+
* - A simple but fast and flexible particle system
|
|
7145
|
+
* - Lightweight Particles are created and managed by ParticleEmitters
|
|
7146
|
+
* - The particle design tool can be used to help create emitters
|
|
7147
|
+
* @namespace Particles
|
|
7121
7148
|
*/
|
|
7122
7149
|
|
|
7123
7150
|
/**
|
|
7124
|
-
* @callback
|
|
7151
|
+
* @callback ParticleCallback - Function that processes a particle
|
|
7125
7152
|
* @param {Particle} particle
|
|
7126
|
-
* @memberof
|
|
7153
|
+
* @memberof Particles
|
|
7154
|
+
*/
|
|
7155
|
+
|
|
7156
|
+
/**
|
|
7157
|
+
* @callback ParticleCollideCallback - Collide callback for particles
|
|
7158
|
+
* @param {Particle} particle
|
|
7159
|
+
* @param {number} tileData
|
|
7160
|
+
* @param {Vector2} pos
|
|
7161
|
+
* @memberof Particles
|
|
7127
7162
|
*/
|
|
7128
7163
|
|
|
7129
7164
|
/**
|
|
7130
7165
|
* Particle Emitter - Spawns particles with the given settings
|
|
7131
7166
|
* @extends EngineObject
|
|
7132
|
-
* @memberof
|
|
7167
|
+
* @memberof Particles
|
|
7133
7168
|
* @example
|
|
7134
7169
|
* // create a particle emitter
|
|
7135
7170
|
* let pos = vec2(2,3);
|
|
@@ -7147,7 +7182,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
7147
7182
|
class ParticleEmitter extends EngineObject
|
|
7148
7183
|
{
|
|
7149
7184
|
/** Create a particle system with the given settings
|
|
7150
|
-
* @param {Vector2}
|
|
7185
|
+
* @param {Vector2} pos - World space position of the emitter
|
|
7151
7186
|
* @param {number} [angle] - Angle to emit the particles
|
|
7152
7187
|
* @param {number|Vector2} [emitSize] - World space size of the emitter (float for circle diameter, vec2 for rect)
|
|
7153
7188
|
* @param {number} [emitTime] - How long to stay alive (0 is forever)
|
|
@@ -7177,7 +7212,7 @@ class ParticleEmitter extends EngineObject
|
|
|
7177
7212
|
*/
|
|
7178
7213
|
constructor
|
|
7179
7214
|
(
|
|
7180
|
-
|
|
7215
|
+
pos,
|
|
7181
7216
|
angle,
|
|
7182
7217
|
emitSize = 0,
|
|
7183
7218
|
emitTime = 0,
|
|
@@ -7206,12 +7241,13 @@ class ParticleEmitter extends EngineObject
|
|
|
7206
7241
|
localSpace = false
|
|
7207
7242
|
)
|
|
7208
7243
|
{
|
|
7209
|
-
super(
|
|
7244
|
+
super(pos, vec2(), tileInfo, angle, undefined, renderOrder);
|
|
7210
7245
|
|
|
7211
7246
|
// emitter settings
|
|
7247
|
+
/** @property {boolean} - Should particles be emitted in a circle */
|
|
7248
|
+
this.emitCircle = typeof emitSize === 'number';
|
|
7212
7249
|
/** @property {number|Vector2} - World space size of the emitter (float for circle diameter, vec2 for rect) */
|
|
7213
|
-
this.emitSize = emitSize
|
|
7214
|
-
emitSize.copy() : emitSize;
|
|
7250
|
+
this.emitSize = typeof emitSize === 'number' ? vec2(emitSize) : emitSize.copy();
|
|
7215
7251
|
/** @property {number} - How long to stay alive (0 is forever) */
|
|
7216
7252
|
this.emitTime = emitTime;
|
|
7217
7253
|
/** @property {number} - How many particles per second to spawn, does not emit if 0 */
|
|
@@ -7262,26 +7298,31 @@ class ParticleEmitter extends EngineObject
|
|
|
7262
7298
|
this.localSpace = localSpace;
|
|
7263
7299
|
/** @property {number} - If non zero the particle is drawn as a trail, stretched in the direction of velocity */
|
|
7264
7300
|
this.trailScale = 0;
|
|
7265
|
-
/** @property {
|
|
7266
|
-
this.particleDestroyCallback = undefined;
|
|
7267
|
-
/** @property {ParticleCallbackFunction} - Callback when particle is created */
|
|
7301
|
+
/** @property {ParticleCallback} - Callback when particle is created */
|
|
7268
7302
|
this.particleCreateCallback = undefined;
|
|
7269
|
-
/** @property {
|
|
7270
|
-
this.
|
|
7303
|
+
/** @property {ParticleCallback} - Callback when particle is destroyed */
|
|
7304
|
+
this.particleDestroyCallback = undefined;
|
|
7305
|
+
/** @property {ParticleCollideCallback} - Callback when particle collides */
|
|
7306
|
+
this.particleCollideCallback = undefined;
|
|
7271
7307
|
/** @property {number} - Percentage of velocity to pass to particles (0-1) */
|
|
7272
7308
|
this.velocityInheritance = 0;
|
|
7309
|
+
/** @property {number} - Track particle emit time */
|
|
7310
|
+
this.emitTimeBuffer = 0;
|
|
7311
|
+
/** @property {Array<Particle>} - Array of particles for this emitter */
|
|
7312
|
+
this.particles = [];
|
|
7273
7313
|
|
|
7274
7314
|
// track previous position and angle
|
|
7275
7315
|
this.previousAngle = this.angle;
|
|
7276
7316
|
this.previousPos = this.pos.copy();
|
|
7277
7317
|
}
|
|
7278
7318
|
|
|
7279
|
-
/** Emitters do not have physics */
|
|
7280
|
-
updatePhysics() {}
|
|
7281
|
-
|
|
7282
7319
|
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
7283
7320
|
update()
|
|
7284
7321
|
{
|
|
7322
|
+
// physics sanity checks
|
|
7323
|
+
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
7324
|
+
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
7325
|
+
|
|
7285
7326
|
if (this.velocityInheritance)
|
|
7286
7327
|
{
|
|
7287
7328
|
// pass emitter velocity to particles
|
|
@@ -7295,7 +7336,7 @@ class ParticleEmitter extends EngineObject
|
|
|
7295
7336
|
}
|
|
7296
7337
|
|
|
7297
7338
|
// update emitter
|
|
7298
|
-
if (
|
|
7339
|
+
if (this.isActive())
|
|
7299
7340
|
{
|
|
7300
7341
|
// emit particles
|
|
7301
7342
|
if (this.emitRate && particleEmitRateScale)
|
|
@@ -7305,14 +7346,21 @@ class ParticleEmitter extends EngineObject
|
|
|
7305
7346
|
this.emitParticle();
|
|
7306
7347
|
}
|
|
7307
7348
|
}
|
|
7308
|
-
else
|
|
7309
|
-
this.destroy();
|
|
7349
|
+
else if (this.particles.length === 0)
|
|
7350
|
+
this.destroy(true);
|
|
7351
|
+
|
|
7352
|
+
// update and remove destroyed particles
|
|
7353
|
+
this.particles = this.particles.filter((p)=>
|
|
7354
|
+
{
|
|
7355
|
+
p.update();
|
|
7356
|
+
return !p.destroyed;
|
|
7357
|
+
});
|
|
7310
7358
|
|
|
7311
7359
|
if (debugParticles)
|
|
7312
7360
|
{
|
|
7313
7361
|
// show emitter bounds
|
|
7314
|
-
if (
|
|
7315
|
-
debugCircle(this.pos, this.emitSize/2, '#0f0');
|
|
7362
|
+
if (this.emitCircle)
|
|
7363
|
+
debugCircle(this.pos, this.emitSize.x/2, '#0f0');
|
|
7316
7364
|
else
|
|
7317
7365
|
debugRect(this.pos, this.emitSize, '#0f0', 0, this.angle);
|
|
7318
7366
|
}
|
|
@@ -7323,14 +7371,15 @@ class ParticleEmitter extends EngineObject
|
|
|
7323
7371
|
emitParticle()
|
|
7324
7372
|
{
|
|
7325
7373
|
// spawn a particle
|
|
7326
|
-
let pos =
|
|
7327
|
-
randInCircle(this.emitSize/2)
|
|
7328
|
-
: vec2(rand(-.5,.5), rand(-.5,.5))
|
|
7374
|
+
let pos = this.emitCircle ? // check if circle emitter
|
|
7375
|
+
randInCircle(this.emitSize.x/2) // circle emitter
|
|
7376
|
+
: vec2(rand(-.5,.5), rand(-.5,.5)) // box emitter
|
|
7329
7377
|
.multiply(this.emitSize).rotate(this.angle)
|
|
7330
7378
|
let angle = rand(this.particleConeAngle, -this.particleConeAngle);
|
|
7331
7379
|
if (!this.localSpace)
|
|
7332
7380
|
{
|
|
7333
|
-
pos
|
|
7381
|
+
pos.x += this.pos.x;
|
|
7382
|
+
pos.y += this.pos.y;
|
|
7334
7383
|
angle += this.angle;
|
|
7335
7384
|
}
|
|
7336
7385
|
|
|
@@ -7350,25 +7399,17 @@ class ParticleEmitter extends EngineObject
|
|
|
7350
7399
|
const velocityAngle = this.localSpace ? coneAngle : this.angle + coneAngle;
|
|
7351
7400
|
|
|
7352
7401
|
// build particle
|
|
7353
|
-
const
|
|
7354
|
-
|
|
7355
|
-
particle.angleVelocity = angleSpeed;
|
|
7402
|
+
const velocity = vec2(speed*sin(velocityAngle), speed*cos(velocityAngle));
|
|
7403
|
+
let angleVelocity = angleSpeed;
|
|
7356
7404
|
if (!this.localSpace && this.velocityInheritance > 0)
|
|
7357
7405
|
{
|
|
7358
7406
|
// apply emitter velocity to particle
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
}
|
|
7363
|
-
particle
|
|
7364
|
-
|
|
7365
|
-
particle.angleDamping = this.angleDamping;
|
|
7366
|
-
particle.restitution = this.restitution;
|
|
7367
|
-
particle.friction = this.friction;
|
|
7368
|
-
particle.gravityScale = this.gravityScale;
|
|
7369
|
-
particle.collideTiles = this.collideTiles;
|
|
7370
|
-
particle.renderOrder = this.renderOrder;
|
|
7371
|
-
particle.mirror = randBool();
|
|
7407
|
+
velocity.x += this.velocity.x;
|
|
7408
|
+
velocity.y += this.velocity.y;
|
|
7409
|
+
angleVelocity += this.angleVelocity;
|
|
7410
|
+
}
|
|
7411
|
+
const particle = new Particle(this, pos, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, velocity, angleVelocity);
|
|
7412
|
+
this.particles.push(particle);
|
|
7372
7413
|
|
|
7373
7414
|
// call particle create callback
|
|
7374
7415
|
this.particleCreateCallback?.(particle);
|
|
@@ -7377,140 +7418,245 @@ class ParticleEmitter extends EngineObject
|
|
|
7377
7418
|
return particle;
|
|
7378
7419
|
}
|
|
7379
7420
|
|
|
7380
|
-
|
|
7381
|
-
|
|
7421
|
+
/** Particle emitters do not have physics */
|
|
7422
|
+
updatePhysics() {}
|
|
7423
|
+
|
|
7424
|
+
/** Render all particles for this emitter */
|
|
7425
|
+
render()
|
|
7426
|
+
{
|
|
7427
|
+
// render all particles
|
|
7428
|
+
for (const particle of this.particles)
|
|
7429
|
+
particle.render();
|
|
7430
|
+
}
|
|
7431
|
+
|
|
7432
|
+
/** is emitter actively spawning */
|
|
7433
|
+
isActive() { return !this.emitTime || this.getAliveTime() < this.emitTime; }
|
|
7434
|
+
|
|
7435
|
+
/** Destroy the particle emitter
|
|
7436
|
+
* @param {boolean} [immediate] - should particle emitters and other attached effects be allowed to die off */
|
|
7437
|
+
destroy(immediate=false)
|
|
7438
|
+
{
|
|
7439
|
+
if (this.destroyed) return;
|
|
7440
|
+
|
|
7441
|
+
super.destroy(immediate);
|
|
7442
|
+
if (!immediate && this.particles.length > 0)
|
|
7443
|
+
{
|
|
7444
|
+
// wait for particles to die off
|
|
7445
|
+
this.destroyed = false;
|
|
7446
|
+
this.emitTime = -1;
|
|
7447
|
+
}
|
|
7448
|
+
}
|
|
7382
7449
|
}
|
|
7383
7450
|
|
|
7384
7451
|
///////////////////////////////////////////////////////////////////////////////
|
|
7385
7452
|
/**
|
|
7386
7453
|
* Particle Object - Created automatically by Particle Emitters
|
|
7387
|
-
* @
|
|
7388
|
-
* @memberof Engine
|
|
7454
|
+
* @memberof Particles
|
|
7389
7455
|
*/
|
|
7390
|
-
class Particle
|
|
7456
|
+
class Particle
|
|
7391
7457
|
{
|
|
7392
7458
|
/**
|
|
7393
7459
|
* Create a particle with the passed in settings
|
|
7394
7460
|
* Typically this is created automatically by a ParticleEmitter
|
|
7395
|
-
* @param {
|
|
7396
|
-
* @param {
|
|
7397
|
-
* @param {number}
|
|
7398
|
-
* @param {Color}
|
|
7399
|
-
* @param {Color}
|
|
7400
|
-
* @param {number}
|
|
7401
|
-
* @param {number}
|
|
7402
|
-
* @param {number}
|
|
7403
|
-
* @param {
|
|
7404
|
-
* @param {
|
|
7405
|
-
* @param {number} trailScale - If a trail, how long to make it
|
|
7406
|
-
* @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
|
|
7407
|
-
* @param {ParticleCallbackFunction} [destroyCallback] - Callback when particle dies
|
|
7461
|
+
* @param {ParticleEmitter} emitter - The emitter that created this particle
|
|
7462
|
+
* @param {Vector2} pos - World or local space position
|
|
7463
|
+
* @param {number} angle - Angle of the particle
|
|
7464
|
+
* @param {Color} colorStart - Color at start of life
|
|
7465
|
+
* @param {Color} colorEnd - Color at end of life
|
|
7466
|
+
* @param {number} lifeTime - How long to live for
|
|
7467
|
+
* @param {number} sizeStart - Size at start of life
|
|
7468
|
+
* @param {number} sizeEnd - Size at end of life
|
|
7469
|
+
* @param {Vector2} [velocity] - Velocity of the particle
|
|
7470
|
+
* @param {number} [angleVelocity] - Angular speed of the particle
|
|
7408
7471
|
*/
|
|
7409
|
-
constructor(
|
|
7410
|
-
)
|
|
7472
|
+
constructor(emitter, pos, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, velocity = vec2(), angleVelocity = 0)
|
|
7411
7473
|
{
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
/** @property {
|
|
7474
|
+
/** @property {ParticleEmitter} */
|
|
7475
|
+
this.emitter = emitter;
|
|
7476
|
+
/** @property {Vector2} */
|
|
7477
|
+
this.pos = pos;
|
|
7478
|
+
/** @property {number} */
|
|
7479
|
+
this.angle = angle;
|
|
7480
|
+
/** @property {Vector2} */
|
|
7481
|
+
this.size = vec2(sizeStart);
|
|
7482
|
+
/** @property {Color} */
|
|
7483
|
+
this.color = colorStart.copy();
|
|
7484
|
+
/** @property {Color} */
|
|
7415
7485
|
this.colorStart = colorStart;
|
|
7416
|
-
/** @property {Color}
|
|
7486
|
+
/** @property {Color} */
|
|
7417
7487
|
this.colorEnd = colorEnd;
|
|
7418
|
-
/** @property {number}
|
|
7488
|
+
/** @property {number} */
|
|
7419
7489
|
this.lifeTime = lifeTime;
|
|
7420
|
-
/** @property {number}
|
|
7490
|
+
/** @property {number} */
|
|
7421
7491
|
this.sizeStart = sizeStart;
|
|
7422
|
-
/** @property {number}
|
|
7492
|
+
/** @property {number} */
|
|
7423
7493
|
this.sizeEnd = sizeEnd;
|
|
7424
|
-
/** @property {
|
|
7425
|
-
this.
|
|
7426
|
-
/** @property {
|
|
7427
|
-
this.
|
|
7428
|
-
/** @property {number}
|
|
7429
|
-
this.
|
|
7430
|
-
/** @property {
|
|
7431
|
-
this.
|
|
7432
|
-
/** @property {
|
|
7433
|
-
this.
|
|
7434
|
-
|
|
7435
|
-
this.
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7494
|
+
/** @property {Vector2} */
|
|
7495
|
+
this.velocity = velocity;
|
|
7496
|
+
/** @property {number} */
|
|
7497
|
+
this.angleVelocity = angleVelocity;
|
|
7498
|
+
/** @property {number} */
|
|
7499
|
+
this.spawnTime = time;
|
|
7500
|
+
/** @property {boolean} */
|
|
7501
|
+
this.mirror = randBool();
|
|
7502
|
+
/** @property {EngineObject} */
|
|
7503
|
+
this.groundObject = undefined;
|
|
7504
|
+
/** @property {boolean} */
|
|
7505
|
+
this.destroyed = false;
|
|
7506
|
+
/** @property {TileInfo} */
|
|
7507
|
+
this.tileInfo = emitter.tileInfo;
|
|
7508
|
+
}
|
|
7509
|
+
|
|
7510
|
+
/** Update the particle */
|
|
7439
7511
|
update()
|
|
7440
7512
|
{
|
|
7441
|
-
|
|
7513
|
+
// emitter properties
|
|
7514
|
+
const emitter = this.emitter;
|
|
7515
|
+
const damping = emitter.damping;
|
|
7516
|
+
const angleDamping = emitter.angleDamping;
|
|
7517
|
+
const restitution = emitter.restitution;
|
|
7518
|
+
const friction = emitter.friction;
|
|
7519
|
+
const gravityScale = emitter.gravityScale;
|
|
7520
|
+
const collideTiles = emitter.collideTiles;
|
|
7521
|
+
const collideCallback = emitter.particleCollideCallback;
|
|
7522
|
+
|
|
7523
|
+
// destroy particle when its time runs out
|
|
7524
|
+
if (this.lifeTime > 0 && time - this.spawnTime > this.lifeTime)
|
|
7442
7525
|
{
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
if (length2 > objectMaxSpeed*objectMaxSpeed)
|
|
7446
|
-
{
|
|
7447
|
-
const s = objectMaxSpeed / length2**.5;
|
|
7448
|
-
this.velocity.x *= s;
|
|
7449
|
-
this.velocity.y *= s;
|
|
7450
|
-
}
|
|
7526
|
+
this.destroy();
|
|
7527
|
+
return;
|
|
7451
7528
|
}
|
|
7452
7529
|
|
|
7453
|
-
|
|
7530
|
+
// apply physics
|
|
7531
|
+
const oldPos = this.pos.copy();
|
|
7532
|
+
this.velocity.x *= damping;
|
|
7533
|
+
this.velocity.y *= damping;
|
|
7534
|
+
this.pos.x += this.velocity.x += gravity.x * gravityScale;
|
|
7535
|
+
this.pos.y += this.velocity.y += gravity.y * gravityScale;
|
|
7536
|
+
this.angle += this.angleVelocity *= angleDamping;
|
|
7537
|
+
|
|
7538
|
+
// don't do collision if solver disabled
|
|
7539
|
+
if (!enablePhysicsSolver || !collideTiles) return;
|
|
7540
|
+
|
|
7541
|
+
// apply max circular speed to prevent going through collision
|
|
7542
|
+
const length2 = this.velocity.lengthSquared();
|
|
7543
|
+
if (length2 > objectMaxSpeed*objectMaxSpeed)
|
|
7454
7544
|
{
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
this.
|
|
7458
|
-
this.size.set(this.sizeEnd, this.sizeEnd);
|
|
7459
|
-
this.destroyCallback?.(this);
|
|
7460
|
-
this.destroyed = 1;
|
|
7545
|
+
const s = objectMaxSpeed / length2**.5;
|
|
7546
|
+
this.velocity.x *= s;
|
|
7547
|
+
this.velocity.y *= s;
|
|
7461
7548
|
}
|
|
7549
|
+
|
|
7550
|
+
// check collision against tiles
|
|
7551
|
+
this.groundObject = undefined;
|
|
7552
|
+
const testCollision = collideCallback ? (pos)=>
|
|
7553
|
+
{
|
|
7554
|
+
const data = tileCollisionGetData(pos);
|
|
7555
|
+
return data && collideCallback(this, data, pos);
|
|
7556
|
+
} : (pos)=> tileCollisionGetData(pos) > 0;
|
|
7557
|
+
|
|
7558
|
+
if (testCollision(this.pos))
|
|
7559
|
+
{
|
|
7560
|
+
// if already was stuck in collision, don't do anything
|
|
7561
|
+
const hitLayer = tileCollisionTest(this.pos);
|
|
7562
|
+
if (!testCollision(oldPos))
|
|
7563
|
+
{
|
|
7564
|
+
if (!collideCallback || collideCallback?.(this, hitLayer))
|
|
7565
|
+
{
|
|
7566
|
+
// test which side we bounced off (or both if a corner)
|
|
7567
|
+
const isBlockedX = testCollision(vec2(this.pos.x, oldPos.y));
|
|
7568
|
+
const isBlockedY = testCollision(vec2(oldPos.x, this.pos.y));
|
|
7569
|
+
const hitRestitution = max(restitution, hitLayer.restitution);
|
|
7570
|
+
const hitFriction = max(friction, hitLayer.friction);
|
|
7571
|
+
if (isBlockedX)
|
|
7572
|
+
{
|
|
7573
|
+
// move to previous X position and bounce
|
|
7574
|
+
this.pos.x = oldPos.x;
|
|
7575
|
+
this.velocity.x *= -hitRestitution;
|
|
7576
|
+
this.velocity.y *= hitFriction;
|
|
7577
|
+
}
|
|
7578
|
+
if (isBlockedY || !isBlockedX)
|
|
7579
|
+
{
|
|
7580
|
+
const wasFalling = this.velocity.y < 0 && gravity.y < 0 || this.velocity.y > 0 && gravity.y > 0;
|
|
7581
|
+
if (wasFalling)
|
|
7582
|
+
this.groundObject = hitLayer;
|
|
7583
|
+
|
|
7584
|
+
// move to previous Y position and bounce
|
|
7585
|
+
this.pos.y = oldPos.y;
|
|
7586
|
+
this.velocity.y *= -hitRestitution;
|
|
7587
|
+
this.velocity.x *= hitFriction;
|
|
7588
|
+
}
|
|
7589
|
+
debugPhysics && debugRect(this.pos, this.size, '#f00');
|
|
7590
|
+
}
|
|
7591
|
+
}
|
|
7592
|
+
}
|
|
7593
|
+
}
|
|
7594
|
+
|
|
7595
|
+
/** Destroy this particle */
|
|
7596
|
+
destroy()
|
|
7597
|
+
{
|
|
7598
|
+
const destroyCallback = this.emitter.particleDestroyCallback;
|
|
7599
|
+
const c = this.colorEnd;
|
|
7600
|
+
this.color.set(c.r, c.g, c.b, c.a);
|
|
7601
|
+
this.size.set(this.sizeEnd, this.sizeEnd);
|
|
7602
|
+
this.destroyed = true;
|
|
7603
|
+
destroyCallback?.(this);
|
|
7462
7604
|
}
|
|
7463
7605
|
|
|
7464
|
-
/** Render the particle, automatically called each frame
|
|
7606
|
+
/** Render the particle, automatically called each frame */
|
|
7465
7607
|
render()
|
|
7466
7608
|
{
|
|
7609
|
+
// emitter properties
|
|
7610
|
+
const emitter = this.emitter;
|
|
7611
|
+
const localSpace = emitter.localSpace;
|
|
7612
|
+
const additive = emitter.additive;
|
|
7613
|
+
const trailScale = emitter.trailScale;
|
|
7614
|
+
const fadeRate = emitter.fadeRate / 2;
|
|
7615
|
+
|
|
7467
7616
|
// lerp color and size
|
|
7468
7617
|
const p1 = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1, p2 = 1-p1;
|
|
7469
7618
|
const radius = p2 * this.sizeStart + p1 * this.sizeEnd;
|
|
7470
7619
|
const size = vec2(radius);
|
|
7620
|
+
const alphaFade = p1 < fadeRate ? p1/fadeRate :
|
|
7621
|
+
p1 > 1-fadeRate ? (1-p1)/fadeRate : 1;
|
|
7471
7622
|
this.color.r = p2 * this.colorStart.r + p1 * this.colorEnd.r;
|
|
7472
7623
|
this.color.g = p2 * this.colorStart.g + p1 * this.colorEnd.g;
|
|
7473
7624
|
this.color.b = p2 * this.colorStart.b + p1 * this.colorEnd.b;
|
|
7474
|
-
this.color.a = p2 * this.colorStart.a + p1 * this.colorEnd.a;
|
|
7475
|
-
|
|
7476
|
-
// fade alpha
|
|
7477
|
-
const fadeRate = this.fadeRate/2;
|
|
7478
|
-
this.color.a *= p1 < fadeRate ? p1/fadeRate :
|
|
7479
|
-
p1 > 1-fadeRate ? (1-p1)/fadeRate : 1;
|
|
7625
|
+
this.color.a = (p2 * this.colorStart.a + p1 * this.colorEnd.a) * alphaFade;
|
|
7480
7626
|
|
|
7481
7627
|
// draw the particle
|
|
7482
|
-
|
|
7628
|
+
additive && setBlendMode(true);
|
|
7483
7629
|
|
|
7484
7630
|
// update the position and angle for drawing
|
|
7485
|
-
|
|
7486
|
-
|
|
7631
|
+
const pos = this.pos.copy();
|
|
7632
|
+
let angle = this.angle;
|
|
7633
|
+
if (localSpace)
|
|
7487
7634
|
{
|
|
7488
7635
|
// in local space of emitter
|
|
7489
|
-
const a =
|
|
7636
|
+
const a = emitter.angle;
|
|
7490
7637
|
const c = cos(a), s = sin(a);
|
|
7491
|
-
pos
|
|
7492
|
-
|
|
7493
|
-
angle +=
|
|
7638
|
+
pos.set(emitter.pos.x + pos.x*c - pos.y*s,
|
|
7639
|
+
emitter.pos.y + pos.x*s + pos.y*c);
|
|
7640
|
+
angle += a;
|
|
7494
7641
|
}
|
|
7495
|
-
if (
|
|
7642
|
+
if (trailScale)
|
|
7496
7643
|
{
|
|
7497
7644
|
// trail style particles
|
|
7498
|
-
const
|
|
7499
|
-
this.velocity.rotate(-
|
|
7500
|
-
|
|
7501
|
-
const speed = direction.length();
|
|
7645
|
+
const velocity = localSpace ?
|
|
7646
|
+
this.velocity.rotate(-emitter.angle) : this.velocity;
|
|
7647
|
+
const speed = velocity.length();
|
|
7502
7648
|
if (speed)
|
|
7503
7649
|
{
|
|
7504
7650
|
// stretch in direction of motion
|
|
7505
|
-
const trailLength = speed *
|
|
7651
|
+
const trailLength = speed * trailScale;
|
|
7506
7652
|
size.y = max(size.x, trailLength);
|
|
7507
|
-
angle = atan2(
|
|
7653
|
+
angle = atan2(velocity.x, velocity.y);
|
|
7508
7654
|
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
7509
7655
|
}
|
|
7510
7656
|
}
|
|
7511
7657
|
else
|
|
7512
7658
|
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
7513
|
-
|
|
7659
|
+
additive && setBlendMode();
|
|
7514
7660
|
debugParticles && debugRect(pos, size, '#f005', 0, angle);
|
|
7515
7661
|
}
|
|
7516
7662
|
}
|
|
@@ -8790,7 +8936,7 @@ class PostProcessPlugin
|
|
|
8790
8936
|
{
|
|
8791
8937
|
/** Create global post processing shader
|
|
8792
8938
|
* @param {string} shaderCode
|
|
8793
|
-
* @param {boolean} [includeMainCanvas] - combine
|
|
8939
|
+
* @param {boolean} [includeMainCanvas] - combine mainCanvas onto glCanvas
|
|
8794
8940
|
* @param {boolean} [feedbackTexture] - use glCanvas from previous frame as the texture
|
|
8795
8941
|
* @example
|
|
8796
8942
|
* // create the post process plugin object
|
|
@@ -9183,7 +9329,7 @@ class UISystemPlugin
|
|
|
9183
9329
|
// navigation properties
|
|
9184
9330
|
/** @property {UIObject} - Object currently selected by navigation (gamepad or keyboard) */
|
|
9185
9331
|
this.navigationObject = undefined;
|
|
9186
|
-
/** @property {Timer} -
|
|
9332
|
+
/** @property {Timer} - Cool down timer for navigation inputs */
|
|
9187
9333
|
this.navigationTimer = new Timer(undefined, true);
|
|
9188
9334
|
/** @property {number} - Time between navigation inputs in seconds */
|
|
9189
9335
|
this.navigationDelay = .2;
|
|
@@ -9649,7 +9795,7 @@ class UISystemPlugin
|
|
|
9649
9795
|
|
|
9650
9796
|
const savedNavigationDirection = uiSystem.navigationDirection;
|
|
9651
9797
|
|
|
9652
|
-
// allow both
|
|
9798
|
+
// allow both axes for navigation
|
|
9653
9799
|
uiSystem.navigationDirection = 2;
|
|
9654
9800
|
|
|
9655
9801
|
// confirm menu
|
|
@@ -9744,7 +9890,7 @@ class UIObject
|
|
|
9744
9890
|
this.lineWidth = uiSystem.defaultLineWidth;
|
|
9745
9891
|
/** @property {number} - Corner radius for rounded rects */
|
|
9746
9892
|
this.cornerRadius = uiSystem.defaultCornerRadius;
|
|
9747
|
-
/** @property {string} - Font for this
|
|
9893
|
+
/** @property {string} - Font for this object */
|
|
9748
9894
|
this.font = uiSystem.defaultFont;
|
|
9749
9895
|
/** @property {string} - Font style for this object or undefined */
|
|
9750
9896
|
this.fontStyle = undefined;
|
|
@@ -10232,6 +10378,8 @@ class UIScrollbar extends UIObject
|
|
|
10232
10378
|
this.value = value;
|
|
10233
10379
|
/** @property {Color} - Color for the handle part of the scrollbar */
|
|
10234
10380
|
this.handleColor = handleColor.copy();
|
|
10381
|
+
/** @property {boolean} - Should it fill up like a progress bar? */
|
|
10382
|
+
this.fillMode = false;
|
|
10235
10383
|
|
|
10236
10384
|
// set properties
|
|
10237
10385
|
this.text = text;
|
|
@@ -10277,19 +10425,30 @@ class UIScrollbar extends UIObject
|
|
|
10277
10425
|
|
|
10278
10426
|
// handle horizontal or vertical scrollbar
|
|
10279
10427
|
const isHorizontal = this.size.x > this.size.y;
|
|
10280
|
-
const
|
|
10281
|
-
const
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10428
|
+
const barWidth = isHorizontal ? this.size.x : this.size.y;
|
|
10429
|
+
const handleWidth = isHorizontal ? this.size.y : this.size.x;
|
|
10430
|
+
if (this.fillMode)
|
|
10431
|
+
{
|
|
10432
|
+
// draw progress bar
|
|
10433
|
+
const minWidth = min(handleWidth, this.cornerRadius * 2);
|
|
10434
|
+
const progressWidth = lerp(minWidth, barWidth, this.value);
|
|
10435
|
+
const p = (progressWidth - barWidth) * (isHorizontal ? .5 : -.5);
|
|
10436
|
+
const pos = this.pos.add(isHorizontal ? vec2(p, 0) : vec2(0, p));
|
|
10437
|
+
const color = this.disabled ? this.disabledColor : this.handleColor;
|
|
10438
|
+
const drawSize = isHorizontal ?
|
|
10439
|
+
vec2(progressWidth, this.size.y) : vec2(this.size.x, progressWidth);
|
|
10440
|
+
uiSystem.drawRect(pos, drawSize, color, this.lineWidth, this.lineColor, this.cornerRadius, this.gradientColor);
|
|
10441
|
+
}
|
|
10442
|
+
else
|
|
10443
|
+
{
|
|
10444
|
+
// draw the scrollbar handle
|
|
10445
|
+
const value = clamp(isHorizontal ? this.value : 1 - this.value);
|
|
10446
|
+
const p = (barWidth - handleWidth) * (value - .5);
|
|
10447
|
+
const pos = this.pos.add(isHorizontal ? vec2(p, 0) : vec2(0, p));
|
|
10448
|
+
const color = this.disabled ? this.disabledColor : this.handleColor;
|
|
10449
|
+
const drawSize = vec2(handleWidth);
|
|
10450
|
+
uiSystem.drawRect(pos, drawSize, color, this.lineWidth, this.lineColor, this.cornerRadius, this.gradientColor);
|
|
10451
|
+
}
|
|
10293
10452
|
|
|
10294
10453
|
// draw the text scaled to fit on the scrollbar
|
|
10295
10454
|
const textSize = this.getTextSize();
|
|
@@ -10311,7 +10470,7 @@ class UIScrollbar extends UIObject
|
|
|
10311
10470
|
* @extends UIObject
|
|
10312
10471
|
* @example
|
|
10313
10472
|
* // Create a video player UI object
|
|
10314
|
-
* const video = new VideoPlayerUIObject(vec2(400, 300), vec2(320, 240), '
|
|
10473
|
+
* const video = new VideoPlayerUIObject(vec2(400, 300), vec2(320, 240), 'video.mp4', true);
|
|
10315
10474
|
* video.play();
|
|
10316
10475
|
* @memberof UISystem
|
|
10317
10476
|
*/
|
|
@@ -10352,12 +10511,11 @@ class UIVideo extends UIObject
|
|
|
10352
10511
|
|
|
10353
10512
|
/** Play or resume the video
|
|
10354
10513
|
* @return {Promise} Promise that resolves when playback starts */
|
|
10355
|
-
play()
|
|
10514
|
+
async play()
|
|
10356
10515
|
{
|
|
10357
10516
|
// try to play the video, catch any errors (autoplay may be blocked)
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
return promise;
|
|
10517
|
+
try { await this.video.play(); }
|
|
10518
|
+
catch(e) {}
|
|
10361
10519
|
}
|
|
10362
10520
|
|
|
10363
10521
|
/** Pause the video */
|
|
@@ -11060,11 +11218,11 @@ class Box2dStaticObject extends Box2dObject
|
|
|
11060
11218
|
|
|
11061
11219
|
///////////////////////////////////////////////////////////////////////////////
|
|
11062
11220
|
/**
|
|
11063
|
-
* Box2D
|
|
11221
|
+
* Box2D Kinematic Object - Box2d with a kinematic physics body
|
|
11064
11222
|
* @extends Box2dObject
|
|
11065
11223
|
* @memberof Box2D
|
|
11066
11224
|
*/
|
|
11067
|
-
class
|
|
11225
|
+
class Box2dKinematicObject extends Box2dObject
|
|
11068
11226
|
{
|
|
11069
11227
|
/** Create a LittleJS object with Box2d physics
|
|
11070
11228
|
* @param {Vector2} [pos]
|
|
@@ -11116,20 +11274,19 @@ class Box2dTileLayer extends Box2dStaticObject
|
|
|
11116
11274
|
this.destroyAllFixtures();
|
|
11117
11275
|
|
|
11118
11276
|
// create box2d object for this layer
|
|
11119
|
-
const size = this.tileLayer.size;
|
|
11120
11277
|
this.pos = this.tileLayer.pos.copy();
|
|
11121
|
-
this.size = size.copy();
|
|
11278
|
+
this.size = this.tileLayer.size.copy();
|
|
11122
11279
|
|
|
11123
11280
|
// track which tiles have been processed
|
|
11124
11281
|
const processed = [];
|
|
11125
|
-
const getIndex = (x, y)=> x + y * size.x;
|
|
11282
|
+
const getIndex = (x, y)=> x + y * this.size.x;
|
|
11126
11283
|
const isSolidUnprocessed = (x, y)=>
|
|
11127
11284
|
!processed[getIndex(x, y)] &&
|
|
11128
11285
|
this.tileLayer.getCollisionData(vec2(x, y)) > 0;
|
|
11129
11286
|
|
|
11130
11287
|
// combine tiles into larger boxes
|
|
11131
|
-
for (let x = 0; x < size.x; ++x)
|
|
11132
|
-
for (let y = 0; y < size.y; ++y)
|
|
11288
|
+
for (let x = 0; x < this.size.x; ++x)
|
|
11289
|
+
for (let y = 0; y < this.size.y; ++y)
|
|
11133
11290
|
{
|
|
11134
11291
|
if (!isSolidUnprocessed(x, y)) continue;
|
|
11135
11292
|
|
|
@@ -12785,6 +12942,8 @@ export
|
|
|
12785
12942
|
saveCanvas,
|
|
12786
12943
|
saveDataURL,
|
|
12787
12944
|
shareURL,
|
|
12945
|
+
readSaveData,
|
|
12946
|
+
writeSaveData,
|
|
12788
12947
|
|
|
12789
12948
|
// Random
|
|
12790
12949
|
rand,
|
|
@@ -12994,7 +13153,8 @@ export
|
|
|
12994
13153
|
Box2dPlugin,
|
|
12995
13154
|
Box2dObject,
|
|
12996
13155
|
Box2dStaticObject,
|
|
12997
|
-
|
|
13156
|
+
Box2dKinematicObject,
|
|
13157
|
+
Box2dTileLayer,
|
|
12998
13158
|
Box2dRaycastResult,
|
|
12999
13159
|
Box2dJoint,
|
|
13000
13160
|
Box2dTargetJoint,
|