littlejsengine 1.17.1 → 1.17.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +10 -10
- package/dist/littlejs.d.ts +254 -171
- package/dist/littlejs.esm.js +892 -628
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +889 -626
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +885 -622
- package/examples/box2d/gameObjects.js +3 -3
- package/examples/electron/index.html +2 -2
- package/examples/index.html +10 -9
- package/examples/logo.png +0 -0
- package/examples/logo2.png +0 -0
- package/examples/module/build.bat +7 -0
- package/examples/module/build.js +121 -0
- package/examples/particles/index.html +1 -1
- package/examples/platformer/game.js +1 -1
- package/examples/platformer/gameEffects.js +5 -10
- package/examples/platformer/gameLevel.js +3 -3
- package/examples/puzzle/game.js +2 -2
- package/examples/shorts/base.html +4 -1
- package/examples/shorts/box2dPool.js +2 -2
- package/examples/shorts/box2dTileLayer.js +48 -0
- package/examples/shorts/colors.js +1 -1
- package/examples/shorts/fps.js +1 -1
- package/examples/shorts/music.js +5 -4
- package/examples/shorts/musicPlayer.js +4 -3
- package/examples/shorts/parallax.js +2 -2
- package/examples/shorts/tiltedView.js +1 -1
- package/examples/starter/index.html +2 -2
- package/package.json +5 -5
- package/plugins/box2d.js +166 -63
- package/plugins/pluginExport.js +1 -1
- package/plugins/postProcess.js +1 -1
- package/plugins/uiSystem.js +33 -20
- package/plugins/zzfxm.js +2 -2
- package/reference.md +9 -8
- package/src/engine.js +111 -134
- package/src/engineAudio.js +44 -58
- package/src/engineDebug.js +4 -4
- package/src/engineDraw.js +7 -4
- package/src/engineExport.js +2 -1
- package/src/engineInput.js +5 -5
- package/src/engineMath.js +9 -9
- package/src/engineObject.js +25 -25
- package/src/engineParticles.js +241 -118
- package/src/engineTileLayer.js +117 -88
- package/src/engineUtilities.js +105 -78
- package/src/engineWebGL.js +2 -2
- package/plugins/Box2D_License.txt +0 -17
package/src/engine.js
CHANGED
|
@@ -30,7 +30,7 @@ const engineName = 'LittleJS';
|
|
|
30
30
|
* @type {string}
|
|
31
31
|
* @default
|
|
32
32
|
* @memberof Engine */
|
|
33
|
-
const engineVersion = '1.17.
|
|
33
|
+
const engineVersion = '1.17.10';
|
|
34
34
|
|
|
35
35
|
/** Frames per second to update
|
|
36
36
|
* @type {number}
|
|
@@ -85,8 +85,9 @@ function getPaused() { return paused; }
|
|
|
85
85
|
* @memberof Engine */
|
|
86
86
|
function setPaused(isPaused=true) { paused = isPaused; }
|
|
87
87
|
|
|
88
|
-
//
|
|
88
|
+
// Engine internal variables
|
|
89
89
|
let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
|
|
90
|
+
let showEngineVersion = true;
|
|
90
91
|
|
|
91
92
|
///////////////////////////////////////////////////////////////////////////////
|
|
92
93
|
// plugin hooks
|
|
@@ -150,16 +151,17 @@ function engineAddPlugin(update, render, glContextLost, glContextRestored)
|
|
|
150
151
|
* @example
|
|
151
152
|
* // Basic engine startup
|
|
152
153
|
* engineInit(
|
|
153
|
-
* ()
|
|
154
|
-
* ()
|
|
155
|
-
* ()
|
|
156
|
-
* ()
|
|
157
|
-
* ()
|
|
154
|
+
* ()=> { LOG('Game initialized!'); }, // gameInit
|
|
155
|
+
* ()=> { updateGameLogic(); }, // gameUpdate
|
|
156
|
+
* ()=> { updateUI(); }, // gameUpdatePost
|
|
157
|
+
* ()=> { drawBackground(); }, // gameRender
|
|
158
|
+
* ()=> { drawHUD(); }, // gameRenderPost
|
|
158
159
|
* ['tiles.png', 'tilesLevel.png'] // images to load
|
|
159
160
|
* );
|
|
160
161
|
* @memberof Engine */
|
|
161
162
|
async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
162
163
|
{
|
|
164
|
+
showEngineVersion && console.log(`${engineName} Engine v${engineVersion}`);
|
|
163
165
|
ASSERT(!mainContext, 'engine already initialized');
|
|
164
166
|
ASSERT(isArray(imageSources), 'pass in images as array');
|
|
165
167
|
|
|
@@ -428,7 +430,6 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
428
430
|
promises.push(new Promise(resolve =>
|
|
429
431
|
{
|
|
430
432
|
let t = 0;
|
|
431
|
-
console.log(`${engineName} Engine v${engineVersion}`);
|
|
432
433
|
updateSplash();
|
|
433
434
|
function updateSplash()
|
|
434
435
|
{
|
|
@@ -460,13 +461,13 @@ function engineObjectsUpdate()
|
|
|
460
461
|
engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
|
|
461
462
|
|
|
462
463
|
// recursive object update
|
|
463
|
-
function
|
|
464
|
+
function updateChildObject(o)
|
|
464
465
|
{
|
|
465
466
|
if (o.destroyed) return;
|
|
466
467
|
|
|
467
468
|
o.update();
|
|
468
469
|
for (const child of o.children)
|
|
469
|
-
|
|
470
|
+
updateChildObject(child);
|
|
470
471
|
}
|
|
471
472
|
for (const o of engineObjects)
|
|
472
473
|
{
|
|
@@ -476,7 +477,7 @@ function engineObjectsUpdate()
|
|
|
476
477
|
o.update();
|
|
477
478
|
o.updatePhysics();
|
|
478
479
|
for (const child of o.children)
|
|
479
|
-
|
|
480
|
+
updateChildObject(child);
|
|
480
481
|
o.updateTransforms();
|
|
481
482
|
}
|
|
482
483
|
|
|
@@ -485,11 +486,14 @@ function engineObjectsUpdate()
|
|
|
485
486
|
}
|
|
486
487
|
|
|
487
488
|
/** Destroy and remove all objects
|
|
489
|
+
* - This can be used to clear out all objects when restarting a level
|
|
490
|
+
* - Objects can override their destroy function to do cleanup or stick around
|
|
491
|
+
* @param {boolean} [immediate] - should attached effects be allowed to die off?
|
|
488
492
|
* @memberof Engine */
|
|
489
|
-
function engineObjectsDestroy()
|
|
493
|
+
function engineObjectsDestroy(immediate=true)
|
|
490
494
|
{
|
|
491
495
|
for (const o of engineObjects)
|
|
492
|
-
o.parent || o.destroy();
|
|
496
|
+
o.parent || o.destroy(immediate);
|
|
493
497
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
494
498
|
}
|
|
495
499
|
|
|
@@ -564,164 +568,137 @@ function engineObjectsRaycast(start, end, objects=engineObjects)
|
|
|
564
568
|
///////////////////////////////////////////////////////////////////////////////
|
|
565
569
|
function drawEngineLogo(t)
|
|
566
570
|
{
|
|
571
|
+
const blackAndWhite = 0;
|
|
572
|
+
const showName = 1;
|
|
573
|
+
|
|
567
574
|
// LittleJS Logo and Splash Screen
|
|
568
575
|
const x = mainContext;
|
|
569
576
|
const w = mainCanvas.width = innerWidth;
|
|
570
577
|
const h = mainCanvas.height = innerHeight;
|
|
571
|
-
|
|
572
578
|
{
|
|
573
579
|
// background
|
|
574
580
|
const p3 = percent(t, 1, .8);
|
|
575
581
|
const p4 = percent(t, 0, .5);
|
|
576
|
-
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.
|
|
582
|
+
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.6);
|
|
577
583
|
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
578
584
|
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
579
585
|
x.save();
|
|
580
586
|
x.fillStyle = g;
|
|
581
587
|
x.fillRect(0,0,w,h);
|
|
582
588
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
589
|
+
const gradient = (X1,Y1,X2,Y2,C,S=1)=>
|
|
590
|
+
{
|
|
591
|
+
if (C >= 0)
|
|
592
|
+
{
|
|
593
|
+
if (blackAndWhite)
|
|
594
|
+
x.fillStyle = '#fff';
|
|
595
|
+
else
|
|
596
|
+
{
|
|
597
|
+
const g = x.fillStyle = x.createLinearGradient(X1,Y1,X2,Y2);
|
|
598
|
+
g.addColorStop(0,color(C,2));
|
|
599
|
+
g.addColorStop(1,color(C,1));
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
else
|
|
603
|
+
x.fillStyle = '#000';
|
|
604
|
+
C >= -1 ? (x.fill(), S && x.stroke()) : x.stroke();
|
|
605
|
+
}
|
|
606
|
+
const circle = (X,Y,R,A=0,B=2*PI,C,S)=>
|
|
586
607
|
{
|
|
587
608
|
x.beginPath();
|
|
588
|
-
x.
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
const line = (X, Y, Z, W)=>
|
|
609
|
+
x.arc(X,Y,R,p*A,p*B);
|
|
610
|
+
gradient(X,Y-R,X,Y+R,C,S);
|
|
611
|
+
}
|
|
612
|
+
const rect = (X,Y,W,H,C)=>
|
|
593
613
|
{
|
|
594
614
|
x.beginPath();
|
|
595
|
-
x.
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
|
|
615
|
+
x.rect(X,Y,W,H*p);
|
|
616
|
+
gradient(X,Y+H,X+W,Y,C);
|
|
617
|
+
}
|
|
618
|
+
const poly = (points,C,Y,H)=>
|
|
600
619
|
{
|
|
601
|
-
const D = (A+B)/2, E = p*(B-A)/2;
|
|
602
620
|
x.beginPath();
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
x.
|
|
606
|
-
|
|
607
|
-
}
|
|
608
|
-
const color = (c
|
|
609
|
-
|
|
621
|
+
for (const p of points)
|
|
622
|
+
x.lineTo(p.x, p.y);
|
|
623
|
+
x.closePath();
|
|
624
|
+
gradient(0, Y, 0, Y+H,C);
|
|
625
|
+
}
|
|
626
|
+
const color = (c,l)=> l?`hsl(${[.95,.56,.13][c%3]*360} 99%${[0,50,75][l]}%`:'#000';
|
|
627
|
+
|
|
628
|
+
// center and fit tos screen
|
|
610
629
|
const alpha = wave(1,1,t);
|
|
611
630
|
const p = percent(alpha, .1, .5);
|
|
612
|
-
|
|
613
|
-
// setup
|
|
631
|
+
const size = min(6, min(w,h)/99);
|
|
614
632
|
x.translate(w/2,h/2);
|
|
615
|
-
const size = min(6, min(w,h)/99); // fit to screen
|
|
616
633
|
x.scale(size,size);
|
|
617
634
|
x.translate(-40,-35);
|
|
635
|
+
p < 1 && x.setLineDash([99*p,99]);
|
|
618
636
|
x.lineJoin = x.lineCap = 'round';
|
|
619
637
|
x.lineWidth = .1 + p*1.9;
|
|
638
|
+
//x.strokeStyle='#fff7';
|
|
639
|
+
|
|
640
|
+
if (showName)
|
|
641
|
+
{
|
|
642
|
+
// engine name text
|
|
643
|
+
const Y = 54;
|
|
644
|
+
const s = 'LittleJS';
|
|
645
|
+
x.font = '900 15.5px arial';
|
|
646
|
+
x.lineWidth = .1+p*3.9;
|
|
647
|
+
x.textAlign = 'center';
|
|
648
|
+
x.textBaseline = 'top';
|
|
649
|
+
rect(11,Y+2,59,8*p,-1);
|
|
650
|
+
x.beginPath();
|
|
651
|
+
|
|
652
|
+
let w2 = 0;
|
|
653
|
+
for (let i=0;i<s.length;++i)
|
|
654
|
+
w2 += x.measureText(s[i]).width;
|
|
655
|
+
for (let j=2;j--;)
|
|
656
|
+
for (let i=0,X=40-w2/2;i<s.length;++i)
|
|
657
|
+
{
|
|
658
|
+
const w = x.measureText(s[i]).width, X2 = X+w/2;
|
|
659
|
+
gradient(X2,Y,X2+2,Y+13,i>5?1:0);
|
|
660
|
+
x[j?'strokeText':'fillText'](s[i],X2,Y+.5,17*p);
|
|
661
|
+
X += w;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
x.lineWidth = .1 + p*1.9;
|
|
665
|
+
rect(3,Y,73,0); // bottom
|
|
666
|
+
}
|
|
620
667
|
|
|
621
|
-
//
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
//
|
|
626
|
-
rect(7,16,18,-8,color(2,2));
|
|
627
|
-
rect(7,8,18,4,color(2,3));
|
|
628
|
-
rect(25,8,8,8,color(2,1));
|
|
629
|
-
rect(25,8,-18,8);
|
|
630
|
-
rect(25,8,8,8);
|
|
631
|
-
|
|
632
|
-
// cab
|
|
633
|
-
rect(25,16,7,24,color());
|
|
634
|
-
rect(11,39,14,-23,color(1,1));
|
|
635
|
-
rect(11,16,14,18,color(1,2));
|
|
636
|
-
rect(11,16,14,8,color(1,3));
|
|
637
|
-
rect(25,16,-14,24);
|
|
638
|
-
|
|
639
|
-
// cab window
|
|
640
|
-
rect(15,29,6,-9,color(2,2));
|
|
641
|
-
circle(15,21,5,0,PI/2,color(2,4),1);
|
|
642
|
-
rect(21,21,-6,9);
|
|
643
|
-
|
|
644
|
-
// little stack
|
|
645
|
-
rect(37,14,9,6,color(3,2));
|
|
646
|
-
rect(37,14,4.5,6,color(3,3));
|
|
647
|
-
rect(37,14,9,6);
|
|
668
|
+
rect(7,15,26,-7,0); // cab top
|
|
669
|
+
rect(25,15,8,25,-1); // cab front
|
|
670
|
+
rect(10,40,15,-25,1); // cab back
|
|
671
|
+
rect(14,21,7,9,2); // cab window
|
|
672
|
+
rect(38,15,6,6,2); // little stack
|
|
648
673
|
|
|
649
674
|
// big stack
|
|
650
|
-
rect(
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
rect(
|
|
654
|
-
circle(55,2,11.4,.5,PI-.5,color(3,3));
|
|
655
|
-
circle(55,2,11.4,.5,PI/2,color(3,2),1);
|
|
656
|
-
circle(55,2,11.4,.5,PI-.5);
|
|
657
|
-
rect(45,7,20,-7,color(0,2));
|
|
658
|
-
rect(45,-1,20,4,color(0,3));
|
|
659
|
-
rect(45,-1,20,8);
|
|
675
|
+
rect(49,20,10,-6,0);
|
|
676
|
+
const stackPoints = [vec2(44,8),vec2(64,8),vec2(59,8+6*p),vec2(49,8+6*p)];
|
|
677
|
+
poly(stackPoints,2,8,6*p);
|
|
678
|
+
rect(44,8,20,-7,0);
|
|
660
679
|
|
|
661
680
|
// engine
|
|
662
|
-
for (let i=5;
|
|
663
|
-
|
|
664
|
-
// stagger radius to fix slight seam
|
|
665
|
-
circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
|
|
666
|
-
circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
|
|
667
|
-
circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
|
|
668
|
-
}
|
|
681
|
+
for (let i=5;i--;) circle(59-i*6,30,10,0,7,1,0);
|
|
682
|
+
circle(59,30,4,0,7,2); // light
|
|
669
683
|
|
|
670
684
|
// engine outline
|
|
671
|
-
|
|
672
|
-
circle(
|
|
673
|
-
circle(
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
//
|
|
677
|
-
circle(60,30,4,PI,3*PI,color(3,2));
|
|
678
|
-
circle(60,30,4,PI,2*PI,color(3,3));
|
|
679
|
-
circle(60,30,4,PI,3*PI);
|
|
680
|
-
|
|
681
|
-
// front brush
|
|
682
|
-
for (let i=6; i--;)
|
|
683
|
-
{
|
|
684
|
-
x.beginPath();
|
|
685
|
-
x.lineTo(53,54);
|
|
686
|
-
x.lineTo(53,40);
|
|
687
|
-
x.lineTo(53+(1+i*2.9)*p,40);
|
|
688
|
-
x.lineTo(53+(4+i*3.5)*p,54);
|
|
689
|
-
x.fillStyle = color(0,i%2+2);
|
|
690
|
-
x.fill();
|
|
691
|
-
i%2 && x.stroke();
|
|
692
|
-
}
|
|
685
|
+
rect(35,20,24,0); // top
|
|
686
|
+
circle(59,30,10); // front
|
|
687
|
+
circle(47,30,10,PI/2,PI*3/2); // middle
|
|
688
|
+
circle(35,30,10,PI/2,PI*3/2); // back
|
|
689
|
+
rect(7,40,13,7,-1); // bottom back
|
|
690
|
+
rect(17,40,43,14,-1); // bottom center
|
|
693
691
|
|
|
694
692
|
// wheels
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
for (let i=
|
|
699
|
-
for (let j=2; j--;)
|
|
700
|
-
{
|
|
701
|
-
circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
|
|
702
|
-
x.stroke();
|
|
703
|
-
circle(15*i+15,47,j?7:1,0,PI,color(i,2));
|
|
704
|
-
x.stroke();
|
|
705
|
-
}
|
|
706
|
-
line(6,40,68,40); // center
|
|
707
|
-
line(77,54,4,54); // bottom
|
|
708
|
-
|
|
709
|
-
// draw engine name
|
|
710
|
-
const s = engineName;
|
|
711
|
-
x.font = '900 16px arial';
|
|
712
|
-
x.textAlign = 'center';
|
|
713
|
-
x.textBaseline = 'top';
|
|
714
|
-
x.lineWidth = .1+p*3.9;
|
|
715
|
-
let w2 = 0;
|
|
716
|
-
for (let i=0; i<s.length; ++i)
|
|
717
|
-
w2 += x.measureText(s[i]).width;
|
|
718
|
-
for (let j=2; j--;)
|
|
719
|
-
for (let i=0, X=41-w2/2; i<s.length; ++i)
|
|
693
|
+
for (let i=3;i--;) for (let j=2;j--;) circle(15*i+17,47,j?7:1,0,7,2);
|
|
694
|
+
|
|
695
|
+
// cowcatcher
|
|
696
|
+
for (let i=2;i--;)
|
|
720
697
|
{
|
|
721
|
-
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
X += w;
|
|
698
|
+
let w=6, s=7, o=53+w*p*i
|
|
699
|
+
const points = [vec2(o+s,54),vec2(o,40),vec2(o+w*p,40),vec2(o+s+w*p,54)];
|
|
700
|
+
poly(points,0,40,14);
|
|
725
701
|
}
|
|
702
|
+
|
|
726
703
|
x.restore();
|
|
727
704
|
}
|
package/src/engineAudio.js
CHANGED
|
@@ -44,29 +44,46 @@ function audioInit()
|
|
|
44
44
|
///////////////////////////////////////////////////////////////////////////////
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* Sound Object - Stores a sound for later
|
|
47
|
+
* Sound Object - Stores a sound for later
|
|
48
|
+
* - this can be used to load and play wave, mp3, and ogg files
|
|
49
|
+
* - it can also create sounds using the ZzFX sound generator
|
|
50
|
+
* - can attenuate and apply stereo panning to sounds
|
|
51
|
+
* - sound instance control with pause/resume capability
|
|
48
52
|
*
|
|
49
53
|
* <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
|
|
50
54
|
* @memberof Audio
|
|
51
55
|
* @example
|
|
52
|
-
* //
|
|
56
|
+
* // load an audio asset file
|
|
57
|
+
* const sound_example = new Sound('sound.mp3');
|
|
58
|
+
*
|
|
59
|
+
* // create a zzfx sound
|
|
53
60
|
* const sound_example = new Sound([.5,.5]);
|
|
54
61
|
*
|
|
55
|
-
* // play
|
|
62
|
+
* // play a sound
|
|
56
63
|
* sound_example.play();
|
|
57
64
|
*/
|
|
58
65
|
class Sound
|
|
59
66
|
{
|
|
60
|
-
/**
|
|
61
|
-
*
|
|
67
|
+
/**
|
|
68
|
+
* @callback SoundLoadCallback - Function called when sound is loaded
|
|
69
|
+
* @param {Sound} sound
|
|
70
|
+
* @memberof Audio
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/** Create a sound object and cache the audio for later use
|
|
74
|
+
* @param {string|Array} [asset] - Filename of audio file or zzfx array
|
|
75
|
+
* @param {number} [randomness] - How much to randomize frequency each time sound plays, for zzfx sounds the zzfx default is used if undefined
|
|
62
76
|
* @param {number} [range=soundDefaultRange] - World space max range of sound
|
|
63
77
|
* @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
|
|
78
|
+
* @param {SoundLoadCallback} [onloadCallback] - callback function to call when sound is loaded
|
|
64
79
|
*/
|
|
65
|
-
constructor(
|
|
80
|
+
constructor(asset, randomness, range=soundDefaultRange, taper=soundDefaultTaper, onloadCallback)
|
|
66
81
|
{
|
|
67
82
|
if (!soundEnable || headlessMode) return;
|
|
68
83
|
|
|
69
|
-
ASSERT(!
|
|
84
|
+
ASSERT(!asset || isArray(asset) || isString(asset), 'asset must be a file name or zzfx array');
|
|
85
|
+
ASSERT(randomness === undefined || isNumber(randomness), 'randomness must be a number');
|
|
86
|
+
ASSERT(randomness === undefined || randomness >= 0 && randomness <=1, 'randomness must be between 0 and 1');
|
|
70
87
|
ASSERT(isNumber(range), 'range must be a number');
|
|
71
88
|
ASSERT(isNumber(taper), 'taper must be a number');
|
|
72
89
|
|
|
@@ -75,23 +92,35 @@ class Sound
|
|
|
75
92
|
/** @property {number} - At what percentage of range should it start tapering */
|
|
76
93
|
this.taper = taper;
|
|
77
94
|
/** @property {number} - How much to randomize frequency each time sound plays */
|
|
78
|
-
this.randomness = 0;
|
|
95
|
+
this.randomness = randomness ?? 0;
|
|
79
96
|
/** @property {number} - Sample rate for this sound */
|
|
80
97
|
this.sampleRate = audioDefaultSampleRate;
|
|
81
98
|
/** @property {number} - Percentage of this sound currently loaded */
|
|
82
99
|
this.loadedPercent = 0;
|
|
100
|
+
/** @property {SoundLoadCallback} - function to call when sound is loaded */
|
|
101
|
+
this.onloadCallback = onloadCallback;
|
|
83
102
|
|
|
84
|
-
|
|
85
|
-
if (zzfxSound)
|
|
103
|
+
if (Array.isArray(asset))
|
|
86
104
|
{
|
|
105
|
+
// generate zzfx sound
|
|
106
|
+
const zzfxSound = asset;
|
|
107
|
+
|
|
87
108
|
// remove randomness so it can be applied on playback
|
|
88
|
-
const
|
|
109
|
+
const defaultRandomness = randomness ?? .05;
|
|
110
|
+
const randomnessIndex = 1;
|
|
89
111
|
this.randomness = zzfxSound[randomnessIndex] ?? defaultRandomness;
|
|
90
112
|
zzfxSound[randomnessIndex] = 0;
|
|
91
113
|
|
|
92
114
|
// generate the zzfx samples
|
|
93
115
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
94
116
|
this.loadedPercent = 1;
|
|
117
|
+
onloadCallback?.(this);
|
|
118
|
+
}
|
|
119
|
+
else if (typeof asset === 'string')
|
|
120
|
+
{
|
|
121
|
+
// load the audio file
|
|
122
|
+
const filename = asset;
|
|
123
|
+
this.loadSound(filename);
|
|
95
124
|
}
|
|
96
125
|
}
|
|
97
126
|
|
|
@@ -143,7 +172,7 @@ class Sound
|
|
|
143
172
|
* @param {number} [volume] - Volume to play the music at
|
|
144
173
|
* @param {boolean} [loop] - Should the music loop?
|
|
145
174
|
* @param {boolean} [paused] - Should the music start paused
|
|
146
|
-
* @return {SoundInstance} - The
|
|
175
|
+
* @return {SoundInstance} - The sound instance
|
|
147
176
|
*/
|
|
148
177
|
playMusic(volume=1, loop=true, paused=false)
|
|
149
178
|
{ return this.play(undefined, volume, 1, 0, loop, paused); }
|
|
@@ -153,7 +182,7 @@ class Sound
|
|
|
153
182
|
* @param {number} [semitoneOffset=0] - How many semitones to offset pitch
|
|
154
183
|
* @param {Vector2} [pos] - World space position to play the sound if any
|
|
155
184
|
* @param {number} [volume=1] - How much to scale volume by
|
|
156
|
-
* @return {SoundInstance} - The
|
|
185
|
+
* @return {SoundInstance} - The sound instance
|
|
157
186
|
*/
|
|
158
187
|
playNote(semitoneOffset=0, pos, volume)
|
|
159
188
|
{
|
|
@@ -166,57 +195,14 @@ class Sound
|
|
|
166
195
|
* @return {number} - How long the sound is in seconds (undefined if loading)
|
|
167
196
|
*/
|
|
168
197
|
getDuration()
|
|
169
|
-
{ return this.sampleChannels?.[0]
|
|
198
|
+
{ return this.sampleChannels?.[0]?.length / this.sampleRate || 0; }
|
|
170
199
|
|
|
171
200
|
/** Check if sound is loaded, for sounds fetched from a url
|
|
172
201
|
* @return {boolean} - True if sound is loaded and ready to play
|
|
173
202
|
*/
|
|
174
203
|
isLoaded() { return this.loadedPercent === 1; }
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Sound Wave Object - Loads and stores an audio file for later use
|
|
181
|
-
* - this can be used to load and play wave, mp3, and ogg files
|
|
182
|
-
* @extends Sound
|
|
183
|
-
* @memberof Audio
|
|
184
|
-
* @example
|
|
185
|
-
* // load an audio asset file
|
|
186
|
-
* const sound_example = new SoundWave('sound.mp3');
|
|
187
|
-
*
|
|
188
|
-
* // play the sound
|
|
189
|
-
* sound_example.play();
|
|
190
|
-
*/
|
|
191
|
-
class SoundWave extends Sound
|
|
192
|
-
{
|
|
193
|
-
/**
|
|
194
|
-
* @callback SoundLoadCallback - Function called when sound is loaded
|
|
195
|
-
* @param {SoundWave} sound
|
|
196
|
-
* @memberof Audio
|
|
197
|
-
*/
|
|
198
204
|
|
|
199
|
-
/**
|
|
200
|
-
* @param {string} filename - Filename of audio file to load
|
|
201
|
-
* @param {number} [randomness] - How much to randomize frequency each time sound plays
|
|
202
|
-
* @param {number} [range=soundDefaultRange] - World space max range of sound
|
|
203
|
-
* @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
|
|
204
|
-
* @param {SoundLoadCallback} [onloadCallback] - callback function to call when sound is loaded
|
|
205
|
-
*/
|
|
206
|
-
constructor(filename, randomness=0, range, taper, onloadCallback)
|
|
207
|
-
{
|
|
208
|
-
super(undefined, range, taper);
|
|
209
|
-
if (!soundEnable || headlessMode) return;
|
|
210
|
-
ASSERT(!filename || isString(filename), 'filename must be a string');
|
|
211
|
-
ASSERT(isNumber(randomness), 'randomness must be a number');
|
|
212
|
-
|
|
213
|
-
/** @property {SoundLoadCallback} - callback function to call when sound is loaded */
|
|
214
|
-
this.onloadCallback = onloadCallback;
|
|
215
|
-
this.randomness = randomness;
|
|
216
|
-
filename && this.loadSound(filename);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/** Loads a sound from a URL and decodes it into sample data. Must be used with await!
|
|
205
|
+
/** Loads a sound from a URL and decodes it into sample data.
|
|
220
206
|
* @param {string} filename
|
|
221
207
|
* @return {Promise<void>} */
|
|
222
208
|
async loadSound(filename)
|
package/src/engineDebug.js
CHANGED
|
@@ -49,7 +49,7 @@ let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParti
|
|
|
49
49
|
/** Asserts if the expression is false, does nothing in release builds
|
|
50
50
|
* Halts execution if the assert fails and throws an error
|
|
51
51
|
* @param {boolean} assert
|
|
52
|
-
* @param {...Object}
|
|
52
|
+
* @param {...Object} output - error message output
|
|
53
53
|
* @memberof Debug */
|
|
54
54
|
function ASSERT(assert, ...output)
|
|
55
55
|
{
|
|
@@ -59,7 +59,7 @@ function ASSERT(assert, ...output)
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/** Log to console if debug is enabled, does nothing in release builds
|
|
62
|
-
* @param {...Object}
|
|
62
|
+
* @param {...Object} output - message output
|
|
63
63
|
* @memberof Debug */
|
|
64
64
|
function LOG(...output) { console.log(...output); }
|
|
65
65
|
|
|
@@ -706,8 +706,8 @@ function debugProtectConstant(obj)
|
|
|
706
706
|
props.forEach(prop =>
|
|
707
707
|
{
|
|
708
708
|
Object.defineProperty(obj, prop, {
|
|
709
|
-
get: ()
|
|
710
|
-
set: (value)
|
|
709
|
+
get: ()=> values[prop],
|
|
710
|
+
set: (value)=>
|
|
711
711
|
{
|
|
712
712
|
ASSERT(false, `Cannot modify engine constant. Attempted to set constant (${obj}) property '${prop}' to '${value}'.`);
|
|
713
713
|
},
|
package/src/engineDraw.js
CHANGED
|
@@ -190,7 +190,7 @@ class TileInfo
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
/**
|
|
193
|
-
* Returns a tile info for an index using this tile as
|
|
193
|
+
* Returns a tile info for an index using this tile as reference
|
|
194
194
|
* @param {Vector2|number} [index=0]
|
|
195
195
|
* @return {TileInfo}
|
|
196
196
|
*/
|
|
@@ -258,7 +258,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=WHITE,
|
|
|
258
258
|
ASSERT(!additiveColor || isColor(additiveColor), 'additiveColor must be a color');
|
|
259
259
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
260
260
|
|
|
261
|
-
const textureInfo = tileInfo
|
|
261
|
+
const textureInfo = tileInfo?.textureInfo;
|
|
262
262
|
const bleed = tileInfo?.bleed ?? 0;
|
|
263
263
|
if (useWebGL && glEnable)
|
|
264
264
|
{
|
|
@@ -564,9 +564,12 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
|
|
|
564
564
|
ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
|
|
565
565
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
566
566
|
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
567
|
-
ASSERT(lineWidth >= 0
|
|
567
|
+
ASSERT(lineWidth >= 0, 'lineWidth must be a positive value or 0');
|
|
568
568
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
569
569
|
|
|
570
|
+
// clamp line width to prevent artifacts
|
|
571
|
+
lineWidth = clamp(lineWidth, 0, Math.min(size.x, size.y));
|
|
572
|
+
|
|
570
573
|
if (useWebGL && glEnable)
|
|
571
574
|
{
|
|
572
575
|
// draw as a regular polygon
|
|
@@ -1017,7 +1020,7 @@ let engineFontImage;
|
|
|
1017
1020
|
class FontImage
|
|
1018
1021
|
{
|
|
1019
1022
|
/** Create an image font
|
|
1020
|
-
* @param {TileInfo} tileInfo - Tile info of first
|
|
1023
|
+
* @param {TileInfo} tileInfo - Tile info of first character in font
|
|
1021
1024
|
*/
|
|
1022
1025
|
constructor(tileInfo)
|
|
1023
1026
|
{
|
package/src/engineExport.js
CHANGED
|
@@ -177,6 +177,8 @@ export
|
|
|
177
177
|
saveCanvas,
|
|
178
178
|
saveDataURL,
|
|
179
179
|
shareURL,
|
|
180
|
+
readSaveData,
|
|
181
|
+
writeSaveData,
|
|
180
182
|
|
|
181
183
|
// Random
|
|
182
184
|
rand,
|
|
@@ -315,7 +317,6 @@ export
|
|
|
315
317
|
audioMasterGain,
|
|
316
318
|
audioDefaultSampleRate,
|
|
317
319
|
Sound,
|
|
318
|
-
SoundWave,
|
|
319
320
|
SoundInstance,
|
|
320
321
|
speak,
|
|
321
322
|
speakStop,
|
package/src/engineInput.js
CHANGED
|
@@ -408,9 +408,9 @@ function inputInit()
|
|
|
408
408
|
function touchInputInit()
|
|
409
409
|
{
|
|
410
410
|
// add non passive touch event listeners
|
|
411
|
-
document.addEventListener('touchstart', (e)
|
|
412
|
-
document.addEventListener('touchmove', (e)
|
|
413
|
-
document.addEventListener('touchend', (e)
|
|
411
|
+
document.addEventListener('touchstart', (e)=> handleTouch(e), { passive: false });
|
|
412
|
+
document.addEventListener('touchmove', (e)=> handleTouch(e), { passive: false });
|
|
413
|
+
document.addEventListener('touchend', (e)=> handleTouch(e), { passive: false });
|
|
414
414
|
|
|
415
415
|
// handle all touch events the same way
|
|
416
416
|
let wasTouching;
|
|
@@ -616,7 +616,7 @@ function inputUpdate()
|
|
|
616
616
|
// poll gamepads
|
|
617
617
|
const maxGamepads = 8;
|
|
618
618
|
const gamepads = navigator.getGamepads();
|
|
619
|
-
const gamepadCount = min(maxGamepads, gamepads.length)
|
|
619
|
+
const gamepadCount = min(maxGamepads, gamepads.length);
|
|
620
620
|
for (let i=0; i<gamepadCount; ++i)
|
|
621
621
|
{
|
|
622
622
|
// get or create gamepad data
|
|
@@ -769,7 +769,7 @@ function inputRender()
|
|
|
769
769
|
}
|
|
770
770
|
}
|
|
771
771
|
|
|
772
|
-
// center position for right
|
|
772
|
+
// center position for right touch pad face buttons
|
|
773
773
|
function touchGamepadButtonCenter()
|
|
774
774
|
{
|
|
775
775
|
const center = mainCanvasSize.subtract(vec2(touchGamepadSize));
|