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.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.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
|
/**
|
|
@@ -1034,7 +988,9 @@ function wave(frequency=1, amplitude=1, t=time, offset=0)
|
|
|
1034
988
|
function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
1035
989
|
|
|
1036
990
|
/**
|
|
1037
|
-
* Check if object
|
|
991
|
+
* Check if object can be converted to a string (has a toString method)
|
|
992
|
+
* - Returns true for strings, numbers, and most objects
|
|
993
|
+
* - Returns false for null and undefined
|
|
1038
994
|
* @param {any} s
|
|
1039
995
|
* @return {boolean}
|
|
1040
996
|
* @memberof Math */
|
|
@@ -1111,7 +1067,7 @@ function lineTest(posStart, posEnd, testFunction, normal)
|
|
|
1111
1067
|
if (stepX < 0)
|
|
1112
1068
|
hitPos.x -= e;
|
|
1113
1069
|
}
|
|
1114
|
-
if (stepY < 0)
|
|
1070
|
+
else if (stepY < 0)
|
|
1115
1071
|
hitPos.y -= e;
|
|
1116
1072
|
|
|
1117
1073
|
// set normal
|
|
@@ -1818,7 +1774,7 @@ class Color
|
|
|
1818
1774
|
* @memberof Math */
|
|
1819
1775
|
const WHITE = debugProtectConstant(rgb());
|
|
1820
1776
|
|
|
1821
|
-
/** Color - Clear White #
|
|
1777
|
+
/** Color - Clear White #ffffff00 with 0 alpha
|
|
1822
1778
|
* @type {Color}
|
|
1823
1779
|
* @memberof Math */
|
|
1824
1780
|
const CLEAR_WHITE = debugProtectConstant(rgb(1,1,1,0));
|
|
@@ -1828,7 +1784,7 @@ const CLEAR_WHITE = debugProtectConstant(rgb(1,1,1,0));
|
|
|
1828
1784
|
* @memberof Math */
|
|
1829
1785
|
const BLACK = debugProtectConstant(rgb(0,0,0));
|
|
1830
1786
|
|
|
1831
|
-
/** Color - Clear Black #
|
|
1787
|
+
/** Color - Clear Black #00000000 with 0 alpha
|
|
1832
1788
|
* @type {Color}
|
|
1833
1789
|
* @memberof Math */
|
|
1834
1790
|
const CLEAR_BLACK = debugProtectConstant(rgb(0,0,0,0));
|
|
@@ -1884,6 +1840,90 @@ const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
|
1884
1840
|
* @namespace Utilities
|
|
1885
1841
|
*/
|
|
1886
1842
|
|
|
1843
|
+
/**
|
|
1844
|
+
* Timer object tracks how long has passed since it was set
|
|
1845
|
+
* @memberof Engine
|
|
1846
|
+
* @example
|
|
1847
|
+
* let a = new Timer; // creates a timer that is not set
|
|
1848
|
+
* a.set(3); // sets the timer to 3 seconds
|
|
1849
|
+
*
|
|
1850
|
+
* let b = new Timer(1); // creates a timer with 1 second left
|
|
1851
|
+
* b.unset(); // unset the timer
|
|
1852
|
+
*/
|
|
1853
|
+
class Timer
|
|
1854
|
+
{
|
|
1855
|
+
/** Create a timer object set time passed in
|
|
1856
|
+
* @param {number} [timeLeft] - How much time left before the timer
|
|
1857
|
+
* @param {boolean} [useRealTime] - Should the timer keep running even when the game is paused? (useful for UI) */
|
|
1858
|
+
constructor(timeLeft, useRealTime=false)
|
|
1859
|
+
{
|
|
1860
|
+
ASSERT(timeLeft === undefined || isNumber(timeLeft), 'Constructed Timer is invalid.', timeLeft);
|
|
1861
|
+
this.useRealTime = useRealTime;
|
|
1862
|
+
const globalTime = this.getGlobalTime();
|
|
1863
|
+
this.time = timeLeft === undefined ? undefined : globalTime + timeLeft;
|
|
1864
|
+
this.setTime = timeLeft;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
/** Set the timer with seconds passed in
|
|
1868
|
+
* @param {number} [timeLeft] - How much time left before the timer is elapsed in seconds */
|
|
1869
|
+
set(timeLeft=0)
|
|
1870
|
+
{
|
|
1871
|
+
ASSERT(isNumber(timeLeft), 'Timer is invalid.', timeLeft);
|
|
1872
|
+
const globalTime = this.getGlobalTime();
|
|
1873
|
+
this.time = globalTime + timeLeft;
|
|
1874
|
+
this.setTime = timeLeft;
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
/** Set if the timer should keep running even when the game is paused
|
|
1878
|
+
* @param {boolean} [useRealTime] */
|
|
1879
|
+
setUseRealTime(useRealTime=true)
|
|
1880
|
+
{
|
|
1881
|
+
ASSERT(!this.isSet(), 'Cannot change global time setting while timer is set.');
|
|
1882
|
+
this.useRealTime = useRealTime;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
/** Unset the timer */
|
|
1886
|
+
unset() { this.time = undefined; }
|
|
1887
|
+
|
|
1888
|
+
/** Returns true if set
|
|
1889
|
+
* @return {boolean} */
|
|
1890
|
+
isSet() { return this.time !== undefined; }
|
|
1891
|
+
|
|
1892
|
+
/** Returns true if set and has not elapsed
|
|
1893
|
+
* @return {boolean} */
|
|
1894
|
+
active() { return this.getGlobalTime() < this.time; }
|
|
1895
|
+
|
|
1896
|
+
/** Returns true if set and elapsed
|
|
1897
|
+
* @return {boolean} */
|
|
1898
|
+
elapsed() { return this.getGlobalTime() >= this.time; }
|
|
1899
|
+
|
|
1900
|
+
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
1901
|
+
* @return {number} */
|
|
1902
|
+
get() { return this.isSet()? this.getGlobalTime() - this.time : 0; }
|
|
1903
|
+
|
|
1904
|
+
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
1905
|
+
* @return {number} */
|
|
1906
|
+
getPercent() { return this.isSet()? 1-percent(this.time - this.getGlobalTime(), 0, this.setTime) : 0; }
|
|
1907
|
+
|
|
1908
|
+
/** Get the time this timer was set to, returns 0 if not set
|
|
1909
|
+
* @return {number} */
|
|
1910
|
+
getSetTime() { return this.isSet() ? this.setTime : 0; }
|
|
1911
|
+
|
|
1912
|
+
/** Get the current global time this timer is based on
|
|
1913
|
+
* @return {number} */
|
|
1914
|
+
getGlobalTime() { return this.useRealTime ? timeReal : time; }
|
|
1915
|
+
|
|
1916
|
+
/** Returns this timer expressed as a string
|
|
1917
|
+
* @return {string} */
|
|
1918
|
+
toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
|
|
1919
|
+
|
|
1920
|
+
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
1921
|
+
* @return {number} */
|
|
1922
|
+
valueOf() { return this.get(); }
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1926
|
+
|
|
1887
1927
|
/** Formats seconds to mm:ss style for display purposes
|
|
1888
1928
|
* @param {number} t - time in seconds
|
|
1889
1929
|
* @return {string}
|
|
@@ -1970,86 +2010,29 @@ function shareURL(title, url, callback)
|
|
|
1970
2010
|
|
|
1971
2011
|
///////////////////////////////////////////////////////////////////////////////
|
|
1972
2012
|
|
|
1973
|
-
/**
|
|
1974
|
-
*
|
|
1975
|
-
*
|
|
1976
|
-
*
|
|
1977
|
-
*
|
|
1978
|
-
|
|
1979
|
-
*
|
|
1980
|
-
* let b = new Timer(1); // creates a timer with 1 second left
|
|
1981
|
-
* b.unset(); // unset the timer
|
|
1982
|
-
*/
|
|
1983
|
-
class Timer
|
|
2013
|
+
/** Read save data from local storage
|
|
2014
|
+
* @param {string} saveName - unique name for the game/save
|
|
2015
|
+
* @param {Object} [defaultSaveData] - default values for save
|
|
2016
|
+
* @return {Object}
|
|
2017
|
+
* @memberof Utilities */
|
|
2018
|
+
function readSaveData(saveName, defaultSaveData)
|
|
1984
2019
|
{
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
{
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
const globalTime = this.getGlobalTime();
|
|
1993
|
-
this.time = timeLeft === undefined ? undefined : globalTime + timeLeft;
|
|
1994
|
-
this.setTime = timeLeft;
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
|
-
/** Set the timer with seconds passed in
|
|
1998
|
-
* @param {number} [timeLeft] - How much time left before the timer is elapsed in seconds */
|
|
1999
|
-
set(timeLeft=0)
|
|
2000
|
-
{
|
|
2001
|
-
ASSERT(isNumber(timeLeft), 'Timer is invalid.', timeLeft);
|
|
2002
|
-
const globalTime = this.getGlobalTime();
|
|
2003
|
-
this.time = globalTime + timeLeft;
|
|
2004
|
-
this.setTime = timeLeft;
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
/** Set if the timer should keep running even when the game is paused
|
|
2008
|
-
* @param {boolean} [useRealTime] */
|
|
2009
|
-
setUseRealTime(useRealTime=true)
|
|
2010
|
-
{
|
|
2011
|
-
ASSERT(!this.isSet(), 'Cannot change global time setting while timer is set.');
|
|
2012
|
-
this.useRealTime = useRealTime;
|
|
2013
|
-
}
|
|
2014
|
-
|
|
2015
|
-
/** Unset the timer */
|
|
2016
|
-
unset() { this.time = undefined; }
|
|
2017
|
-
|
|
2018
|
-
/** Returns true if set
|
|
2019
|
-
* @return {boolean} */
|
|
2020
|
-
isSet() { return this.time !== undefined; }
|
|
2021
|
-
|
|
2022
|
-
/** Returns true if set and has not elapsed
|
|
2023
|
-
* @return {boolean} */
|
|
2024
|
-
active() { return this.getGlobalTime() < this.time; }
|
|
2025
|
-
|
|
2026
|
-
/** Returns true if set and elapsed
|
|
2027
|
-
* @return {boolean} */
|
|
2028
|
-
elapsed() { return this.getGlobalTime() >= this.time; }
|
|
2029
|
-
|
|
2030
|
-
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
2031
|
-
* @return {number} */
|
|
2032
|
-
get() { return this.isSet()? this.getGlobalTime() - this.time : 0; }
|
|
2033
|
-
|
|
2034
|
-
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
2035
|
-
* @return {number} */
|
|
2036
|
-
getPercent() { return this.isSet()? 1-percent(this.time - this.getGlobalTime(), 0, this.setTime) : 0; }
|
|
2037
|
-
|
|
2038
|
-
/** Get the time this timer was set to, returns 0 if not set
|
|
2039
|
-
* @return {number} */
|
|
2040
|
-
getSetTime() { return this.isSet() ? this.setTime : 0; }
|
|
2041
|
-
|
|
2042
|
-
/** Get the current global time this timer is based on
|
|
2043
|
-
* @return {number} */
|
|
2044
|
-
getGlobalTime() { return this.useRealTime ? timeReal : time; }
|
|
2045
|
-
|
|
2046
|
-
/** Returns this timer expressed as a string
|
|
2047
|
-
* @return {string} */
|
|
2048
|
-
toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
|
|
2020
|
+
ASSERT(isString(saveName), 'loadData requires saveName string');
|
|
2021
|
+
|
|
2022
|
+
// replace undefined values with defaults
|
|
2023
|
+
const data = localStorage[saveName];
|
|
2024
|
+
const loadedData = data ? JSON.parse(data) : {};
|
|
2025
|
+
return { ...defaultSaveData, ...loadedData };
|
|
2026
|
+
}
|
|
2049
2027
|
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2028
|
+
/** Write save data to local storage
|
|
2029
|
+
* @param {string} saveName - unique name for the game/save
|
|
2030
|
+
* @param {Object} saveData - object containing data to be saved
|
|
2031
|
+
* @memberof Utilities */
|
|
2032
|
+
function writeSaveData(saveName, saveData)
|
|
2033
|
+
{
|
|
2034
|
+
ASSERT(isString(saveName), 'saveData requires saveName string');
|
|
2035
|
+
localStorage[saveName] = JSON.stringify(saveData);
|
|
2053
2036
|
}
|
|
2054
2037
|
/**
|
|
2055
2038
|
* LittleJS Engine Settings
|
|
@@ -2710,17 +2693,19 @@ class EngineObject
|
|
|
2710
2693
|
this.additiveColor = undefined;
|
|
2711
2694
|
/** @property {boolean} - Should it flip along y axis when rendered */
|
|
2712
2695
|
this.mirror = false;
|
|
2696
|
+
/** @property {boolean} - Has object been destroyed? */
|
|
2697
|
+
this.destroyed = false;
|
|
2713
2698
|
|
|
2714
2699
|
// physical properties
|
|
2715
|
-
/** @property {number}
|
|
2700
|
+
/** @property {number} - How heavy the object is, static if 0 */
|
|
2716
2701
|
this.mass = objectDefaultMass;
|
|
2717
|
-
/** @property {number}
|
|
2702
|
+
/** @property {number} - How much to slow down velocity each frame (0-1) */
|
|
2718
2703
|
this.damping = objectDefaultDamping;
|
|
2719
|
-
/** @property {number}
|
|
2704
|
+
/** @property {number} - How much to slow down rotation each frame (0-1) */
|
|
2720
2705
|
this.angleDamping = objectDefaultAngleDamping;
|
|
2721
|
-
/** @property {number}
|
|
2706
|
+
/** @property {number} - How bouncy the object is when colliding (0-1) */
|
|
2722
2707
|
this.restitution = objectDefaultRestitution;
|
|
2723
|
-
/** @property {number}
|
|
2708
|
+
/** @property {number} - How much friction to apply when sliding (0-1) */
|
|
2724
2709
|
this.friction = objectDefaultFriction;
|
|
2725
2710
|
/** @property {number} - How much to scale gravity by for this object */
|
|
2726
2711
|
this.gravityScale = 1;
|
|
@@ -2929,10 +2914,10 @@ class EngineObject
|
|
|
2929
2914
|
if (!tileCollisionTest(oldPos, this.size, this))
|
|
2930
2915
|
{
|
|
2931
2916
|
// test which side we bounced off (or both if a corner)
|
|
2932
|
-
const
|
|
2933
|
-
const
|
|
2934
|
-
|
|
2935
|
-
if (
|
|
2917
|
+
const isBlockedX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
|
|
2918
|
+
const isBlockedY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
|
|
2919
|
+
const restitution = max(this.restitution, hitLayer.restitution);
|
|
2920
|
+
if (isBlockedX)
|
|
2936
2921
|
{
|
|
2937
2922
|
// try to move up a tiny bit
|
|
2938
2923
|
const epsilon = 1e-3;
|
|
@@ -2948,16 +2933,12 @@ class EngineObject
|
|
|
2948
2933
|
return;
|
|
2949
2934
|
}
|
|
2950
2935
|
|
|
2951
|
-
// move to previous position and bounce
|
|
2936
|
+
// move to previous X position and bounce
|
|
2952
2937
|
this.pos.x = oldPos.x;
|
|
2953
|
-
this.velocity.x *= -
|
|
2938
|
+
this.velocity.x *= -restitution;
|
|
2954
2939
|
}
|
|
2955
|
-
if (
|
|
2940
|
+
if (isBlockedY || !isBlockedX)
|
|
2956
2941
|
{
|
|
2957
|
-
// bounce velocity
|
|
2958
|
-
const restitution = max(this.restitution, hitLayer.restitution);
|
|
2959
|
-
this.velocity.y *= -restitution;
|
|
2960
|
-
|
|
2961
2942
|
if (wasFalling)
|
|
2962
2943
|
{
|
|
2963
2944
|
// adjust position to slightly away from nearest tile
|
|
@@ -2973,10 +2954,12 @@ class EngineObject
|
|
|
2973
2954
|
}
|
|
2974
2955
|
else
|
|
2975
2956
|
{
|
|
2976
|
-
// move to previous position
|
|
2957
|
+
// move to previous Y position
|
|
2977
2958
|
this.pos.y = oldPos.y;
|
|
2978
2959
|
this.groundObject = undefined;
|
|
2979
2960
|
}
|
|
2961
|
+
// bounce velocity
|
|
2962
|
+
this.velocity.y *= -restitution;
|
|
2980
2963
|
}
|
|
2981
2964
|
debugPhysics && debugRect(this.pos, this.size, '#f00');
|
|
2982
2965
|
}
|
|
@@ -2994,19 +2977,19 @@ class EngineObject
|
|
|
2994
2977
|
drawTile(this.pos, this.drawSize || this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
|
|
2995
2978
|
}
|
|
2996
2979
|
|
|
2997
|
-
/** Destroy this object, destroy its children, detach its parent, and mark it for removal
|
|
2998
|
-
|
|
2980
|
+
/** Destroy this object, destroy its children, detach its parent, and mark it for removal
|
|
2981
|
+
* @param {boolean} [immediate] - should attached effects be allowed to die off? */
|
|
2982
|
+
destroy(immediate=false)
|
|
2999
2983
|
{
|
|
3000
|
-
if (this.destroyed)
|
|
3001
|
-
return;
|
|
2984
|
+
if (this.destroyed) return;
|
|
3002
2985
|
|
|
3003
2986
|
// disconnect from parent and destroy children
|
|
3004
|
-
this.destroyed =
|
|
2987
|
+
this.destroyed = true;
|
|
3005
2988
|
this.parent?.removeChild(this);
|
|
3006
2989
|
for (const child of this.children)
|
|
3007
2990
|
{
|
|
3008
2991
|
child.parent = undefined;
|
|
3009
|
-
child.destroy();
|
|
2992
|
+
child.destroy(immediate);
|
|
3010
2993
|
}
|
|
3011
2994
|
}
|
|
3012
2995
|
|
|
@@ -3105,7 +3088,7 @@ class EngineObject
|
|
|
3105
3088
|
}
|
|
3106
3089
|
|
|
3107
3090
|
/** Check if overlapping another engine object
|
|
3108
|
-
* Collisions are
|
|
3091
|
+
* Collisions are resolved to prevent overlaps
|
|
3109
3092
|
* @param {EngineObject} object
|
|
3110
3093
|
* @return {boolean} */
|
|
3111
3094
|
isOverlappingObject(object)
|
|
@@ -3357,7 +3340,7 @@ class TileInfo
|
|
|
3357
3340
|
}
|
|
3358
3341
|
|
|
3359
3342
|
/**
|
|
3360
|
-
* Returns a tile info for an index using this tile as
|
|
3343
|
+
* Returns a tile info for an index using this tile as reference
|
|
3361
3344
|
* @param {Vector2|number} [index=0]
|
|
3362
3345
|
* @return {TileInfo}
|
|
3363
3346
|
*/
|
|
@@ -3896,6 +3879,31 @@ function drawTextScreen(text, pos, size, color=WHITE, lineWidth=0, lineColor=BLA
|
|
|
3896
3879
|
///////////////////////////////////////////////////////////////////////////////
|
|
3897
3880
|
// Drawing utilities
|
|
3898
3881
|
|
|
3882
|
+
/** Load a texture at a specific index
|
|
3883
|
+
* @param {number} textureIndex - Index to store the texture at
|
|
3884
|
+
* @param {string} [src] - Image source path
|
|
3885
|
+
* @return {Promise} Promise that resolves when texture is loaded
|
|
3886
|
+
* @memberof Draw */
|
|
3887
|
+
async function loadTexture(textureIndex, src)
|
|
3888
|
+
{
|
|
3889
|
+
ASSERT(isNumber(textureIndex), 'textureIndex must be a number');
|
|
3890
|
+
ASSERT(!textureInfos[textureIndex], 'textureIndex is already loaded!');
|
|
3891
|
+
ASSERT(!src || isString(src), 'image src must be a string');
|
|
3892
|
+
|
|
3893
|
+
const image = new Image;
|
|
3894
|
+
if (src)
|
|
3895
|
+
{
|
|
3896
|
+
await new Promise(resolve =>
|
|
3897
|
+
{
|
|
3898
|
+
image.onerror = image.onload = resolve;
|
|
3899
|
+
image.crossOrigin = 'anonymous';
|
|
3900
|
+
image.src = src;
|
|
3901
|
+
});
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
textureInfos[textureIndex] = new TextureInfo(image);
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3899
3907
|
/** Convert from screen to world space coordinates
|
|
3900
3908
|
* @param {Vector2} screenPos
|
|
3901
3909
|
* @return {Vector2}
|
|
@@ -4187,7 +4195,7 @@ let engineFontImage;
|
|
|
4187
4195
|
class FontImage
|
|
4188
4196
|
{
|
|
4189
4197
|
/** Create an image font
|
|
4190
|
-
* @param {TileInfo} tileInfo - Tile info of first
|
|
4198
|
+
* @param {TileInfo} tileInfo - Tile info of first character in font
|
|
4191
4199
|
*/
|
|
4192
4200
|
constructor(tileInfo)
|
|
4193
4201
|
{
|
|
@@ -4276,23 +4284,20 @@ class FontImage
|
|
|
4276
4284
|
}
|
|
4277
4285
|
|
|
4278
4286
|
// load engine font, called automatically on startup
|
|
4279
|
-
function fontImageInit()
|
|
4287
|
+
async function fontImageInit()
|
|
4280
4288
|
{
|
|
4281
|
-
|
|
4289
|
+
const image = new Image;
|
|
4290
|
+
await new Promise(resolve =>
|
|
4282
4291
|
{
|
|
4283
|
-
|
|
4284
|
-
const image = new Image;
|
|
4285
|
-
image.onerror = image.onload = ()=>
|
|
4286
|
-
{
|
|
4287
|
-
const tilePos=vec2(), tileSize=vec2(8), padding=1, bleed=0;
|
|
4288
|
-
const textureInfo = new TextureInfo(image);
|
|
4289
|
-
const tileInfo = new TileInfo(tilePos, tileSize, textureInfo, padding, bleed);
|
|
4290
|
-
engineFontImage = new FontImage(tileInfo);
|
|
4291
|
-
resolve();
|
|
4292
|
-
}
|
|
4292
|
+
image.onerror = image.onload = resolve;
|
|
4293
4293
|
image.crossOrigin = 'anonymous';
|
|
4294
4294
|
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==';
|
|
4295
4295
|
});
|
|
4296
|
+
|
|
4297
|
+
const tilePos=vec2(), tileSize=vec2(8), padding=1, bleed=0;
|
|
4298
|
+
const textureInfo = new TextureInfo(image);
|
|
4299
|
+
const tileInfo = new TileInfo(tilePos, tileSize, textureInfo, padding, bleed);
|
|
4300
|
+
engineFontImage = new FontImage(tileInfo);
|
|
4296
4301
|
}
|
|
4297
4302
|
/**
|
|
4298
4303
|
* LittleJS Input System
|
|
@@ -5063,7 +5068,7 @@ function inputRender()
|
|
|
5063
5068
|
}
|
|
5064
5069
|
}
|
|
5065
5070
|
|
|
5066
|
-
// center position for right
|
|
5071
|
+
// center position for right touch pad face buttons
|
|
5067
5072
|
function touchGamepadButtonCenter()
|
|
5068
5073
|
{
|
|
5069
5074
|
const center = mainCanvasSize.subtract(vec2(touchGamepadSize));
|
|
@@ -5275,7 +5280,7 @@ class Sound
|
|
|
5275
5280
|
|
|
5276
5281
|
/** Loads a sound from a URL and decodes it into sample data.
|
|
5277
5282
|
* @param {string} filename
|
|
5278
|
-
* @return {Promise
|
|
5283
|
+
* @return {Promise} */
|
|
5279
5284
|
async loadSound(filename)
|
|
5280
5285
|
{
|
|
5281
5286
|
const response = await fetch(filename);
|
|
@@ -5333,7 +5338,7 @@ class Sound
|
|
|
5333
5338
|
* // Control the individual instance
|
|
5334
5339
|
* instance.setVolume(.5);
|
|
5335
5340
|
* instance.pause();
|
|
5336
|
-
* instance.
|
|
5341
|
+
* instance.resume();
|
|
5337
5342
|
* instance.stop();
|
|
5338
5343
|
*/
|
|
5339
5344
|
class SoundInstance
|
|
@@ -5447,7 +5452,7 @@ class SoundInstance
|
|
|
5447
5452
|
this.startTime = undefined;
|
|
5448
5453
|
}
|
|
5449
5454
|
|
|
5450
|
-
/**
|
|
5455
|
+
/** Resume this sound instance */
|
|
5451
5456
|
resume()
|
|
5452
5457
|
{
|
|
5453
5458
|
if (!this.isPaused()) return;
|
|
@@ -5461,8 +5466,8 @@ class SoundInstance
|
|
|
5461
5466
|
*/
|
|
5462
5467
|
isPlaying() { return !!this.source; }
|
|
5463
5468
|
|
|
5464
|
-
/** Check if this instance is paused
|
|
5465
|
-
* @return {boolean} - True if
|
|
5469
|
+
/** Check if this instance is paused or stopped (not currently playing)
|
|
5470
|
+
* @return {boolean} - True if not playing
|
|
5466
5471
|
*/
|
|
5467
5472
|
isPaused() { return !this.isPlaying(); }
|
|
5468
5473
|
|
|
@@ -5764,50 +5769,62 @@ const tileCollisionLayers = [];
|
|
|
5764
5769
|
|
|
5765
5770
|
/** Get tile collision data for a given cell in the grid
|
|
5766
5771
|
* @param {Vector2} pos
|
|
5772
|
+
* @param {boolean} [solidOnly] - Only check solid layers?
|
|
5767
5773
|
* @return {number}
|
|
5768
5774
|
* @memberof TileLayers */
|
|
5769
|
-
function tileCollisionGetData(pos)
|
|
5775
|
+
function tileCollisionGetData(pos, solidOnly=true)
|
|
5770
5776
|
{
|
|
5771
5777
|
// check all tile collision layers
|
|
5772
5778
|
for (const layer of tileCollisionLayers)
|
|
5779
|
+
if (!solidOnly || layer.isSolid)
|
|
5773
5780
|
if (pos.arrayCheck(layer.size))
|
|
5774
|
-
|
|
5781
|
+
{
|
|
5782
|
+
const data = layer.getCollisionData(pos);
|
|
5783
|
+
if (data) return data;
|
|
5784
|
+
}
|
|
5775
5785
|
return 0;
|
|
5776
5786
|
}
|
|
5777
5787
|
|
|
5778
5788
|
/** Check if a tile layer collides with another object
|
|
5779
|
-
* @param {Vector2}
|
|
5780
|
-
* @param {Vector2}
|
|
5781
|
-
* @param {EngineObject} [
|
|
5782
|
-
* @param {boolean}
|
|
5789
|
+
* @param {Vector2} pos
|
|
5790
|
+
* @param {Vector2} [size=vec2()]
|
|
5791
|
+
* @param {EngineObject|TileCollisionCallback} [callbackObject] - Callback, engine object, or undefined
|
|
5792
|
+
* @param {boolean} [solidOnly] - Only check solid layers?
|
|
5783
5793
|
* @return {TileCollisionLayer}
|
|
5784
5794
|
* @memberof TileLayers */
|
|
5785
|
-
function tileCollisionTest(pos, size=vec2(),
|
|
5795
|
+
function tileCollisionTest(pos, size=vec2(), callbackObject, solidOnly=true)
|
|
5786
5796
|
{
|
|
5787
5797
|
for (const layer of tileCollisionLayers)
|
|
5788
5798
|
{
|
|
5789
5799
|
if (!solidOnly || layer.isSolid)
|
|
5790
|
-
if (layer.collisionTest(pos, size,
|
|
5800
|
+
if (layer.collisionTest(pos, size, callbackObject))
|
|
5791
5801
|
return layer;
|
|
5792
5802
|
}
|
|
5793
5803
|
}
|
|
5794
5804
|
|
|
5805
|
+
/**
|
|
5806
|
+
* @callback TileCollisionCallback - Function to handle a tile collision test
|
|
5807
|
+
* @param {number} tileData - the value of the tile at the position
|
|
5808
|
+
* @param {Vector2} pos - world space position of tile where the collision occurred
|
|
5809
|
+
* @memberof TileLayers
|
|
5810
|
+
*/
|
|
5811
|
+
|
|
5795
5812
|
/** Return the exact position of the boundary of first tile hit, undefined if nothing was hit.
|
|
5796
|
-
* The point will be inside the colliding tile if it hits
|
|
5797
|
-
* @param {Vector2}
|
|
5798
|
-
* @param {Vector2}
|
|
5799
|
-
* @param {EngineObject} [
|
|
5800
|
-
* @param {Vector2}
|
|
5801
|
-
* @param {boolean}
|
|
5813
|
+
* The point will be inside the colliding tile if it hits
|
|
5814
|
+
* @param {Vector2} posStart
|
|
5815
|
+
* @param {Vector2} posEnd
|
|
5816
|
+
* @param {EngineObject|TileCollisionCallback} [callbackObject] - Callback, engine object, or undefined
|
|
5817
|
+
* @param {Vector2} [normal] - Optional normal of the surface hit
|
|
5818
|
+
* @param {boolean} [solidOnly=true] - Only check solid layers?
|
|
5802
5819
|
* @return {Vector2|undefined} - position of the center of the tile hit or undefined if no hit
|
|
5803
5820
|
* @memberof TileLayers */
|
|
5804
|
-
function tileCollisionRaycast(posStart, posEnd,
|
|
5821
|
+
function tileCollisionRaycast(posStart, posEnd, callbackObject, normal, solidOnly=true)
|
|
5805
5822
|
{
|
|
5806
5823
|
for (const layer of tileCollisionLayers)
|
|
5807
5824
|
{
|
|
5808
5825
|
if (!solidOnly || layer.isSolid)
|
|
5809
5826
|
{
|
|
5810
|
-
const hitPos = layer.collisionRaycast(posStart, posEnd,
|
|
5827
|
+
const hitPos = layer.collisionRaycast(posStart, posEnd, callbackObject, normal)
|
|
5811
5828
|
if (hitPos) return hitPos;
|
|
5812
5829
|
}
|
|
5813
5830
|
}
|
|
@@ -6371,18 +6388,22 @@ class TileCollisionLayer extends TileLayer
|
|
|
6371
6388
|
/** Check if collision with another object should occur
|
|
6372
6389
|
* @param {Vector2} pos
|
|
6373
6390
|
* @param {Vector2} [size=vec2()]
|
|
6374
|
-
* @param {EngineObject} [object
|
|
6391
|
+
* @param {EngineObject|TileCollisionCallback} [callbackObject] - Callback, engine object, or undefined
|
|
6375
6392
|
* @return {boolean} */
|
|
6376
|
-
collisionTest(pos, size=new Vector2,
|
|
6393
|
+
collisionTest(pos, size=new Vector2, callbackObject)
|
|
6377
6394
|
{
|
|
6378
6395
|
ASSERT(isVector2(pos) && isVector2(size), 'pos and size must be Vector2s');
|
|
6379
|
-
ASSERT(!
|
|
6380
|
-
|
|
6381
|
-
//
|
|
6382
|
-
const
|
|
6383
|
-
|
|
6396
|
+
ASSERT(!callbackObject || typeof callbackObject === 'function' || callbackObject instanceof EngineObject, 'callbackObject must be a function or EngineObject');
|
|
6397
|
+
|
|
6398
|
+
// make function to check for collision
|
|
6399
|
+
const collisionTest = callbackObject ? typeof callbackObject === 'function' ?
|
|
6400
|
+
(tileData, pos)=> callbackObject(tileData, pos) :
|
|
6401
|
+
(tileData, pos)=> callbackObject.collideWithTile(tileData, pos) :
|
|
6402
|
+
()=> true;
|
|
6384
6403
|
|
|
6385
6404
|
// check any tiles in the area for collision
|
|
6405
|
+
const posX = pos.x - this.pos.x;
|
|
6406
|
+
const posY = pos.y - this.pos.y;
|
|
6386
6407
|
const minX = max(posX - size.x/2|0, 0);
|
|
6387
6408
|
const minY = max(posY - size.y/2|0, 0);
|
|
6388
6409
|
const maxX = min(posX + size.x/2, this.size.x);
|
|
@@ -6393,9 +6414,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
6393
6414
|
{
|
|
6394
6415
|
// check if the object should collide with this tile
|
|
6395
6416
|
const tileData = this.collisionData[y*this.size.x+x];
|
|
6396
|
-
if (tileData)
|
|
6397
|
-
if (!object || object.collideWithTile(tileData,
|
|
6398
|
-
hitPos.set(x + this.pos.x, y + this.pos.y)))
|
|
6417
|
+
if (tileData && collisionTest(tileData, hitPos.set(x+this.pos.x, y+this.pos.y)))
|
|
6399
6418
|
return true;
|
|
6400
6419
|
}
|
|
6401
6420
|
return false;
|
|
@@ -6403,52 +6422,68 @@ class TileCollisionLayer extends TileLayer
|
|
|
6403
6422
|
|
|
6404
6423
|
/** Return the exact position of the boundary of first tile hit, undefined if nothing was hit.
|
|
6405
6424
|
* The point will be inside the colliding tile if it hits (may have a tiny shift)
|
|
6406
|
-
* @param {Vector2}
|
|
6407
|
-
* @param {Vector2}
|
|
6408
|
-
* @param {EngineObject} [
|
|
6409
|
-
* @param {Vector2}
|
|
6425
|
+
* @param {Vector2} posStart
|
|
6426
|
+
* @param {Vector2} posEnd
|
|
6427
|
+
* @param {EngineObject|TileCollisionCallback} [callbackObject] - Callback, engine object, or undefined
|
|
6428
|
+
* @param {Vector2} [normal] - Optional normal of the surface hit
|
|
6410
6429
|
* @return {Vector2|undefined} */
|
|
6411
|
-
collisionRaycast(posStart, posEnd,
|
|
6430
|
+
collisionRaycast(posStart, posEnd, callbackObject, normal)
|
|
6412
6431
|
{
|
|
6413
6432
|
ASSERT(isVector2(posStart) && isVector2(posEnd), 'positions must be Vector2s');
|
|
6414
|
-
ASSERT(!
|
|
6433
|
+
ASSERT(!callbackObject || typeof callbackObject === 'function' || callbackObject instanceof EngineObject, 'callbackObject must be a function or EngineObject');
|
|
6415
6434
|
|
|
6416
|
-
|
|
6417
|
-
const collisionTest =
|
|
6435
|
+
// make function to check for collision
|
|
6436
|
+
const collisionTest = callbackObject ? typeof callbackObject === 'function' ?
|
|
6437
|
+
(tileData, pos)=> callbackObject(tileData, pos) :
|
|
6438
|
+
(tileData, pos)=> callbackObject.collideWithTile(tileData, pos) :
|
|
6439
|
+
(tileData)=> tileData > 0;
|
|
6440
|
+
const testFunction = (pos)=>
|
|
6418
6441
|
{
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
const tileData = this.getCollisionData(localPos);
|
|
6422
|
-
return tileData && (!object || object.collideWithTile(tileData, pos));
|
|
6442
|
+
const tileData = this.getCollisionData(localPos.set(pos.x - this.pos.x, pos.y - this.pos.y));
|
|
6443
|
+
return tileData && collisionTest(tileData, pos);
|
|
6423
6444
|
}
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6445
|
+
|
|
6446
|
+
// use line test against tile collision
|
|
6447
|
+
const localPos = new Vector2;
|
|
6448
|
+
const hitPos = lineTest(posStart, posEnd, testFunction, normal);
|
|
6449
|
+
if (debugRaycast && hitPos)
|
|
6427
6450
|
{
|
|
6428
6451
|
const tilePos = hitPos.floor().add(vec2(.5));
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
return hitPos;
|
|
6452
|
+
debugRect(tilePos, vec2(1), '#f008');
|
|
6453
|
+
debugLine(posStart, posEnd, '#00f', .02);
|
|
6454
|
+
debugLine(posStart, hitPos, '#f00', .02);
|
|
6455
|
+
debugPoint(hitPos, '#0f0');
|
|
6456
|
+
normal && debugLine(hitPos, hitPos.add(normal), '#ff0', .02);
|
|
6435
6457
|
}
|
|
6458
|
+
return hitPos;
|
|
6436
6459
|
}
|
|
6437
6460
|
}
|
|
6438
6461
|
/**
|
|
6439
6462
|
* LittleJS Particle System
|
|
6463
|
+
* - A simple but fast and flexible particle system
|
|
6464
|
+
* - Lightweight Particles are created and managed by ParticleEmitters
|
|
6465
|
+
* - The particle design tool can be used to help create emitters
|
|
6466
|
+
* @namespace Particles
|
|
6440
6467
|
*/
|
|
6441
6468
|
|
|
6442
6469
|
/**
|
|
6443
|
-
* @callback
|
|
6470
|
+
* @callback ParticleCallback - Function that processes a particle
|
|
6444
6471
|
* @param {Particle} particle
|
|
6445
|
-
* @memberof
|
|
6472
|
+
* @memberof Particles
|
|
6473
|
+
*/
|
|
6474
|
+
|
|
6475
|
+
/**
|
|
6476
|
+
* @callback ParticleCollideCallback - Collide callback for particles
|
|
6477
|
+
* @param {Particle} particle
|
|
6478
|
+
* @param {number} tileData
|
|
6479
|
+
* @param {Vector2} pos
|
|
6480
|
+
* @memberof Particles
|
|
6446
6481
|
*/
|
|
6447
6482
|
|
|
6448
6483
|
/**
|
|
6449
6484
|
* Particle Emitter - Spawns particles with the given settings
|
|
6450
6485
|
* @extends EngineObject
|
|
6451
|
-
* @memberof
|
|
6486
|
+
* @memberof Particles
|
|
6452
6487
|
* @example
|
|
6453
6488
|
* // create a particle emitter
|
|
6454
6489
|
* let pos = vec2(2,3);
|
|
@@ -6466,7 +6501,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
6466
6501
|
class ParticleEmitter extends EngineObject
|
|
6467
6502
|
{
|
|
6468
6503
|
/** Create a particle system with the given settings
|
|
6469
|
-
* @param {Vector2}
|
|
6504
|
+
* @param {Vector2} pos - World space position of the emitter
|
|
6470
6505
|
* @param {number} [angle] - Angle to emit the particles
|
|
6471
6506
|
* @param {number|Vector2} [emitSize] - World space size of the emitter (float for circle diameter, vec2 for rect)
|
|
6472
6507
|
* @param {number} [emitTime] - How long to stay alive (0 is forever)
|
|
@@ -6496,7 +6531,7 @@ class ParticleEmitter extends EngineObject
|
|
|
6496
6531
|
*/
|
|
6497
6532
|
constructor
|
|
6498
6533
|
(
|
|
6499
|
-
|
|
6534
|
+
pos,
|
|
6500
6535
|
angle,
|
|
6501
6536
|
emitSize = 0,
|
|
6502
6537
|
emitTime = 0,
|
|
@@ -6525,12 +6560,13 @@ class ParticleEmitter extends EngineObject
|
|
|
6525
6560
|
localSpace = false
|
|
6526
6561
|
)
|
|
6527
6562
|
{
|
|
6528
|
-
super(
|
|
6563
|
+
super(pos, vec2(), tileInfo, angle, undefined, renderOrder);
|
|
6529
6564
|
|
|
6530
6565
|
// emitter settings
|
|
6566
|
+
/** @property {boolean} - Should particles be emitted in a circle */
|
|
6567
|
+
this.emitCircle = typeof emitSize === 'number';
|
|
6531
6568
|
/** @property {number|Vector2} - World space size of the emitter (float for circle diameter, vec2 for rect) */
|
|
6532
|
-
this.emitSize = emitSize
|
|
6533
|
-
emitSize.copy() : emitSize;
|
|
6569
|
+
this.emitSize = typeof emitSize === 'number' ? vec2(emitSize) : emitSize.copy();
|
|
6534
6570
|
/** @property {number} - How long to stay alive (0 is forever) */
|
|
6535
6571
|
this.emitTime = emitTime;
|
|
6536
6572
|
/** @property {number} - How many particles per second to spawn, does not emit if 0 */
|
|
@@ -6581,26 +6617,31 @@ class ParticleEmitter extends EngineObject
|
|
|
6581
6617
|
this.localSpace = localSpace;
|
|
6582
6618
|
/** @property {number} - If non zero the particle is drawn as a trail, stretched in the direction of velocity */
|
|
6583
6619
|
this.trailScale = 0;
|
|
6584
|
-
/** @property {
|
|
6585
|
-
this.particleDestroyCallback = undefined;
|
|
6586
|
-
/** @property {ParticleCallbackFunction} - Callback when particle is created */
|
|
6620
|
+
/** @property {ParticleCallback} - Callback when particle is created */
|
|
6587
6621
|
this.particleCreateCallback = undefined;
|
|
6588
|
-
/** @property {
|
|
6589
|
-
this.
|
|
6622
|
+
/** @property {ParticleCallback} - Callback when particle is destroyed */
|
|
6623
|
+
this.particleDestroyCallback = undefined;
|
|
6624
|
+
/** @property {ParticleCollideCallback} - Callback when particle collides */
|
|
6625
|
+
this.particleCollideCallback = undefined;
|
|
6590
6626
|
/** @property {number} - Percentage of velocity to pass to particles (0-1) */
|
|
6591
6627
|
this.velocityInheritance = 0;
|
|
6628
|
+
/** @property {number} - Track particle emit time */
|
|
6629
|
+
this.emitTimeBuffer = 0;
|
|
6630
|
+
/** @property {Array<Particle>} - Array of particles for this emitter */
|
|
6631
|
+
this.particles = [];
|
|
6592
6632
|
|
|
6593
6633
|
// track previous position and angle
|
|
6594
6634
|
this.previousAngle = this.angle;
|
|
6595
6635
|
this.previousPos = this.pos.copy();
|
|
6596
6636
|
}
|
|
6597
6637
|
|
|
6598
|
-
/** Emitters do not have physics */
|
|
6599
|
-
updatePhysics() {}
|
|
6600
|
-
|
|
6601
6638
|
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
6602
6639
|
update()
|
|
6603
6640
|
{
|
|
6641
|
+
// physics sanity checks
|
|
6642
|
+
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
6643
|
+
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
6644
|
+
|
|
6604
6645
|
if (this.velocityInheritance)
|
|
6605
6646
|
{
|
|
6606
6647
|
// pass emitter velocity to particles
|
|
@@ -6614,7 +6655,7 @@ class ParticleEmitter extends EngineObject
|
|
|
6614
6655
|
}
|
|
6615
6656
|
|
|
6616
6657
|
// update emitter
|
|
6617
|
-
if (
|
|
6658
|
+
if (this.isActive())
|
|
6618
6659
|
{
|
|
6619
6660
|
// emit particles
|
|
6620
6661
|
if (this.emitRate && particleEmitRateScale)
|
|
@@ -6624,14 +6665,21 @@ class ParticleEmitter extends EngineObject
|
|
|
6624
6665
|
this.emitParticle();
|
|
6625
6666
|
}
|
|
6626
6667
|
}
|
|
6627
|
-
else
|
|
6628
|
-
this.destroy();
|
|
6668
|
+
else if (this.particles.length === 0)
|
|
6669
|
+
this.destroy(true);
|
|
6670
|
+
|
|
6671
|
+
// update and remove destroyed particles
|
|
6672
|
+
this.particles = this.particles.filter((p)=>
|
|
6673
|
+
{
|
|
6674
|
+
p.update();
|
|
6675
|
+
return !p.destroyed;
|
|
6676
|
+
});
|
|
6629
6677
|
|
|
6630
6678
|
if (debugParticles)
|
|
6631
6679
|
{
|
|
6632
6680
|
// show emitter bounds
|
|
6633
|
-
if (
|
|
6634
|
-
debugCircle(this.pos, this.emitSize/2, '#0f0');
|
|
6681
|
+
if (this.emitCircle)
|
|
6682
|
+
debugCircle(this.pos, this.emitSize.x/2, '#0f0');
|
|
6635
6683
|
else
|
|
6636
6684
|
debugRect(this.pos, this.emitSize, '#0f0', 0, this.angle);
|
|
6637
6685
|
}
|
|
@@ -6642,14 +6690,15 @@ class ParticleEmitter extends EngineObject
|
|
|
6642
6690
|
emitParticle()
|
|
6643
6691
|
{
|
|
6644
6692
|
// spawn a particle
|
|
6645
|
-
let pos =
|
|
6646
|
-
randInCircle(this.emitSize/2)
|
|
6647
|
-
: vec2(rand(-.5,.5), rand(-.5,.5))
|
|
6693
|
+
let pos = this.emitCircle ? // check if circle emitter
|
|
6694
|
+
randInCircle(this.emitSize.x/2) // circle emitter
|
|
6695
|
+
: vec2(rand(-.5,.5), rand(-.5,.5)) // box emitter
|
|
6648
6696
|
.multiply(this.emitSize).rotate(this.angle)
|
|
6649
6697
|
let angle = rand(this.particleConeAngle, -this.particleConeAngle);
|
|
6650
6698
|
if (!this.localSpace)
|
|
6651
6699
|
{
|
|
6652
|
-
pos
|
|
6700
|
+
pos.x += this.pos.x;
|
|
6701
|
+
pos.y += this.pos.y;
|
|
6653
6702
|
angle += this.angle;
|
|
6654
6703
|
}
|
|
6655
6704
|
|
|
@@ -6669,25 +6718,17 @@ class ParticleEmitter extends EngineObject
|
|
|
6669
6718
|
const velocityAngle = this.localSpace ? coneAngle : this.angle + coneAngle;
|
|
6670
6719
|
|
|
6671
6720
|
// build particle
|
|
6672
|
-
const
|
|
6673
|
-
|
|
6674
|
-
particle.angleVelocity = angleSpeed;
|
|
6721
|
+
const velocity = vec2(speed*sin(velocityAngle), speed*cos(velocityAngle));
|
|
6722
|
+
let angleVelocity = angleSpeed;
|
|
6675
6723
|
if (!this.localSpace && this.velocityInheritance > 0)
|
|
6676
6724
|
{
|
|
6677
6725
|
// apply emitter velocity to particle
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6726
|
+
velocity.x += this.velocity.x;
|
|
6727
|
+
velocity.y += this.velocity.y;
|
|
6728
|
+
angleVelocity += this.angleVelocity;
|
|
6681
6729
|
}
|
|
6682
|
-
particle
|
|
6683
|
-
|
|
6684
|
-
particle.angleDamping = this.angleDamping;
|
|
6685
|
-
particle.restitution = this.restitution;
|
|
6686
|
-
particle.friction = this.friction;
|
|
6687
|
-
particle.gravityScale = this.gravityScale;
|
|
6688
|
-
particle.collideTiles = this.collideTiles;
|
|
6689
|
-
particle.renderOrder = this.renderOrder;
|
|
6690
|
-
particle.mirror = randBool();
|
|
6730
|
+
const particle = new Particle(this, pos, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, velocity, angleVelocity);
|
|
6731
|
+
this.particles.push(particle);
|
|
6691
6732
|
|
|
6692
6733
|
// call particle create callback
|
|
6693
6734
|
this.particleCreateCallback?.(particle);
|
|
@@ -6696,140 +6737,245 @@ class ParticleEmitter extends EngineObject
|
|
|
6696
6737
|
return particle;
|
|
6697
6738
|
}
|
|
6698
6739
|
|
|
6699
|
-
|
|
6700
|
-
|
|
6740
|
+
/** Particle emitters do not have physics */
|
|
6741
|
+
updatePhysics() {}
|
|
6742
|
+
|
|
6743
|
+
/** Render all particles for this emitter */
|
|
6744
|
+
render()
|
|
6745
|
+
{
|
|
6746
|
+
// render all particles
|
|
6747
|
+
for (const particle of this.particles)
|
|
6748
|
+
particle.render();
|
|
6749
|
+
}
|
|
6750
|
+
|
|
6751
|
+
/** is emitter actively spawning */
|
|
6752
|
+
isActive() { return !this.emitTime || this.getAliveTime() < this.emitTime; }
|
|
6753
|
+
|
|
6754
|
+
/** Destroy the particle emitter
|
|
6755
|
+
* @param {boolean} [immediate] - should particle emitters and other attached effects be allowed to die off */
|
|
6756
|
+
destroy(immediate=false)
|
|
6757
|
+
{
|
|
6758
|
+
if (this.destroyed) return;
|
|
6759
|
+
|
|
6760
|
+
super.destroy(immediate);
|
|
6761
|
+
if (!immediate && this.particles.length > 0)
|
|
6762
|
+
{
|
|
6763
|
+
// wait for particles to die off
|
|
6764
|
+
this.destroyed = false;
|
|
6765
|
+
this.emitTime = -1;
|
|
6766
|
+
}
|
|
6767
|
+
}
|
|
6701
6768
|
}
|
|
6702
6769
|
|
|
6703
6770
|
///////////////////////////////////////////////////////////////////////////////
|
|
6704
6771
|
/**
|
|
6705
6772
|
* Particle Object - Created automatically by Particle Emitters
|
|
6706
|
-
* @
|
|
6707
|
-
* @memberof Engine
|
|
6773
|
+
* @memberof Particles
|
|
6708
6774
|
*/
|
|
6709
|
-
class Particle
|
|
6775
|
+
class Particle
|
|
6710
6776
|
{
|
|
6711
6777
|
/**
|
|
6712
6778
|
* Create a particle with the passed in settings
|
|
6713
6779
|
* Typically this is created automatically by a ParticleEmitter
|
|
6714
|
-
* @param {
|
|
6715
|
-
* @param {
|
|
6716
|
-
* @param {number}
|
|
6717
|
-
* @param {Color}
|
|
6718
|
-
* @param {Color}
|
|
6719
|
-
* @param {number}
|
|
6720
|
-
* @param {number}
|
|
6721
|
-
* @param {number}
|
|
6722
|
-
* @param {
|
|
6723
|
-
* @param {
|
|
6724
|
-
* @param {number} trailScale - If a trail, how long to make it
|
|
6725
|
-
* @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
|
|
6726
|
-
* @param {ParticleCallbackFunction} [destroyCallback] - Callback when particle dies
|
|
6780
|
+
* @param {ParticleEmitter} emitter - The emitter that created this particle
|
|
6781
|
+
* @param {Vector2} pos - World or local space position
|
|
6782
|
+
* @param {number} angle - Angle of the particle
|
|
6783
|
+
* @param {Color} colorStart - Color at start of life
|
|
6784
|
+
* @param {Color} colorEnd - Color at end of life
|
|
6785
|
+
* @param {number} lifeTime - How long to live for
|
|
6786
|
+
* @param {number} sizeStart - Size at start of life
|
|
6787
|
+
* @param {number} sizeEnd - Size at end of life
|
|
6788
|
+
* @param {Vector2} [velocity] - Velocity of the particle
|
|
6789
|
+
* @param {number} [angleVelocity] - Angular speed of the particle
|
|
6727
6790
|
*/
|
|
6728
|
-
constructor(
|
|
6729
|
-
)
|
|
6791
|
+
constructor(emitter, pos, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, velocity = vec2(), angleVelocity = 0)
|
|
6730
6792
|
{
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
/** @property {
|
|
6793
|
+
/** @property {ParticleEmitter} */
|
|
6794
|
+
this.emitter = emitter;
|
|
6795
|
+
/** @property {Vector2} */
|
|
6796
|
+
this.pos = pos;
|
|
6797
|
+
/** @property {number} */
|
|
6798
|
+
this.angle = angle;
|
|
6799
|
+
/** @property {Vector2} */
|
|
6800
|
+
this.size = vec2(sizeStart);
|
|
6801
|
+
/** @property {Color} */
|
|
6802
|
+
this.color = colorStart.copy();
|
|
6803
|
+
/** @property {Color} */
|
|
6734
6804
|
this.colorStart = colorStart;
|
|
6735
|
-
/** @property {Color}
|
|
6805
|
+
/** @property {Color} */
|
|
6736
6806
|
this.colorEnd = colorEnd;
|
|
6737
|
-
/** @property {number}
|
|
6807
|
+
/** @property {number} */
|
|
6738
6808
|
this.lifeTime = lifeTime;
|
|
6739
|
-
/** @property {number}
|
|
6809
|
+
/** @property {number} */
|
|
6740
6810
|
this.sizeStart = sizeStart;
|
|
6741
|
-
/** @property {number}
|
|
6811
|
+
/** @property {number} */
|
|
6742
6812
|
this.sizeEnd = sizeEnd;
|
|
6743
|
-
/** @property {
|
|
6744
|
-
this.
|
|
6745
|
-
/** @property {
|
|
6746
|
-
this.
|
|
6747
|
-
/** @property {number}
|
|
6748
|
-
this.
|
|
6749
|
-
/** @property {
|
|
6750
|
-
this.
|
|
6751
|
-
/** @property {
|
|
6752
|
-
this.
|
|
6753
|
-
|
|
6754
|
-
this.
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6813
|
+
/** @property {Vector2} */
|
|
6814
|
+
this.velocity = velocity;
|
|
6815
|
+
/** @property {number} */
|
|
6816
|
+
this.angleVelocity = angleVelocity;
|
|
6817
|
+
/** @property {number} */
|
|
6818
|
+
this.spawnTime = time;
|
|
6819
|
+
/** @property {boolean} */
|
|
6820
|
+
this.mirror = randBool();
|
|
6821
|
+
/** @property {EngineObject} */
|
|
6822
|
+
this.groundObject = undefined;
|
|
6823
|
+
/** @property {boolean} */
|
|
6824
|
+
this.destroyed = false;
|
|
6825
|
+
/** @property {TileInfo} */
|
|
6826
|
+
this.tileInfo = emitter.tileInfo;
|
|
6827
|
+
}
|
|
6828
|
+
|
|
6829
|
+
/** Update the particle */
|
|
6758
6830
|
update()
|
|
6759
6831
|
{
|
|
6760
|
-
|
|
6832
|
+
// emitter properties
|
|
6833
|
+
const emitter = this.emitter;
|
|
6834
|
+
const damping = emitter.damping;
|
|
6835
|
+
const angleDamping = emitter.angleDamping;
|
|
6836
|
+
const restitution = emitter.restitution;
|
|
6837
|
+
const friction = emitter.friction;
|
|
6838
|
+
const gravityScale = emitter.gravityScale;
|
|
6839
|
+
const collideTiles = emitter.collideTiles;
|
|
6840
|
+
const collideCallback = emitter.particleCollideCallback;
|
|
6841
|
+
|
|
6842
|
+
// destroy particle when its time runs out
|
|
6843
|
+
if (this.lifeTime > 0 && time - this.spawnTime > this.lifeTime)
|
|
6761
6844
|
{
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
if (length2 > objectMaxSpeed*objectMaxSpeed)
|
|
6765
|
-
{
|
|
6766
|
-
const s = objectMaxSpeed / length2**.5;
|
|
6767
|
-
this.velocity.x *= s;
|
|
6768
|
-
this.velocity.y *= s;
|
|
6769
|
-
}
|
|
6845
|
+
this.destroy();
|
|
6846
|
+
return;
|
|
6770
6847
|
}
|
|
6771
6848
|
|
|
6772
|
-
|
|
6849
|
+
// apply physics
|
|
6850
|
+
const oldPos = this.pos.copy();
|
|
6851
|
+
this.velocity.x *= damping;
|
|
6852
|
+
this.velocity.y *= damping;
|
|
6853
|
+
this.pos.x += this.velocity.x += gravity.x * gravityScale;
|
|
6854
|
+
this.pos.y += this.velocity.y += gravity.y * gravityScale;
|
|
6855
|
+
this.angle += this.angleVelocity *= angleDamping;
|
|
6856
|
+
|
|
6857
|
+
// don't do collision if solver disabled
|
|
6858
|
+
if (!enablePhysicsSolver || !collideTiles) return;
|
|
6859
|
+
|
|
6860
|
+
// apply max circular speed to prevent going through collision
|
|
6861
|
+
const length2 = this.velocity.lengthSquared();
|
|
6862
|
+
if (length2 > objectMaxSpeed*objectMaxSpeed)
|
|
6773
6863
|
{
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
this.
|
|
6777
|
-
this.size.set(this.sizeEnd, this.sizeEnd);
|
|
6778
|
-
this.destroyCallback?.(this);
|
|
6779
|
-
this.destroyed = 1;
|
|
6864
|
+
const s = objectMaxSpeed / length2**.5;
|
|
6865
|
+
this.velocity.x *= s;
|
|
6866
|
+
this.velocity.y *= s;
|
|
6780
6867
|
}
|
|
6868
|
+
|
|
6869
|
+
// check collision against tiles
|
|
6870
|
+
this.groundObject = undefined;
|
|
6871
|
+
const testCollision = collideCallback ? (pos)=>
|
|
6872
|
+
{
|
|
6873
|
+
const data = tileCollisionGetData(pos);
|
|
6874
|
+
return data && collideCallback(this, data, pos);
|
|
6875
|
+
} : (pos)=> tileCollisionGetData(pos) > 0;
|
|
6876
|
+
|
|
6877
|
+
if (testCollision(this.pos))
|
|
6878
|
+
{
|
|
6879
|
+
// if already was stuck in collision, don't do anything
|
|
6880
|
+
const hitLayer = tileCollisionTest(this.pos);
|
|
6881
|
+
if (!testCollision(oldPos))
|
|
6882
|
+
{
|
|
6883
|
+
if (!collideCallback || collideCallback?.(this, hitLayer))
|
|
6884
|
+
{
|
|
6885
|
+
// test which side we bounced off (or both if a corner)
|
|
6886
|
+
const isBlockedX = testCollision(vec2(this.pos.x, oldPos.y));
|
|
6887
|
+
const isBlockedY = testCollision(vec2(oldPos.x, this.pos.y));
|
|
6888
|
+
const hitRestitution = max(restitution, hitLayer.restitution);
|
|
6889
|
+
const hitFriction = max(friction, hitLayer.friction);
|
|
6890
|
+
if (isBlockedX)
|
|
6891
|
+
{
|
|
6892
|
+
// move to previous X position and bounce
|
|
6893
|
+
this.pos.x = oldPos.x;
|
|
6894
|
+
this.velocity.x *= -hitRestitution;
|
|
6895
|
+
this.velocity.y *= hitFriction;
|
|
6896
|
+
}
|
|
6897
|
+
if (isBlockedY || !isBlockedX)
|
|
6898
|
+
{
|
|
6899
|
+
const wasFalling = this.velocity.y < 0 && gravity.y < 0 || this.velocity.y > 0 && gravity.y > 0;
|
|
6900
|
+
if (wasFalling)
|
|
6901
|
+
this.groundObject = hitLayer;
|
|
6902
|
+
|
|
6903
|
+
// move to previous Y position and bounce
|
|
6904
|
+
this.pos.y = oldPos.y;
|
|
6905
|
+
this.velocity.y *= -hitRestitution;
|
|
6906
|
+
this.velocity.x *= hitFriction;
|
|
6907
|
+
}
|
|
6908
|
+
debugPhysics && debugRect(this.pos, this.size, '#f00');
|
|
6909
|
+
}
|
|
6910
|
+
}
|
|
6911
|
+
}
|
|
6912
|
+
}
|
|
6913
|
+
|
|
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);
|
|
6781
6923
|
}
|
|
6782
6924
|
|
|
6783
|
-
/** Render the particle, automatically called each frame
|
|
6925
|
+
/** Render the particle, automatically called each frame */
|
|
6784
6926
|
render()
|
|
6785
6927
|
{
|
|
6928
|
+
// emitter properties
|
|
6929
|
+
const emitter = this.emitter;
|
|
6930
|
+
const localSpace = emitter.localSpace;
|
|
6931
|
+
const additive = emitter.additive;
|
|
6932
|
+
const trailScale = emitter.trailScale;
|
|
6933
|
+
const fadeRate = emitter.fadeRate / 2;
|
|
6934
|
+
|
|
6786
6935
|
// lerp color and size
|
|
6787
6936
|
const p1 = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1, p2 = 1-p1;
|
|
6788
6937
|
const radius = p2 * this.sizeStart + p1 * this.sizeEnd;
|
|
6789
6938
|
const size = vec2(radius);
|
|
6939
|
+
const alphaFade = p1 < fadeRate ? p1/fadeRate :
|
|
6940
|
+
p1 > 1-fadeRate ? (1-p1)/fadeRate : 1;
|
|
6790
6941
|
this.color.r = p2 * this.colorStart.r + p1 * this.colorEnd.r;
|
|
6791
6942
|
this.color.g = p2 * this.colorStart.g + p1 * this.colorEnd.g;
|
|
6792
6943
|
this.color.b = p2 * this.colorStart.b + p1 * this.colorEnd.b;
|
|
6793
|
-
this.color.a = p2 * this.colorStart.a + p1 * this.colorEnd.a;
|
|
6794
|
-
|
|
6795
|
-
// fade alpha
|
|
6796
|
-
const fadeRate = this.fadeRate/2;
|
|
6797
|
-
this.color.a *= p1 < fadeRate ? p1/fadeRate :
|
|
6798
|
-
p1 > 1-fadeRate ? (1-p1)/fadeRate : 1;
|
|
6944
|
+
this.color.a = (p2 * this.colorStart.a + p1 * this.colorEnd.a) * alphaFade;
|
|
6799
6945
|
|
|
6800
6946
|
// draw the particle
|
|
6801
|
-
|
|
6947
|
+
additive && setBlendMode(true);
|
|
6802
6948
|
|
|
6803
6949
|
// update the position and angle for drawing
|
|
6804
|
-
|
|
6805
|
-
|
|
6950
|
+
const pos = this.pos.copy();
|
|
6951
|
+
let angle = this.angle;
|
|
6952
|
+
if (localSpace)
|
|
6806
6953
|
{
|
|
6807
6954
|
// in local space of emitter
|
|
6808
|
-
const a =
|
|
6955
|
+
const a = emitter.angle;
|
|
6809
6956
|
const c = cos(a), s = sin(a);
|
|
6810
|
-
pos
|
|
6811
|
-
|
|
6812
|
-
angle +=
|
|
6957
|
+
pos.set(emitter.pos.x + pos.x*c - pos.y*s,
|
|
6958
|
+
emitter.pos.y + pos.x*s + pos.y*c);
|
|
6959
|
+
angle += a;
|
|
6813
6960
|
}
|
|
6814
|
-
if (
|
|
6961
|
+
if (trailScale)
|
|
6815
6962
|
{
|
|
6816
6963
|
// trail style particles
|
|
6817
|
-
const
|
|
6818
|
-
this.velocity.rotate(-
|
|
6819
|
-
|
|
6820
|
-
const speed = direction.length();
|
|
6964
|
+
const velocity = localSpace ?
|
|
6965
|
+
this.velocity.rotate(-emitter.angle) : this.velocity;
|
|
6966
|
+
const speed = velocity.length();
|
|
6821
6967
|
if (speed)
|
|
6822
6968
|
{
|
|
6823
6969
|
// stretch in direction of motion
|
|
6824
|
-
const trailLength = speed *
|
|
6970
|
+
const trailLength = speed * trailScale;
|
|
6825
6971
|
size.y = max(size.x, trailLength);
|
|
6826
|
-
angle = atan2(
|
|
6972
|
+
angle = atan2(velocity.x, velocity.y);
|
|
6827
6973
|
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
6828
6974
|
}
|
|
6829
6975
|
}
|
|
6830
6976
|
else
|
|
6831
6977
|
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
6832
|
-
|
|
6978
|
+
additive && setBlendMode();
|
|
6833
6979
|
debugParticles && debugRect(pos, size, '#f005', 0, angle);
|
|
6834
6980
|
}
|
|
6835
6981
|
}
|
|
@@ -8109,7 +8255,7 @@ class PostProcessPlugin
|
|
|
8109
8255
|
{
|
|
8110
8256
|
/** Create global post processing shader
|
|
8111
8257
|
* @param {string} shaderCode
|
|
8112
|
-
* @param {boolean} [includeMainCanvas] - combine
|
|
8258
|
+
* @param {boolean} [includeMainCanvas] - combine mainCanvas onto glCanvas
|
|
8113
8259
|
* @param {boolean} [feedbackTexture] - use glCanvas from previous frame as the texture
|
|
8114
8260
|
* @example
|
|
8115
8261
|
* // create the post process plugin object
|
|
@@ -8502,7 +8648,7 @@ class UISystemPlugin
|
|
|
8502
8648
|
// navigation properties
|
|
8503
8649
|
/** @property {UIObject} - Object currently selected by navigation (gamepad or keyboard) */
|
|
8504
8650
|
this.navigationObject = undefined;
|
|
8505
|
-
/** @property {Timer} -
|
|
8651
|
+
/** @property {Timer} - Cool down timer for navigation inputs */
|
|
8506
8652
|
this.navigationTimer = new Timer(undefined, true);
|
|
8507
8653
|
/** @property {number} - Time between navigation inputs in seconds */
|
|
8508
8654
|
this.navigationDelay = .2;
|
|
@@ -8968,7 +9114,7 @@ class UISystemPlugin
|
|
|
8968
9114
|
|
|
8969
9115
|
const savedNavigationDirection = uiSystem.navigationDirection;
|
|
8970
9116
|
|
|
8971
|
-
// allow both
|
|
9117
|
+
// allow both axes for navigation
|
|
8972
9118
|
uiSystem.navigationDirection = 2;
|
|
8973
9119
|
|
|
8974
9120
|
// confirm menu
|
|
@@ -9063,7 +9209,7 @@ class UIObject
|
|
|
9063
9209
|
this.lineWidth = uiSystem.defaultLineWidth;
|
|
9064
9210
|
/** @property {number} - Corner radius for rounded rects */
|
|
9065
9211
|
this.cornerRadius = uiSystem.defaultCornerRadius;
|
|
9066
|
-
/** @property {string} - Font for this
|
|
9212
|
+
/** @property {string} - Font for this object */
|
|
9067
9213
|
this.font = uiSystem.defaultFont;
|
|
9068
9214
|
/** @property {string} - Font style for this object or undefined */
|
|
9069
9215
|
this.fontStyle = undefined;
|
|
@@ -9551,6 +9697,8 @@ class UIScrollbar extends UIObject
|
|
|
9551
9697
|
this.value = value;
|
|
9552
9698
|
/** @property {Color} - Color for the handle part of the scrollbar */
|
|
9553
9699
|
this.handleColor = handleColor.copy();
|
|
9700
|
+
/** @property {boolean} - Should it fill up like a progress bar? */
|
|
9701
|
+
this.fillMode = false;
|
|
9554
9702
|
|
|
9555
9703
|
// set properties
|
|
9556
9704
|
this.text = text;
|
|
@@ -9596,19 +9744,30 @@ class UIScrollbar extends UIObject
|
|
|
9596
9744
|
|
|
9597
9745
|
// handle horizontal or vertical scrollbar
|
|
9598
9746
|
const isHorizontal = this.size.x > this.size.y;
|
|
9599
|
-
const
|
|
9600
|
-
const
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9747
|
+
const barWidth = isHorizontal ? this.size.x : this.size.y;
|
|
9748
|
+
const handleWidth = isHorizontal ? this.size.y : this.size.x;
|
|
9749
|
+
if (this.fillMode)
|
|
9750
|
+
{
|
|
9751
|
+
// draw progress bar
|
|
9752
|
+
const minWidth = min(handleWidth, this.cornerRadius * 2);
|
|
9753
|
+
const progressWidth = lerp(minWidth, barWidth, this.value);
|
|
9754
|
+
const p = (progressWidth - barWidth) * (isHorizontal ? .5 : -.5);
|
|
9755
|
+
const pos = this.pos.add(isHorizontal ? vec2(p, 0) : vec2(0, p));
|
|
9756
|
+
const color = this.disabled ? this.disabledColor : this.handleColor;
|
|
9757
|
+
const drawSize = isHorizontal ?
|
|
9758
|
+
vec2(progressWidth, this.size.y) : vec2(this.size.x, progressWidth);
|
|
9759
|
+
uiSystem.drawRect(pos, drawSize, color, this.lineWidth, this.lineColor, this.cornerRadius, this.gradientColor);
|
|
9760
|
+
}
|
|
9761
|
+
else
|
|
9762
|
+
{
|
|
9763
|
+
// draw the scrollbar handle
|
|
9764
|
+
const value = clamp(isHorizontal ? this.value : 1 - this.value);
|
|
9765
|
+
const p = (barWidth - handleWidth) * (value - .5);
|
|
9766
|
+
const pos = this.pos.add(isHorizontal ? vec2(p, 0) : vec2(0, p));
|
|
9767
|
+
const color = this.disabled ? this.disabledColor : this.handleColor;
|
|
9768
|
+
const drawSize = vec2(handleWidth);
|
|
9769
|
+
uiSystem.drawRect(pos, drawSize, color, this.lineWidth, this.lineColor, this.cornerRadius, this.gradientColor);
|
|
9770
|
+
}
|
|
9612
9771
|
|
|
9613
9772
|
// draw the text scaled to fit on the scrollbar
|
|
9614
9773
|
const textSize = this.getTextSize();
|
|
@@ -9630,7 +9789,7 @@ class UIScrollbar extends UIObject
|
|
|
9630
9789
|
* @extends UIObject
|
|
9631
9790
|
* @example
|
|
9632
9791
|
* // Create a video player UI object
|
|
9633
|
-
* const video = new VideoPlayerUIObject(vec2(400, 300), vec2(320, 240), '
|
|
9792
|
+
* const video = new VideoPlayerUIObject(vec2(400, 300), vec2(320, 240), 'video.mp4', true);
|
|
9634
9793
|
* video.play();
|
|
9635
9794
|
* @memberof UISystem
|
|
9636
9795
|
*/
|
|
@@ -9671,12 +9830,11 @@ class UIVideo extends UIObject
|
|
|
9671
9830
|
|
|
9672
9831
|
/** Play or resume the video
|
|
9673
9832
|
* @return {Promise} Promise that resolves when playback starts */
|
|
9674
|
-
play()
|
|
9833
|
+
async play()
|
|
9675
9834
|
{
|
|
9676
9835
|
// try to play the video, catch any errors (autoplay may be blocked)
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
return promise;
|
|
9836
|
+
try { await this.video.play(); }
|
|
9837
|
+
catch(e) {}
|
|
9680
9838
|
}
|
|
9681
9839
|
|
|
9682
9840
|
/** Pause the video */
|
|
@@ -10379,11 +10537,11 @@ class Box2dStaticObject extends Box2dObject
|
|
|
10379
10537
|
|
|
10380
10538
|
///////////////////////////////////////////////////////////////////////////////
|
|
10381
10539
|
/**
|
|
10382
|
-
* Box2D
|
|
10540
|
+
* Box2D Kinematic Object - Box2d with a kinematic physics body
|
|
10383
10541
|
* @extends Box2dObject
|
|
10384
10542
|
* @memberof Box2D
|
|
10385
10543
|
*/
|
|
10386
|
-
class
|
|
10544
|
+
class Box2dKinematicObject extends Box2dObject
|
|
10387
10545
|
{
|
|
10388
10546
|
/** Create a LittleJS object with Box2d physics
|
|
10389
10547
|
* @param {Vector2} [pos]
|
|
@@ -10435,20 +10593,19 @@ class Box2dTileLayer extends Box2dStaticObject
|
|
|
10435
10593
|
this.destroyAllFixtures();
|
|
10436
10594
|
|
|
10437
10595
|
// create box2d object for this layer
|
|
10438
|
-
const size = this.tileLayer.size;
|
|
10439
10596
|
this.pos = this.tileLayer.pos.copy();
|
|
10440
|
-
this.size = size.copy();
|
|
10597
|
+
this.size = this.tileLayer.size.copy();
|
|
10441
10598
|
|
|
10442
10599
|
// track which tiles have been processed
|
|
10443
10600
|
const processed = [];
|
|
10444
|
-
const getIndex = (x, y)=> x + y * size.x;
|
|
10601
|
+
const getIndex = (x, y)=> x + y * this.size.x;
|
|
10445
10602
|
const isSolidUnprocessed = (x, y)=>
|
|
10446
10603
|
!processed[getIndex(x, y)] &&
|
|
10447
10604
|
this.tileLayer.getCollisionData(vec2(x, y)) > 0;
|
|
10448
10605
|
|
|
10449
10606
|
// combine tiles into larger boxes
|
|
10450
|
-
for (let x = 0; x < size.x; ++x)
|
|
10451
|
-
for (let y = 0; y < size.y; ++y)
|
|
10607
|
+
for (let x = 0; x < this.size.x; ++x)
|
|
10608
|
+
for (let y = 0; y < this.size.y; ++y)
|
|
10452
10609
|
{
|
|
10453
10610
|
if (!isSolidUnprocessed(x, y)) continue;
|
|
10454
10611
|
|