littlejsengine 1.16.2 → 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.
Files changed (47) hide show
  1. package/dist/littlejs.d.ts +200 -166
  2. package/dist/littlejs.esm.js +1231 -1086
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +888 -735
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +884 -731
  7. package/examples/box2d/gameObjects.js +1 -1
  8. package/examples/breakout/game.js +5 -6
  9. package/examples/electron/index.html +2 -2
  10. package/examples/electron/tiles.png +0 -0
  11. package/examples/htmlMenu/tiles.png +0 -0
  12. package/examples/index.html +1 -1
  13. package/examples/logo.png +0 -0
  14. package/examples/module/tiles.png +0 -0
  15. package/examples/platformer/gameEffects.js +23 -22
  16. package/examples/platformer/gameLevel.js +24 -25
  17. package/examples/shorts/base.html +1 -1
  18. package/examples/shorts/clock.js +3 -3
  19. package/examples/shorts/fontImage.js +4 -3
  20. package/examples/shorts/parallax.js +1 -1
  21. package/examples/shorts/sequencer.js +1 -1
  22. package/examples/shorts/shapes.js +1 -1
  23. package/examples/shorts/texture.js +10 -6
  24. package/examples/shorts/tiles.png +0 -0
  25. package/examples/shorts/tiltedView.js +2 -0
  26. package/examples/starter/index.html +2 -2
  27. package/examples/starter/tiles.png +0 -0
  28. package/examples/typescript/tiles.png +0 -0
  29. package/examples/uiSystem/game.js +1 -1
  30. package/examples/uiSystem/tiles.png +0 -0
  31. package/package.json +1 -1
  32. package/plugins/postProcess.js +47 -28
  33. package/plugins/uiSystem.js +15 -15
  34. package/reference.md +1 -1
  35. package/src/engine.js +174 -174
  36. package/src/engineAudio.js +4 -6
  37. package/src/engineDebug.js +4 -4
  38. package/src/engineDraw.js +179 -122
  39. package/src/engineExport.js +326 -334
  40. package/src/engineFont.png +0 -0
  41. package/src/engineInput.js +14 -20
  42. package/src/engineMath.js +15 -104
  43. package/src/engineObject.js +26 -25
  44. package/src/engineParticles.js +2 -2
  45. package/src/engineTileLayer.js +196 -170
  46. package/src/engineUtilities.js +100 -4
  47. package/src/engineWebGL.js +123 -72
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.16.2';
33
+ const engineVersion = '1.17.1';
34
34
 
35
35
  /** Frames per second to update
36
36
  * @type {number}
@@ -317,7 +317,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
317
317
 
318
318
  // responsive aspect ratio with native resolution
319
319
  const innerAspect = innerWidth / innerHeight;
320
- ASSERT(canvasMinAspect <= canvasMaxAspect);
320
+ ASSERT(canvasMinAspect <= canvasMaxAspect);
321
321
  if (canvasMaxAspect && innerAspect > canvasMaxAspect)
322
322
  {
323
323
  // full height
@@ -374,7 +374,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
374
374
  debugInit();
375
375
 
376
376
  // setup canvases
377
- // transform way is still more reliable then flexbox or grid
377
+ // transform way is still more reliable than flexbox or grid
378
378
  const styleCanvas = 'position:absolute;'+ // allow canvases to overlap
379
379
  'top:50%;left:50%;transform:translate(-50%,-50%)'; // center on screen
380
380
  mainCanvas.style.cssText = styleCanvas;
@@ -419,6 +419,9 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
419
419
  }));
420
420
  }
421
421
 
422
+ // load engine font image
423
+ promises.push(fontImageInit());
424
+
422
425
  if (showSplashScreen)
423
426
  {
424
427
  // draw splash screen
@@ -430,7 +433,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
430
433
  function updateSplash()
431
434
  {
432
435
  inputClear();
433
- drawEngineSplashScreen(t+=.01);
436
+ drawEngineLogo(t+=.01);
434
437
  t>1 ? resolve() : setTimeout(updateSplash, 16);
435
438
  }
436
439
  }));
@@ -446,172 +449,6 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
446
449
  await gameInit();
447
450
  engineUpdate();
448
451
  }
449
-
450
- ///////////////////////////////////////////////////////////////////////////
451
- // LittleJS Splash Screen
452
- function drawEngineSplashScreen(t)
453
- {
454
- const x = mainContext;
455
- const w = mainCanvas.width = innerWidth;
456
- const h = mainCanvas.height = innerHeight;
457
-
458
- {
459
- // background
460
- const p3 = percent(t, 1, .8);
461
- const p4 = percent(t, 0, .5);
462
- const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
463
- g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
464
- g.addColorStop(1,hsl(0,0,0,p3).toString());
465
- x.save();
466
- x.fillStyle = g;
467
- x.fillRect(0,0,w,h);
468
- }
469
-
470
- // draw LittleJS logo...
471
- const rect = (X, Y, W, H, C)=>
472
- {
473
- x.beginPath();
474
- x.rect(X,Y,W,C?H*p:H);
475
- x.fillStyle = C;
476
- C ? x.fill() : x.stroke();
477
- };
478
- const line = (X, Y, Z, W)=>
479
- {
480
- x.beginPath();
481
- x.lineTo(X,Y);
482
- x.lineTo(Z,W);
483
- x.stroke();
484
- };
485
- const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
486
- {
487
- const D = (A+B)/2, E = p*(B-A)/2;
488
- x.beginPath();
489
- F && x.lineTo(X,Y);
490
- x.arc(X,Y,R,D-E,D+E);
491
- x.fillStyle = C;
492
- C ? x.fill() : x.stroke();
493
- };
494
- const color = (c=0, l=0) =>
495
- hsl([.98,.3,.57,.14][c%4],.8,[0,.3,.5,.8,.9][l]).toString();
496
- const alpha = wave(1,1,t);
497
- const p = percent(alpha, .1, .5);
498
-
499
- // setup
500
- x.translate(w/2,h/2);
501
- const size = min(6, min(w,h)/99); // fit to screen
502
- x.scale(size,size);
503
- x.translate(-40,-35);
504
- x.lineJoin = x.lineCap = 'round';
505
- x.lineWidth = .1 + p*1.9;
506
-
507
- // drawing effect
508
- const p2 = percent(alpha,.1,1);
509
- x.setLineDash([99*p2,99]);
510
-
511
- // cab top
512
- rect(7,16,18,-8,color(2,2));
513
- rect(7,8,18,4,color(2,3));
514
- rect(25,8,8,8,color(2,1));
515
- rect(25,8,-18,8);
516
- rect(25,8,8,8);
517
-
518
- // cab
519
- rect(25,16,7,23,color());
520
- rect(11,39,14,-23,color(1,1));
521
- rect(11,16,14,18,color(1,2));
522
- rect(11,16,14,8,color(1,3));
523
- rect(25,16,-14,24);
524
-
525
- // cab window
526
- rect(15,29,6,-9,color(2,2));
527
- circle(15,21,5,0,PI/2,color(2,4),1);
528
- rect(21,21,-6,9);
529
-
530
- // little stack
531
- rect(37,14,9,6,color(3,2));
532
- rect(37,14,4.5,6,color(3,3));
533
- rect(37,14,9,6);
534
-
535
- // big stack
536
- rect(50,20,10,-8,color(0,1));
537
- rect(50,20,6.5,-8,color(0,2));
538
- rect(50,20,3.5,-8,color(0,3));
539
- rect(50,20,10,-8);
540
- circle(55,2,11.4,.5,PI-.5,color(3,3));
541
- circle(55,2,11.4,.5,PI/2,color(3,2),1);
542
- circle(55,2,11.4,.5,PI-.5);
543
- rect(45,7,20,-7,color(0,2));
544
- rect(45,-1,20,4,color(0,3));
545
- rect(45,-1,20,8);
546
-
547
- // engine
548
- for (let i=5; i--;)
549
- {
550
- // stagger radius to fix slight seam
551
- circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
552
- circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
553
- circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
554
- }
555
-
556
- // engine outline
557
- circle(36,30,10,PI/2,PI*3/2);
558
- circle(48,30,10,PI/2,PI*3/2);
559
- circle(60,30,10);
560
- line(36,20,60,20);
561
-
562
- // engine front light
563
- circle(60,30,4,PI,3*PI,color(3,2));
564
- circle(60,30,4,PI,2*PI,color(3,3));
565
- circle(60,30,4,PI,3*PI);
566
-
567
- // front brush
568
- for (let i=6; i--;)
569
- {
570
- x.beginPath();
571
- x.lineTo(53,54);
572
- x.lineTo(53,40);
573
- x.lineTo(53+(1+i*2.9)*p,40);
574
- x.lineTo(53+(4+i*3.5)*p,54);
575
- x.fillStyle = color(0,i%2+2);
576
- x.fill();
577
- i%2 && x.stroke();
578
- }
579
-
580
- // wheels
581
- rect(6,40,5,5);
582
- rect(6,40,5,5,color());
583
- rect(15,54,38,-14,color());
584
- for (let i=3; i--;)
585
- for (let j=2; j--;)
586
- {
587
- circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
588
- x.stroke();
589
- circle(15*i+15,47,j?7:1,0,PI,color(i,2));
590
- x.stroke();
591
- }
592
- line(6,40,68,40); // center
593
- line(77,54,4,54); // bottom
594
-
595
- // draw engine name
596
- const s = engineName;
597
- x.font = '900 16px arial';
598
- x.textAlign = 'center';
599
- x.textBaseline = 'top';
600
- x.lineWidth = .1+p*3.9;
601
- let w2 = 0;
602
- for (let i=0; i<s.length; ++i)
603
- w2 += x.measureText(s[i]).width;
604
- for (let j=2; j--;)
605
- for (let i=0, X=41-w2/2; i<s.length; ++i)
606
- {
607
- x.fillStyle = color(i,2);
608
- const w = x.measureText(s[i]).width;
609
- x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
610
- X += w;
611
- }
612
- x.restore();
613
- }
614
- ///////////////////////////////////////////////////////////////////////////
615
452
  }
616
453
 
617
454
  /** Update each engine object, remove destroyed objects, and update time
@@ -625,8 +462,7 @@ function engineObjectsUpdate()
625
462
  // recursive object update
626
463
  function updateObject(o)
627
464
  {
628
- if (o.destroyed)
629
- return;
465
+ if (o.destroyed) return;
630
466
 
631
467
  o.update();
632
468
  for (const child of o.children)
@@ -634,8 +470,7 @@ function engineObjectsUpdate()
634
470
  }
635
471
  for (const o of engineObjects)
636
472
  {
637
- if (o.parent)
638
- continue;
473
+ if (o.parent || o.destroyed) continue;
639
474
 
640
475
  // update top level objects
641
476
  o.update();
@@ -724,4 +559,169 @@ function engineObjectsRaycast(start, end, objects=engineObjects)
724
559
 
725
560
  debugRaycast && debugLine(start, end, hitObjects.length ? '#f00' : '#00f', .02);
726
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();
727
727
  }
@@ -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] !== undefined ?
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 && this.sampleRate ? this.sampleChannels[0].length / this.sampleRate : 0; }
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
- if (this.onloadCallback)
263
- this.onloadCallback(this);
261
+ this.onloadCallback?.(this);
264
262
  }
265
263
  }
266
264
 
@@ -463,7 +461,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
463
461
 
464
462
  /** Stop all queued speech
465
463
  * @memberof Audio */
466
- function speakStop() {speechSynthesis && speechSynthesis.cancel();}
464
+ function speakStop() {speechSynthesis?.cancel();}
467
465
 
468
466
  /** Get frequency of a note on a musical scale
469
467
  * @param {number} semitoneOffset - How many semitones away from the root note
@@ -65,7 +65,7 @@ function LOG(...output) { console.log(...output); }
65
65
 
66
66
  /** Draw a debug rectangle in world space
67
67
  * @param {Vector2} pos
68
- * @param {Vector2} [size=Vector2()]
68
+ * @param {Vector2} [size=vec2(0)]
69
69
  * @param {Color|string} [color]
70
70
  * @param {number} [time]
71
71
  * @param {number} [angle]
@@ -389,7 +389,7 @@ function debugRender()
389
389
  if (tileCollisionTest(mousePos))
390
390
  {
391
391
  // show floored tile pick for tile collision
392
- drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(1,1,0,.5));
392
+ drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(1,1,0,.5), 0, false);
393
393
  }
394
394
  }
395
395
 
@@ -468,8 +468,8 @@ function debugRender()
468
468
  if (debugObject)
469
469
  {
470
470
  const raycastHitPos = tileCollisionRaycast(debugObject.pos, mousePos);
471
- raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), rgb(0,1,1,.3));
472
- drawLine(mousePos, debugObject.pos, .1, raycastHitPos ? rgb(1,0,0,.5) : rgb(0,1,0,.5));
471
+ raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), rgb(0,1,1,.3), 0, false);
472
+ drawLine(mousePos, debugObject.pos, .1, raycastHitPos ? rgb(1,0,0,.5) : rgb(0,1,0,.5), undefined, undefined, false);
473
473
 
474
474
  let debugText = 'mouse pos = ' + mousePos;
475
475
  if (tileCollisionLayers.length)