littlejsengine 1.16.1 → 1.17.1
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/dist/littlejs.d.ts +287 -240
- package/dist/littlejs.esm.js +1419 -1259
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +1076 -910
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +977 -815
- package/examples/box2d/gameObjects.js +6 -6
- package/examples/breakout/game.js +5 -6
- package/examples/electron/index.html +2 -2
- package/examples/electron/tiles.png +0 -0
- package/examples/htmlMenu/tiles.png +0 -0
- package/examples/index.html +1 -1
- package/examples/logo.png +0 -0
- package/examples/logo2.png +0 -0
- package/examples/module/tiles.png +0 -0
- package/examples/platformer/gameEffects.js +24 -23
- package/examples/platformer/gameLevel.js +24 -25
- package/examples/shorts/base.html +2 -1
- package/examples/shorts/clock.js +3 -3
- package/examples/shorts/fontImage.js +4 -3
- package/examples/shorts/parallax.js +1 -1
- package/examples/shorts/sequencer.js +1 -1
- package/examples/shorts/shapes.js +1 -1
- package/examples/shorts/texture.js +10 -6
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/tiltedView.js +2 -0
- package/examples/starter/index.html +3 -2
- package/examples/starter/tiles.png +0 -0
- package/examples/typescript/tiles.png +0 -0
- package/examples/uiSystem/game.js +1 -1
- package/examples/uiSystem/tiles.png +0 -0
- package/package.json +1 -1
- package/plugins/postProcess.js +47 -28
- package/plugins/uiSystem.js +16 -16
- package/reference.md +6 -6
- package/src/engine.js +186 -198
- package/src/engineAudio.js +6 -10
- package/src/engineBuild.js +1 -0
- package/src/engineDebug.js +100 -98
- package/src/engineDraw.js +185 -148
- package/src/engineExport.js +327 -333
- package/src/engineFont.png +0 -0
- package/src/engineInput.js +17 -26
- package/src/engineMath.js +1112 -0
- package/src/engineMedals.js +4 -4
- package/src/engineObject.js +27 -27
- package/src/engineParticles.js +2 -2
- package/src/engineRelease.js +1 -3
- package/src/engineSettings.js +2 -2
- package/src/engineTileLayer.js +200 -176
- package/src/engineUtilities.js +48 -1097
- package/src/engineWebGL.js +127 -79
package/plugins/uiSystem.js
CHANGED
|
@@ -512,7 +512,7 @@ class UISystemPlugin
|
|
|
512
512
|
const up = 'ArrowUp', down = 'ArrowDown', left = 'ArrowLeft', right = 'ArrowRight';
|
|
513
513
|
if (both)
|
|
514
514
|
{
|
|
515
|
-
return keyIsDown(up) || keyIsDown(left) ? -1 :
|
|
515
|
+
return keyIsDown(up) || keyIsDown(left) ? -1 :
|
|
516
516
|
keyIsDown(down) || keyIsDown(right) ? 1 : 0;
|
|
517
517
|
}
|
|
518
518
|
const back = vertical ? up : left;
|
|
@@ -543,7 +543,7 @@ class UISystemPlugin
|
|
|
543
543
|
* @return {boolean} */
|
|
544
544
|
getNavigationWasPressed()
|
|
545
545
|
{
|
|
546
|
-
return isUsingGamepad ? gamepadWasPressed(0, gamepadPrimary) :
|
|
546
|
+
return isUsingGamepad ? gamepadWasPressed(0, gamepadPrimary) :
|
|
547
547
|
keyWasPressed('Space') || keyWasPressed('Enter');
|
|
548
548
|
}
|
|
549
549
|
|
|
@@ -591,7 +591,7 @@ class UISystemPlugin
|
|
|
591
591
|
buttonYes.textHeight = 40;
|
|
592
592
|
buttonYes.navigationIndex = 1;
|
|
593
593
|
buttonYes.hoverColor = hsl(0,1,.5);
|
|
594
|
-
buttonYes.onClick = ()=> { closeMenu(); yesCallback && yesCallback(); };
|
|
594
|
+
buttonYes.onClick = ()=> { closeMenu(); yesCallback && yesCallback(); };
|
|
595
595
|
confirmMenu.addChild(buttonYes);
|
|
596
596
|
|
|
597
597
|
// no button
|
|
@@ -621,8 +621,8 @@ class UISystemPlugin
|
|
|
621
621
|
class UIObject
|
|
622
622
|
{
|
|
623
623
|
/** Create a UIObject
|
|
624
|
-
* @param {Vector2} [pos=(
|
|
625
|
-
* @param {Vector2} [size=(1
|
|
624
|
+
* @param {Vector2} [pos=vec2()]
|
|
625
|
+
* @param {Vector2} [size=vec2(1)]
|
|
626
626
|
*/
|
|
627
627
|
constructor(pos=vec2(), size=vec2())
|
|
628
628
|
{
|
|
@@ -637,7 +637,7 @@ class UIObject
|
|
|
637
637
|
this.size = size.copy();
|
|
638
638
|
/** @property {Color} - Color of the object */
|
|
639
639
|
this.color = uiSystem.defaultColor.copy();
|
|
640
|
-
/** @property {Color} - Color of the object when active, uses
|
|
640
|
+
/** @property {Color} - Color of the object when active, uses hoverColor if undefined */
|
|
641
641
|
this.activeColor = undefined;
|
|
642
642
|
/** @property {string} - Text for this ui object */
|
|
643
643
|
this.text = undefined;
|
|
@@ -735,10 +735,10 @@ class UIObject
|
|
|
735
735
|
|
|
736
736
|
// disconnect from parent and destroy children
|
|
737
737
|
this.destroyed = 1;
|
|
738
|
-
this.parent
|
|
738
|
+
this.parent?.removeChild(this);
|
|
739
739
|
for (const child of this.children)
|
|
740
740
|
{
|
|
741
|
-
child.parent =
|
|
741
|
+
child.parent = undefined;
|
|
742
742
|
child.destroy();
|
|
743
743
|
}
|
|
744
744
|
}
|
|
@@ -762,7 +762,7 @@ class UIObject
|
|
|
762
762
|
this.onUpdate();
|
|
763
763
|
|
|
764
764
|
// unset active if disabled
|
|
765
|
-
if (this.disabled && this
|
|
765
|
+
if (this.disabled && this === uiSystem.activeObject)
|
|
766
766
|
uiSystem.activeObject = undefined;
|
|
767
767
|
|
|
768
768
|
const wasHover = uiSystem.lastHoverObject === this;
|
|
@@ -834,10 +834,10 @@ class UIObject
|
|
|
834
834
|
this.interactive && this.isActiveObject() && !this.disabled ?
|
|
835
835
|
this.color : this.lineColor;
|
|
836
836
|
const color = isNavigationObject ? this.hoverColor :
|
|
837
|
-
this.disabled ? this.disabledColor :
|
|
838
|
-
this.interactive ?
|
|
839
|
-
this.
|
|
840
|
-
this.
|
|
837
|
+
this.disabled ? this.disabledColor :
|
|
838
|
+
this.interactive ?
|
|
839
|
+
this.isActiveObject() ? this.activeColor || this.hoverColor :
|
|
840
|
+
this.isHoverObject() ? this.hoverColor :
|
|
841
841
|
this.color : this.color;
|
|
842
842
|
const lineWidth = this.lineWidth * (isNavigationObject ? 1.5 : 1);
|
|
843
843
|
|
|
@@ -849,7 +849,7 @@ class UIObject
|
|
|
849
849
|
getTextSize()
|
|
850
850
|
{
|
|
851
851
|
return vec2(
|
|
852
|
-
this.textWidth || this.textFitScale * this.size.x,
|
|
852
|
+
this.textWidth || this.textFitScale * this.size.x,
|
|
853
853
|
this.textHeight || this.textFitScale * this.size.y);
|
|
854
854
|
}
|
|
855
855
|
|
|
@@ -897,9 +897,9 @@ class UIObject
|
|
|
897
897
|
renderDebug(visible=true)
|
|
898
898
|
{
|
|
899
899
|
// apply color based on state
|
|
900
|
-
const color =
|
|
900
|
+
const color =
|
|
901
901
|
!visible ? GREEN :
|
|
902
|
-
this.isHoverObject() ? YELLOW :
|
|
902
|
+
this.isHoverObject() ? YELLOW :
|
|
903
903
|
this.disabled ? PURPLE :
|
|
904
904
|
this.interactive ? RED : BLUE;
|
|
905
905
|
uiSystem.drawRect(this.pos, this.size, CLEAR_BLACK, 4, color);
|
package/reference.md
CHANGED
|
@@ -375,7 +375,7 @@ CanvasLayer(position, size) // Create a canvas layer object
|
|
|
375
375
|
CanvasLayer.canvas // The canvas used by this layer
|
|
376
376
|
CanvasLayer.context // The 2D context of the canvas
|
|
377
377
|
CanvasLayer.getImageData() // Get image data from canvas
|
|
378
|
-
CanvasLayer.
|
|
378
|
+
CanvasLayer.updateWebGL() // Creates or updates WebGL texture
|
|
379
379
|
|
|
380
380
|
// LittleJS Layer System
|
|
381
381
|
TileLayer(position, size, tileInfo, scale) // Create a tile layer object
|
|
@@ -428,11 +428,11 @@ debugCircle(pos, size, color='#fff', time=0, fill) // Draw debug circle
|
|
|
428
428
|
debugPoint(pos, color, time, angle) // Draw debug point
|
|
429
429
|
debugLine(posA, posB, color, width=.1, time) // Draw debug line
|
|
430
430
|
debugText(text, pos, size=1, color='#fff', time=0, angle=0) // Draw debug text
|
|
431
|
-
debugAABB(pA, sA, pB, sB, color)
|
|
432
|
-
debugClear()
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
431
|
+
debugAABB(pA, sA, pB, sB, color) // Draw a debug axis aligned box
|
|
432
|
+
debugClear() // Clear all debug primitives
|
|
433
|
+
saveCanvas(canvas, filename) // Save canvas to a file
|
|
434
|
+
saveText(text, filename) // Save text to a file
|
|
435
|
+
saveDataURL(dataURL, filename) // Save url to a file
|
|
436
436
|
|
|
437
437
|
// Debug settings
|
|
438
438
|
debug // Is debug enabled?
|
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.
|
|
33
|
+
const engineVersion = '1.17.1';
|
|
34
34
|
|
|
35
35
|
/** Frames per second to update
|
|
36
36
|
* @type {number}
|
|
@@ -269,33 +269,20 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
269
269
|
if (!wasUpdated)
|
|
270
270
|
updateCanvas();
|
|
271
271
|
|
|
272
|
-
// render
|
|
272
|
+
// render the game and objects
|
|
273
273
|
enginePreRender();
|
|
274
274
|
gameRender();
|
|
275
275
|
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
276
276
|
for (const o of engineObjects)
|
|
277
277
|
o.destroyed || o.render();
|
|
278
|
+
|
|
279
|
+
// post rendering
|
|
278
280
|
gameRenderPost();
|
|
279
281
|
pluginList.forEach(plugin=>plugin.render?.());
|
|
280
282
|
inputRender();
|
|
281
283
|
debugRender();
|
|
282
284
|
glFlush();
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (debugWatermark && !debugVideoCaptureIsActive())
|
|
286
|
-
{
|
|
287
|
-
// update fps display
|
|
288
|
-
mainContext.textAlign = 'right';
|
|
289
|
-
mainContext.textBaseline = 'top';
|
|
290
|
-
mainContext.font = '1em monospace';
|
|
291
|
-
mainContext.fillStyle = '#000';
|
|
292
|
-
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
293
|
-
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
294
|
-
+ (glEnable ? ' GL' : ' 2D') ;
|
|
295
|
-
mainContext.fillText(text, mainCanvas.width-3, 3);
|
|
296
|
-
mainContext.fillStyle = '#fff';
|
|
297
|
-
mainContext.fillText(text, mainCanvas.width-2, 2);
|
|
298
|
-
}
|
|
285
|
+
debugRenderPost();
|
|
299
286
|
drawCount = 0;
|
|
300
287
|
}
|
|
301
288
|
}
|
|
@@ -330,7 +317,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
330
317
|
|
|
331
318
|
// responsive aspect ratio with native resolution
|
|
332
319
|
const innerAspect = innerWidth / innerHeight;
|
|
333
|
-
ASSERT(canvasMinAspect <= canvasMaxAspect);
|
|
320
|
+
ASSERT(canvasMinAspect <= canvasMaxAspect);
|
|
334
321
|
if (canvasMaxAspect && innerAspect > canvasMaxAspect)
|
|
335
322
|
{
|
|
336
323
|
// full height
|
|
@@ -361,15 +348,9 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
361
348
|
mainContext.lineJoin = 'round';
|
|
362
349
|
mainContext.lineCap = 'round';
|
|
363
350
|
}
|
|
364
|
-
|
|
365
|
-
//
|
|
366
|
-
|
|
367
|
-
{
|
|
368
|
-
await gameInit();
|
|
369
|
-
engineUpdate();
|
|
370
|
-
}
|
|
371
|
-
if (headlessMode)
|
|
372
|
-
return startEngine();
|
|
351
|
+
|
|
352
|
+
// skip setup if headless
|
|
353
|
+
if (headlessMode) return startEngine();
|
|
373
354
|
|
|
374
355
|
// setup webgl
|
|
375
356
|
glInit(rootElement);
|
|
@@ -384,7 +365,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
384
365
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
385
366
|
'-webkit-touch-callout:none'; // compatibility for ios
|
|
386
367
|
rootElement.style.cssText = styleRoot;
|
|
387
|
-
|
|
368
|
+
mainCanvas = rootElement.appendChild(document.createElement('canvas'));
|
|
388
369
|
drawContext = mainContext = mainCanvas.getContext('2d');
|
|
389
370
|
|
|
390
371
|
// init stuff and start engine
|
|
@@ -393,7 +374,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
393
374
|
debugInit();
|
|
394
375
|
|
|
395
376
|
// setup canvases
|
|
396
|
-
// transform way is still more reliable
|
|
377
|
+
// transform way is still more reliable than flexbox or grid
|
|
397
378
|
const styleCanvas = 'position:absolute;'+ // allow canvases to overlap
|
|
398
379
|
'top:50%;left:50%;transform:translate(-50%,-50%)'; // center on screen
|
|
399
380
|
mainCanvas.style.cssText = styleCanvas;
|
|
@@ -438,6 +419,9 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
438
419
|
}));
|
|
439
420
|
}
|
|
440
421
|
|
|
422
|
+
// load engine font image
|
|
423
|
+
promises.push(fontImageInit());
|
|
424
|
+
|
|
441
425
|
if (showSplashScreen)
|
|
442
426
|
{
|
|
443
427
|
// draw splash screen
|
|
@@ -449,7 +433,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
449
433
|
function updateSplash()
|
|
450
434
|
{
|
|
451
435
|
inputClear();
|
|
452
|
-
|
|
436
|
+
drawEngineLogo(t+=.01);
|
|
453
437
|
t>1 ? resolve() : setTimeout(updateSplash, 16);
|
|
454
438
|
}
|
|
455
439
|
}));
|
|
@@ -459,171 +443,12 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
459
443
|
await Promise.all(promises);
|
|
460
444
|
return startEngine();
|
|
461
445
|
|
|
462
|
-
|
|
463
|
-
// LittleJS Splash Screen
|
|
464
|
-
function drawEngineSplashScreen(t)
|
|
446
|
+
async function startEngine()
|
|
465
447
|
{
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
{
|
|
471
|
-
// background
|
|
472
|
-
const p3 = percent(t, 1, .8);
|
|
473
|
-
const p4 = percent(t, 0, .5);
|
|
474
|
-
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
|
|
475
|
-
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
476
|
-
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
477
|
-
x.save();
|
|
478
|
-
x.fillStyle = g;
|
|
479
|
-
x.fillRect(0,0,w,h);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// draw LittleJS logo...
|
|
483
|
-
const rect = (X, Y, W, H, C)=>
|
|
484
|
-
{
|
|
485
|
-
x.beginPath();
|
|
486
|
-
x.rect(X,Y,W,C?H*p:H);
|
|
487
|
-
x.fillStyle = C;
|
|
488
|
-
C ? x.fill() : x.stroke();
|
|
489
|
-
};
|
|
490
|
-
const line = (X, Y, Z, W)=>
|
|
491
|
-
{
|
|
492
|
-
x.beginPath();
|
|
493
|
-
x.lineTo(X,Y);
|
|
494
|
-
x.lineTo(Z,W);
|
|
495
|
-
x.stroke();
|
|
496
|
-
};
|
|
497
|
-
const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
|
|
498
|
-
{
|
|
499
|
-
const D = (A+B)/2, E = p*(B-A)/2;
|
|
500
|
-
x.beginPath();
|
|
501
|
-
F && x.lineTo(X,Y);
|
|
502
|
-
x.arc(X,Y,R,D-E,D+E);
|
|
503
|
-
x.fillStyle = C;
|
|
504
|
-
C ? x.fill() : x.stroke();
|
|
505
|
-
};
|
|
506
|
-
const color = (c=0, l=0) =>
|
|
507
|
-
hsl([.98,.3,.57,.14][c%4],.8,[0,.3,.5,.8,.9][l]).toString();
|
|
508
|
-
const alpha = wave(1,1,t);
|
|
509
|
-
const p = percent(alpha, .1, .5);
|
|
510
|
-
|
|
511
|
-
// setup
|
|
512
|
-
x.translate(w/2,h/2);
|
|
513
|
-
const size = min(6, min(w,h)/99); // fit to screen
|
|
514
|
-
x.scale(size,size);
|
|
515
|
-
x.translate(-40,-35);
|
|
516
|
-
x.lineJoin = x.lineCap = 'round';
|
|
517
|
-
x.lineWidth = .1 + p*1.9;
|
|
518
|
-
|
|
519
|
-
// drawing effect
|
|
520
|
-
const p2 = percent(alpha,.1,1);
|
|
521
|
-
x.setLineDash([99*p2,99]);
|
|
522
|
-
|
|
523
|
-
// cab top
|
|
524
|
-
rect(7,16,18,-8,color(2,2));
|
|
525
|
-
rect(7,8,18,4,color(2,3));
|
|
526
|
-
rect(25,8,8,8,color(2,1));
|
|
527
|
-
rect(25,8,-18,8);
|
|
528
|
-
rect(25,8,8,8);
|
|
529
|
-
|
|
530
|
-
// cab
|
|
531
|
-
rect(25,16,7,23,color());
|
|
532
|
-
rect(11,39,14,-23,color(1,1));
|
|
533
|
-
rect(11,16,14,18,color(1,2));
|
|
534
|
-
rect(11,16,14,8,color(1,3));
|
|
535
|
-
rect(25,16,-14,24);
|
|
536
|
-
|
|
537
|
-
// cab window
|
|
538
|
-
rect(15,29,6,-9,color(2,2));
|
|
539
|
-
circle(15,21,5,0,PI/2,color(2,4),1);
|
|
540
|
-
rect(21,21,-6,9);
|
|
541
|
-
|
|
542
|
-
// little stack
|
|
543
|
-
rect(37,14,9,6,color(3,2));
|
|
544
|
-
rect(37,14,4.5,6,color(3,3));
|
|
545
|
-
rect(37,14,9,6);
|
|
546
|
-
|
|
547
|
-
// big stack
|
|
548
|
-
rect(50,20,10,-8,color(0,1));
|
|
549
|
-
rect(50,20,6.5,-8,color(0,2));
|
|
550
|
-
rect(50,20,3.5,-8,color(0,3));
|
|
551
|
-
rect(50,20,10,-8);
|
|
552
|
-
circle(55,2,11.4,.5,PI-.5,color(3,3));
|
|
553
|
-
circle(55,2,11.4,.5,PI/2,color(3,2),1);
|
|
554
|
-
circle(55,2,11.4,.5,PI-.5);
|
|
555
|
-
rect(45,7,20,-7,color(0,2));
|
|
556
|
-
rect(45,-1,20,4,color(0,3));
|
|
557
|
-
rect(45,-1,20,8);
|
|
558
|
-
|
|
559
|
-
// engine
|
|
560
|
-
for (let i=5; i--;)
|
|
561
|
-
{
|
|
562
|
-
// stagger radius to fix slight seam
|
|
563
|
-
circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
|
|
564
|
-
circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
|
|
565
|
-
circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// engine outline
|
|
569
|
-
circle(36,30,10,PI/2,PI*3/2);
|
|
570
|
-
circle(48,30,10,PI/2,PI*3/2);
|
|
571
|
-
circle(60,30,10);
|
|
572
|
-
line(36,20,60,20);
|
|
573
|
-
|
|
574
|
-
// engine front light
|
|
575
|
-
circle(60,30,4,PI,3*PI,color(3,2));
|
|
576
|
-
circle(60,30,4,PI,2*PI,color(3,3));
|
|
577
|
-
circle(60,30,4,PI,3*PI);
|
|
578
|
-
|
|
579
|
-
// front brush
|
|
580
|
-
for (let i=6; i--;)
|
|
581
|
-
{
|
|
582
|
-
x.beginPath();
|
|
583
|
-
x.lineTo(53,54);
|
|
584
|
-
x.lineTo(53,40);
|
|
585
|
-
x.lineTo(53+(1+i*2.9)*p,40);
|
|
586
|
-
x.lineTo(53+(4+i*3.5)*p,54);
|
|
587
|
-
x.fillStyle = color(0,i%2+2);
|
|
588
|
-
x.fill();
|
|
589
|
-
i%2 && x.stroke();
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
// wheels
|
|
593
|
-
rect(6,40,5,5);
|
|
594
|
-
rect(6,40,5,5,color());
|
|
595
|
-
rect(15,54,38,-14,color());
|
|
596
|
-
for (let i=3; i--;)
|
|
597
|
-
for (let j=2; j--;)
|
|
598
|
-
{
|
|
599
|
-
circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
|
|
600
|
-
x.stroke();
|
|
601
|
-
circle(15*i+15,47,j?7:1,0,PI,color(i,2));
|
|
602
|
-
x.stroke();
|
|
603
|
-
}
|
|
604
|
-
line(6,40,68,40); // center
|
|
605
|
-
line(77,54,4,54); // bottom
|
|
606
|
-
|
|
607
|
-
// draw engine name
|
|
608
|
-
const s = engineName;
|
|
609
|
-
x.font = '900 16px arial';
|
|
610
|
-
x.textAlign = 'center';
|
|
611
|
-
x.textBaseline = 'top';
|
|
612
|
-
x.lineWidth = .1+p*3.9;
|
|
613
|
-
let w2 = 0;
|
|
614
|
-
for (let i=0; i<s.length; ++i)
|
|
615
|
-
w2 += x.measureText(s[i]).width;
|
|
616
|
-
for (let j=2; j--;)
|
|
617
|
-
for (let i=0, X=41-w2/2; i<s.length; ++i)
|
|
618
|
-
{
|
|
619
|
-
x.fillStyle = color(i,2);
|
|
620
|
-
const w = x.measureText(s[i]).width;
|
|
621
|
-
x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
|
|
622
|
-
X += w;
|
|
623
|
-
}
|
|
624
|
-
x.restore();
|
|
448
|
+
// wait for gameInit to load
|
|
449
|
+
await gameInit();
|
|
450
|
+
engineUpdate();
|
|
625
451
|
}
|
|
626
|
-
///////////////////////////////////////////////////////////////////////////
|
|
627
452
|
}
|
|
628
453
|
|
|
629
454
|
/** Update each engine object, remove destroyed objects, and update time
|
|
@@ -637,8 +462,7 @@ function engineObjectsUpdate()
|
|
|
637
462
|
// recursive object update
|
|
638
463
|
function updateObject(o)
|
|
639
464
|
{
|
|
640
|
-
if (o.destroyed)
|
|
641
|
-
return;
|
|
465
|
+
if (o.destroyed) return;
|
|
642
466
|
|
|
643
467
|
o.update();
|
|
644
468
|
for (const child of o.children)
|
|
@@ -646,8 +470,7 @@ function engineObjectsUpdate()
|
|
|
646
470
|
}
|
|
647
471
|
for (const o of engineObjects)
|
|
648
472
|
{
|
|
649
|
-
if (o.parent)
|
|
650
|
-
continue;
|
|
473
|
+
if (o.parent || o.destroyed) continue;
|
|
651
474
|
|
|
652
475
|
// update top level objects
|
|
653
476
|
o.update();
|
|
@@ -736,4 +559,169 @@ function engineObjectsRaycast(start, end, objects=engineObjects)
|
|
|
736
559
|
|
|
737
560
|
debugRaycast && debugLine(start, end, hitObjects.length ? '#f00' : '#00f', .02);
|
|
738
561
|
return hitObjects;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
565
|
+
function drawEngineLogo(t)
|
|
566
|
+
{
|
|
567
|
+
// LittleJS Logo and Splash Screen
|
|
568
|
+
const x = mainContext;
|
|
569
|
+
const w = mainCanvas.width = innerWidth;
|
|
570
|
+
const h = mainCanvas.height = innerHeight;
|
|
571
|
+
|
|
572
|
+
{
|
|
573
|
+
// background
|
|
574
|
+
const p3 = percent(t, 1, .8);
|
|
575
|
+
const p4 = percent(t, 0, .5);
|
|
576
|
+
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
|
|
577
|
+
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
578
|
+
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
579
|
+
x.save();
|
|
580
|
+
x.fillStyle = g;
|
|
581
|
+
x.fillRect(0,0,w,h);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// draw LittleJS logo...
|
|
585
|
+
const rect = (X, Y, W, H, C)=>
|
|
586
|
+
{
|
|
587
|
+
x.beginPath();
|
|
588
|
+
x.rect(X,Y,W,C?H*p:H);
|
|
589
|
+
x.fillStyle = C;
|
|
590
|
+
C ? x.fill() : x.stroke();
|
|
591
|
+
};
|
|
592
|
+
const line = (X, Y, Z, W)=>
|
|
593
|
+
{
|
|
594
|
+
x.beginPath();
|
|
595
|
+
x.lineTo(X,Y);
|
|
596
|
+
x.lineTo(Z,W);
|
|
597
|
+
x.stroke();
|
|
598
|
+
};
|
|
599
|
+
const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
|
|
600
|
+
{
|
|
601
|
+
const D = (A+B)/2, E = p*(B-A)/2;
|
|
602
|
+
x.beginPath();
|
|
603
|
+
F && x.lineTo(X,Y);
|
|
604
|
+
x.arc(X,Y,R,D-E,D+E);
|
|
605
|
+
x.fillStyle = C;
|
|
606
|
+
C ? x.fill() : x.stroke();
|
|
607
|
+
};
|
|
608
|
+
const color = (c=0, l=0) =>
|
|
609
|
+
hsl([.98,.3,.57,.14][c%4],.9,[0,.3,.5,.8,.9][l]).toString();
|
|
610
|
+
const alpha = wave(1,1,t);
|
|
611
|
+
const p = percent(alpha, .1, .5);
|
|
612
|
+
|
|
613
|
+
// setup
|
|
614
|
+
x.translate(w/2,h/2);
|
|
615
|
+
const size = min(6, min(w,h)/99); // fit to screen
|
|
616
|
+
x.scale(size,size);
|
|
617
|
+
x.translate(-40,-35);
|
|
618
|
+
x.lineJoin = x.lineCap = 'round';
|
|
619
|
+
x.lineWidth = .1 + p*1.9;
|
|
620
|
+
|
|
621
|
+
// drawing effect
|
|
622
|
+
const p2 = percent(alpha,.1,1);
|
|
623
|
+
x.setLineDash([99*p2,99]);
|
|
624
|
+
|
|
625
|
+
// cab top
|
|
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);
|
|
648
|
+
|
|
649
|
+
// big stack
|
|
650
|
+
rect(50,20,10,-8,color(0,1));
|
|
651
|
+
rect(50,20,6.5,-8,color(0,2));
|
|
652
|
+
rect(50,20,3.5,-8,color(0,3));
|
|
653
|
+
rect(50,20,10,-8);
|
|
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);
|
|
660
|
+
|
|
661
|
+
// engine
|
|
662
|
+
for (let i=5; i--;)
|
|
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
|
+
}
|
|
669
|
+
|
|
670
|
+
// engine outline
|
|
671
|
+
circle(36,30,10,PI/2,PI*3/2);
|
|
672
|
+
circle(48,30,10,PI/2,PI*3/2);
|
|
673
|
+
circle(60,30,10);
|
|
674
|
+
line(36,20,60,20);
|
|
675
|
+
|
|
676
|
+
// engine front light
|
|
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
|
+
}
|
|
693
|
+
|
|
694
|
+
// wheels
|
|
695
|
+
rect(6,40,5,5);
|
|
696
|
+
rect(6,40,5,5,color());
|
|
697
|
+
rect(15,54,38,-14,color());
|
|
698
|
+
for (let i=3; 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)
|
|
720
|
+
{
|
|
721
|
+
x.fillStyle = color(i,2);
|
|
722
|
+
const w = x.measureText(s[i]).width;
|
|
723
|
+
x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
|
|
724
|
+
X += w;
|
|
725
|
+
}
|
|
726
|
+
x.restore();
|
|
739
727
|
}
|
package/src/engineAudio.js
CHANGED
|
@@ -86,8 +86,7 @@ class Sound
|
|
|
86
86
|
{
|
|
87
87
|
// remove randomness so it can be applied on playback
|
|
88
88
|
const randomnessIndex = 1, defaultRandomness = .05;
|
|
89
|
-
this.randomness = zzfxSound[randomnessIndex]
|
|
90
|
-
zzfxSound[randomnessIndex] : defaultRandomness;
|
|
89
|
+
this.randomness = zzfxSound[randomnessIndex] ?? defaultRandomness;
|
|
91
90
|
zzfxSound[randomnessIndex] = 0;
|
|
92
91
|
|
|
93
92
|
// generate the zzfx samples
|
|
@@ -167,7 +166,7 @@ class Sound
|
|
|
167
166
|
* @return {number} - How long the sound is in seconds (undefined if loading)
|
|
168
167
|
*/
|
|
169
168
|
getDuration()
|
|
170
|
-
{ return this.sampleChannels
|
|
169
|
+
{ return this.sampleChannels?.[0].length / this.sampleRate || 0; }
|
|
171
170
|
|
|
172
171
|
/** Check if sound is loaded, for sounds fetched from a url
|
|
173
172
|
* @return {boolean} - True if sound is loaded and ready to play
|
|
@@ -259,8 +258,7 @@ class SoundWave extends Sound
|
|
|
259
258
|
this.sampleRate = audioBuffer.sampleRate;
|
|
260
259
|
this.sampleChannels = sampleChannels;
|
|
261
260
|
this.loadedPercent = 1;
|
|
262
|
-
|
|
263
|
-
this.onloadCallback(this);
|
|
261
|
+
this.onloadCallback?.(this);
|
|
264
262
|
}
|
|
265
263
|
}
|
|
266
264
|
|
|
@@ -383,8 +381,7 @@ class SoundInstance
|
|
|
383
381
|
/** Pause this sound instance */
|
|
384
382
|
pause()
|
|
385
383
|
{
|
|
386
|
-
if (this.isPaused())
|
|
387
|
-
return;
|
|
384
|
+
if (this.isPaused()) return;
|
|
388
385
|
|
|
389
386
|
// save current time and stop sound
|
|
390
387
|
this.pausedTime = this.getCurrentTime();
|
|
@@ -396,8 +393,7 @@ class SoundInstance
|
|
|
396
393
|
/** Unpauses this sound instance */
|
|
397
394
|
resume()
|
|
398
395
|
{
|
|
399
|
-
if (!this.isPaused())
|
|
400
|
-
return;
|
|
396
|
+
if (!this.isPaused()) return;
|
|
401
397
|
|
|
402
398
|
// restart sound from paused time
|
|
403
399
|
this.start(this.pausedTime);
|
|
@@ -465,7 +461,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
465
461
|
|
|
466
462
|
/** Stop all queued speech
|
|
467
463
|
* @memberof Audio */
|
|
468
|
-
function speakStop() {speechSynthesis
|
|
464
|
+
function speakStop() {speechSynthesis?.cancel();}
|
|
469
465
|
|
|
470
466
|
/** Get frequency of a note on a musical scale
|
|
471
467
|
* @param {number} semitoneOffset - How many semitones away from the root note
|
package/src/engineBuild.js
CHANGED