littlejsengine 1.17.5 → 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/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.5';
33
+ const engineVersion = '1.17.10';
34
34
 
35
35
  /** Frames per second to update
36
36
  * @type {number}
@@ -461,13 +461,13 @@ function engineObjectsUpdate()
461
461
  engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
462
462
 
463
463
  // recursive object update
464
- function updateObject(o)
464
+ function updateChildObject(o)
465
465
  {
466
466
  if (o.destroyed) return;
467
467
 
468
468
  o.update();
469
469
  for (const child of o.children)
470
- updateObject(child);
470
+ updateChildObject(child);
471
471
  }
472
472
  for (const o of engineObjects)
473
473
  {
@@ -477,7 +477,7 @@ function engineObjectsUpdate()
477
477
  o.update();
478
478
  o.updatePhysics();
479
479
  for (const child of o.children)
480
- updateObject(child);
480
+ updateChildObject(child);
481
481
  o.updateTransforms();
482
482
  }
483
483
 
@@ -486,11 +486,14 @@ function engineObjectsUpdate()
486
486
  }
487
487
 
488
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?
489
492
  * @memberof Engine */
490
- function engineObjectsDestroy()
493
+ function engineObjectsDestroy(immediate=true)
491
494
  {
492
495
  for (const o of engineObjects)
493
- o.parent || o.destroy();
496
+ o.parent || o.destroy(immediate);
494
497
  engineObjects = engineObjects.filter(o=>!o.destroyed);
495
498
  }
496
499
 
@@ -565,164 +568,137 @@ function engineObjectsRaycast(start, end, objects=engineObjects)
565
568
  ///////////////////////////////////////////////////////////////////////////////
566
569
  function drawEngineLogo(t)
567
570
  {
571
+ const blackAndWhite = 0;
572
+ const showName = 1;
573
+
568
574
  // LittleJS Logo and Splash Screen
569
575
  const x = mainContext;
570
576
  const w = mainCanvas.width = innerWidth;
571
577
  const h = mainCanvas.height = innerHeight;
572
-
573
578
  {
574
579
  // background
575
580
  const p3 = percent(t, 1, .8);
576
581
  const p4 = percent(t, 0, .5);
577
- const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
582
+ const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.6);
578
583
  g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
579
584
  g.addColorStop(1,hsl(0,0,0,p3).toString());
580
585
  x.save();
581
586
  x.fillStyle = g;
582
587
  x.fillRect(0,0,w,h);
583
588
  }
584
-
585
- // draw LittleJS logo...
586
- const rect = (X, Y, W, H, C)=>
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)=>
587
607
  {
588
608
  x.beginPath();
589
- x.rect(X,Y,W,C?H*p:H);
590
- x.fillStyle = C;
591
- C ? x.fill() : x.stroke();
592
- };
593
- 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)=>
594
613
  {
595
614
  x.beginPath();
596
- x.lineTo(X,Y);
597
- x.lineTo(Z,W);
598
- x.stroke();
599
- };
600
- 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)=>
601
619
  {
602
- const D = (A+B)/2, E = p*(B-A)/2;
603
620
  x.beginPath();
604
- F && x.lineTo(X,Y);
605
- x.arc(X,Y,R,D-E,D+E);
606
- x.fillStyle = C;
607
- C ? x.fill() : x.stroke();
608
- };
609
- const color = (c=0, l=0)=>
610
- hsl([.98,.3,.57,.14][c%4],.9,[0,.3,.5,.8,.9][l]).toString();
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
611
629
  const alpha = wave(1,1,t);
612
630
  const p = percent(alpha, .1, .5);
613
-
614
- // setup
631
+ const size = min(6, min(w,h)/99);
615
632
  x.translate(w/2,h/2);
616
- const size = min(6, min(w,h)/99); // fit to screen
617
633
  x.scale(size,size);
618
634
  x.translate(-40,-35);
635
+ p < 1 && x.setLineDash([99*p,99]);
619
636
  x.lineJoin = x.lineCap = 'round';
620
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
+ }
621
667
 
622
- // drawing effect
623
- const p2 = percent(alpha,.1,1);
624
- x.setLineDash([99*p2,99]);
625
-
626
- // cab top
627
- rect(7,16,18,-8,color(2,2));
628
- rect(7,8,18,4,color(2,3));
629
- rect(25,8,8,8,color(2,1));
630
- rect(25,8,-18,8);
631
- rect(25,8,8,8);
632
-
633
- // cab
634
- rect(25,16,7,24,color());
635
- rect(11,39,14,-23,color(1,1));
636
- rect(11,16,14,18,color(1,2));
637
- rect(11,16,14,8,color(1,3));
638
- rect(25,16,-14,24);
639
-
640
- // cab window
641
- rect(15,29,6,-9,color(2,2));
642
- circle(15,21,5,0,PI/2,color(2,4),1);
643
- rect(21,21,-6,9);
644
-
645
- // little stack
646
- rect(37,14,9,6,color(3,2));
647
- rect(37,14,4.5,6,color(3,3));
648
- 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
649
673
 
650
674
  // big stack
651
- rect(50,20,10,-8,color(0,1));
652
- rect(50,20,6.5,-8,color(0,2));
653
- rect(50,20,3.5,-8,color(0,3));
654
- rect(50,20,10,-8);
655
- circle(55,2,11.4,.5,PI-.5,color(3,3));
656
- circle(55,2,11.4,.5,PI/2,color(3,2),1);
657
- circle(55,2,11.4,.5,PI-.5);
658
- rect(45,7,20,-7,color(0,2));
659
- rect(45,-1,20,4,color(0,3));
660
- 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);
661
679
 
662
680
  // engine
663
- for (let i=5; i--;)
664
- {
665
- // stagger radius to fix slight seam
666
- circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
667
- circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
668
- circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
669
- }
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
670
683
 
671
684
  // engine outline
672
- circle(36,30,10,PI/2,PI*3/2);
673
- circle(48,30,10,PI/2,PI*3/2);
674
- circle(60,30,10);
675
- line(36,20,60,20);
676
-
677
- // engine front light
678
- circle(60,30,4,PI,3*PI,color(3,2));
679
- circle(60,30,4,PI,2*PI,color(3,3));
680
- circle(60,30,4,PI,3*PI);
681
-
682
- // front brush
683
- for (let i=6; i--;)
684
- {
685
- x.beginPath();
686
- x.lineTo(53,54);
687
- x.lineTo(53,40);
688
- x.lineTo(53+(1+i*2.9)*p,40);
689
- x.lineTo(53+(4+i*3.5)*p,54);
690
- x.fillStyle = color(0,i%2+2);
691
- x.fill();
692
- i%2 && x.stroke();
693
- }
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
694
691
 
695
692
  // wheels
696
- rect(6,40,5,5);
697
- rect(6,40,5,5,color());
698
- rect(15,54,38,-14,color());
699
- for (let i=3; i--;)
700
- for (let j=2; j--;)
701
- {
702
- circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
703
- x.stroke();
704
- circle(15*i+15,47,j?7:1,0,PI,color(i,2));
705
- x.stroke();
706
- }
707
- line(6,40,68,40); // center
708
- line(77,54,4,54); // bottom
709
-
710
- // draw engine name
711
- const s = engineName;
712
- x.font = '900 16px arial';
713
- x.textAlign = 'center';
714
- x.textBaseline = 'top';
715
- x.lineWidth = .1+p*3.9;
716
- let w2 = 0;
717
- for (let i=0; i<s.length; ++i)
718
- w2 += x.measureText(s[i]).width;
719
- for (let j=2; j--;)
720
- 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--;)
721
697
  {
722
- x.fillStyle = color(i,2);
723
- const w = x.measureText(s[i]).width;
724
- x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
725
- 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);
726
701
  }
702
+
727
703
  x.restore();
728
704
  }
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 refrence
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
  */
@@ -1020,7 +1020,7 @@ let engineFontImage;
1020
1020
  class FontImage
1021
1021
  {
1022
1022
  /** Create an image font
1023
- * @param {TileInfo} tileInfo - Tile info of first characeter in font
1023
+ * @param {TileInfo} tileInfo - Tile info of first character in font
1024
1024
  */
1025
1025
  constructor(tileInfo)
1026
1026
  {
@@ -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,
@@ -769,7 +769,7 @@ function inputRender()
769
769
  }
770
770
  }
771
771
 
772
- // center position for right tocuh pad face buttons
772
+ // center position for right touch pad face buttons
773
773
  function touchGamepadButtonCenter()
774
774
  {
775
775
  const center = mainCanvasSize.subtract(vec2(touchGamepadSize));
@@ -66,17 +66,19 @@ class EngineObject
66
66
  this.additiveColor = undefined;
67
67
  /** @property {boolean} - Should it flip along y axis when rendered */
68
68
  this.mirror = false;
69
+ /** @property {boolean} - Has object been destroyed? */
70
+ this.destroyed = false;
69
71
 
70
72
  // physical properties
71
- /** @property {number} [mass=objectDefaultMass] - How heavy the object is, static if 0 */
73
+ /** @property {number} - How heavy the object is, static if 0 */
72
74
  this.mass = objectDefaultMass;
73
- /** @property {number} [damping=objectDefaultDamping] - How much to slow down velocity each frame (0-1) */
75
+ /** @property {number} - How much to slow down velocity each frame (0-1) */
74
76
  this.damping = objectDefaultDamping;
75
- /** @property {number} [angleDamping=objectDefaultAngleDamping] - How much to slow down rotation each frame (0-1) */
77
+ /** @property {number} - How much to slow down rotation each frame (0-1) */
76
78
  this.angleDamping = objectDefaultAngleDamping;
77
- /** @property {number} [restitution=objectDefaultRestitution] - How bouncy the object is when colliding (0-1) */
79
+ /** @property {number} - How bouncy the object is when colliding (0-1) */
78
80
  this.restitution = objectDefaultRestitution;
79
- /** @property {number} [friction=objectDefaultFriction] - How much friction to apply when sliding (0-1) */
81
+ /** @property {number} - How much friction to apply when sliding (0-1) */
80
82
  this.friction = objectDefaultFriction;
81
83
  /** @property {number} - How much to scale gravity by for this object */
82
84
  this.gravityScale = 1;
@@ -285,10 +287,10 @@ class EngineObject
285
287
  if (!tileCollisionTest(oldPos, this.size, this))
286
288
  {
287
289
  // test which side we bounced off (or both if a corner)
288
- const blockedLayerY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
289
- const blockedLayerX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
290
-
291
- if (blockedLayerX)
290
+ const isBlockedX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
291
+ const isBlockedY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
292
+ const restitution = max(this.restitution, hitLayer.restitution);
293
+ if (isBlockedX)
292
294
  {
293
295
  // try to move up a tiny bit
294
296
  const epsilon = 1e-3;
@@ -304,16 +306,12 @@ class EngineObject
304
306
  return;
305
307
  }
306
308
 
307
- // move to previous position and bounce
309
+ // move to previous X position and bounce
308
310
  this.pos.x = oldPos.x;
309
- this.velocity.x *= -this.restitution;
311
+ this.velocity.x *= -restitution;
310
312
  }
311
- if (blockedLayerY || !blockedLayerX)
313
+ if (isBlockedY || !isBlockedX)
312
314
  {
313
- // bounce velocity
314
- const restitution = max(this.restitution, hitLayer.restitution);
315
- this.velocity.y *= -restitution;
316
-
317
315
  if (wasFalling)
318
316
  {
319
317
  // adjust position to slightly away from nearest tile
@@ -329,10 +327,12 @@ class EngineObject
329
327
  }
330
328
  else
331
329
  {
332
- // move to previous position
330
+ // move to previous Y position
333
331
  this.pos.y = oldPos.y;
334
332
  this.groundObject = undefined;
335
333
  }
334
+ // bounce velocity
335
+ this.velocity.y *= -restitution;
336
336
  }
337
337
  debugPhysics && debugRect(this.pos, this.size, '#f00');
338
338
  }
@@ -350,19 +350,19 @@ class EngineObject
350
350
  drawTile(this.pos, this.drawSize || this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
351
351
  }
352
352
 
353
- /** Destroy this object, destroy its children, detach its parent, and mark it for removal */
354
- destroy()
353
+ /** Destroy this object, destroy its children, detach its parent, and mark it for removal
354
+ * @param {boolean} [immediate] - should attached effects be allowed to die off? */
355
+ destroy(immediate=false)
355
356
  {
356
- if (this.destroyed)
357
- return;
357
+ if (this.destroyed) return;
358
358
 
359
359
  // disconnect from parent and destroy children
360
- this.destroyed = 1;
360
+ this.destroyed = true;
361
361
  this.parent?.removeChild(this);
362
362
  for (const child of this.children)
363
363
  {
364
364
  child.parent = undefined;
365
- child.destroy();
365
+ child.destroy(immediate);
366
366
  }
367
367
  }
368
368
 
@@ -461,7 +461,7 @@ class EngineObject
461
461
  }
462
462
 
463
463
  /** Check if overlapping another engine object
464
- * Collisions are resoloved to prevent overlaps
464
+ * Collisions are resolved to prevent overlaps
465
465
  * @param {EngineObject} object
466
466
  * @return {boolean} */
467
467
  isOverlappingObject(object)