littlejsengine 1.15.2 → 1.15.8

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 (42) hide show
  1. package/dist/littlejs.d.ts +190 -63
  2. package/dist/littlejs.esm.js +1267 -605
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +1261 -604
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +1179 -552
  7. package/examples/electron/index.html +2 -2
  8. package/examples/index.html +4 -3
  9. package/examples/particles/index.html +22 -6
  10. package/examples/shorts/base.html +2 -1
  11. package/examples/shorts/box2dCar.js +1 -1
  12. package/examples/shorts/box2dPool.js +9 -10
  13. package/examples/shorts/empty.js +1 -1
  14. package/examples/shorts/input.js +30 -25
  15. package/examples/shorts/music.js +1 -0
  16. package/examples/shorts/musicPlayer.js +1 -0
  17. package/examples/shorts/sequencer.js +2 -0
  18. package/examples/shorts/sound.js +1 -0
  19. package/examples/shorts/tileLayer.js +2 -2
  20. package/examples/shorts/tiles.png +0 -0
  21. package/examples/shorts/timers.js +1 -0
  22. package/examples/shorts/uiSystem.js +26 -14
  23. package/examples/shorts/videoPlayer.js +2 -1
  24. package/examples/starter/index.html +3 -2
  25. package/examples/uiSystem/game.js +28 -15
  26. package/package.json +1 -1
  27. package/plugins/box2d.js +1 -1
  28. package/plugins/desktop.ini +2 -0
  29. package/plugins/pluginExport.js +2 -0
  30. package/plugins/uiSystem.js +503 -91
  31. package/src/engine.js +2 -169
  32. package/src/engineAudio.js +0 -1
  33. package/src/engineBuild.js +1 -0
  34. package/src/engineDebug.js +84 -54
  35. package/src/engineDraw.js +81 -40
  36. package/src/engineExport.js +4 -1
  37. package/src/engineInput.js +526 -381
  38. package/src/engineObject.js +27 -23
  39. package/src/engineSettings.js +11 -6
  40. package/src/engineSplash.js +173 -0
  41. package/src/engineTileLayer.js +3 -3
  42. package/src/engineUtilities.js +17 -1
@@ -134,7 +134,7 @@ class EngineObject
134
134
  child.updateTransforms();
135
135
  }
136
136
 
137
- /** Update the object physics, called automatically by engine once each frame */
137
+ /** Update the object physics, called automatically by engine once each frame. Can be overridden to stop or change how physics works for an object. */
138
138
  updatePhysics()
139
139
  {
140
140
  // child objects do not have physics
@@ -167,7 +167,7 @@ class EngineObject
167
167
  if (!enablePhysicsSolver || !this.mass) // don't do collision for static objects
168
168
  return;
169
169
 
170
- const wasMovingDown = this.velocity.y < 0;
170
+ const wasFalling = this.velocity.y < 0 && gravity.y < 0 || this.velocity.y > 0 && gravity.y > 0;
171
171
  if (this.groundObject)
172
172
  {
173
173
  // apply friction in local space of ground object
@@ -223,10 +223,10 @@ class EngineObject
223
223
  {
224
224
  // push outside object collision
225
225
  this.pos.y = o.pos.y + (sizeBoth.y/2 + epsilon) * sign(oldPos.y - o.pos.y);
226
- if ((o.groundObject && wasMovingDown) || !o.mass)
226
+ if ((o.groundObject && wasFalling) || !o.mass)
227
227
  {
228
228
  // set ground object if landed on something
229
- if (wasMovingDown)
229
+ if (wasFalling)
230
230
  this.groundObject = o;
231
231
 
232
232
  // bounce if other object is fixed or grounded
@@ -313,12 +313,15 @@ class EngineObject
313
313
  const restitution = max(this.restitution, hitLayer.restitution);
314
314
  this.velocity.y *= -restitution;
315
315
 
316
- if (wasMovingDown)
316
+ if (wasFalling)
317
317
  {
318
- // adjust position to slightly above nearest tile boundary
318
+ // adjust position to slightly away from nearest tile
319
319
  // this prevents gap between object and ground
320
320
  const epsilon = .0001;
321
- this.pos.y = (oldPos.y-this.size.y/2|0)+this.size.y/2+epsilon;
321
+ const offset = this.size.y/2 + epsilon;
322
+ this.pos.y = gravity.y < 0 ?
323
+ floor(oldPos.y-this.size.y/2) + offset :
324
+ ceil( oldPos.y+this.size.y/2) - offset;
322
325
 
323
326
  // set ground object for tile collision
324
327
  this.groundObject = hitLayer;
@@ -448,7 +451,9 @@ class EngineObject
448
451
  {
449
452
  ASSERT(child.parent === this && this.children.includes(child));
450
453
  ASSERT(child instanceof EngineObject, 'child must be an EngineObject');
451
- this.children.splice(this.children.indexOf(child), 1);
454
+ const index = this.children.indexOf(child);
455
+ ASSERT(index >= 0, 'child not found in children array');
456
+ index >= 0 && this.children.splice(index, 1);
452
457
  child.parent = 0;
453
458
  }
454
459
 
@@ -485,21 +490,20 @@ class EngineObject
485
490
  * @return {string} */
486
491
  toString()
487
492
  {
488
- if (debug)
489
- {
490
- let text = 'type = ' + this.constructor.name;
491
- if (this.pos.x || this.pos.y)
492
- text += '\npos = ' + this.pos;
493
- if (this.velocity.x || this.velocity.y)
494
- text += '\nvelocity = ' + this.velocity;
495
- if (this.size.x || this.size.y)
496
- text += '\nsize = ' + this.size;
497
- if (this.angle)
498
- text += '\nangle = ' + this.angle.toFixed(3);
499
- if (this.color)
500
- text += '\ncolor = ' + this.color;
501
- return text;
502
- }
493
+ if (!debug) return;
494
+
495
+ let text = 'type = ' + this.constructor.name;
496
+ if (this.pos.x || this.pos.y)
497
+ text += '\npos = ' + this.pos;
498
+ if (this.velocity.x || this.velocity.y)
499
+ text += '\nvelocity = ' + this.velocity;
500
+ if (this.size.x || this.size.y)
501
+ text += '\nsize = ' + this.size;
502
+ if (this.angle)
503
+ text += '\nangle = ' + this.angle.toFixed(3);
504
+ if (this.color)
505
+ text += '\ncolor = ' + this.color;
506
+ return text;
503
507
  }
504
508
 
505
509
  /** Render debug info for this object */
@@ -224,17 +224,17 @@ let touchGamepadEnable = false;
224
224
  * @memberof Settings */
225
225
  let touchGamepadCenterButton = true;
226
226
 
227
- /** True if touch gamepad should be analog stick or false to use if 8 way dpad
228
- * @type {boolean}
227
+ /** Number of buttons on touch gamepad (0-4), if 1 also acts as right analog stick
228
+ * @type {number}
229
229
  * @default
230
230
  * @memberof Settings */
231
- let touchGamepadAnalog = true;
231
+ let touchGamepadButtonCount = 4;
232
232
 
233
- /** Number of buttons on touch gamepad
234
- * @type {number}
233
+ /** True if touch gamepad should be analog stick or false to use if 8 way dpad
234
+ * @type {boolean}
235
235
  * @default
236
236
  * @memberof Settings */
237
- let touchGamepadButtonCount = 4;
237
+ let touchGamepadAnalog = true;
238
238
 
239
239
  /** Size of virtual gamepad for touch devices in pixels
240
240
  * @type {number}
@@ -500,6 +500,11 @@ function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
500
500
  * @memberof Settings */
501
501
  function setTouchGamepadCenterButton(enable) { touchGamepadCenterButton = enable; }
502
502
 
503
+ /** Set number of buttons on touch gamepad (0-4), if 1 also acts as right analog stick
504
+ * @param {number} count
505
+ * @memberof Settings */
506
+ function setTouchGamepadButtonCount(count) { touchGamepadButtonCount = count; }
507
+
503
508
  /** Set if touch gamepad should be analog stick or 8 way dpad
504
509
  * @param {boolean} analog
505
510
  * @memberof Settings */
@@ -0,0 +1,173 @@
1
+ /**
2
+ * LittleJS Engine Splash Screen
3
+ * @namespace Settings
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ ///////////////////////////////////////////////////////////////////////////////
9
+ // LittleJS splash screen and logo
10
+
11
+ function drawEngineSplashScreen(t)
12
+ {
13
+ const x = overlayContext;
14
+ const w = overlayCanvas.width = innerWidth;
15
+ const h = overlayCanvas.height = innerHeight;
16
+
17
+ {
18
+ // background
19
+ const p3 = percent(t, 1, .8);
20
+ const p4 = percent(t, 0, .5);
21
+ const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
22
+ g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
23
+ g.addColorStop(1,hsl(0,0,0,p3).toString());
24
+ x.save();
25
+ x.fillStyle = g;
26
+ x.fillRect(0,0,w,h);
27
+ }
28
+
29
+ // draw LittleJS logo...
30
+ const rect = (X, Y, W, H, C)=>
31
+ {
32
+ x.beginPath();
33
+ x.rect(X,Y,W,C?H*p:H);
34
+ x.fillStyle = C;
35
+ C ? x.fill() : x.stroke();
36
+ };
37
+ const line = (X, Y, Z, W)=>
38
+ {
39
+ x.beginPath();
40
+ x.lineTo(X,Y);
41
+ x.lineTo(Z,W);
42
+ x.stroke();
43
+ };
44
+ const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
45
+ {
46
+ const D = (A+B)/2, E = p*(B-A)/2;
47
+ x.beginPath();
48
+ F && x.lineTo(X,Y);
49
+ x.arc(X,Y,R,D-E,D+E);
50
+ x.fillStyle = C;
51
+ C ? x.fill() : x.stroke();
52
+ };
53
+ const color = (c=0, l=0) =>
54
+ hsl([.98,.3,.57,.14][c%4],.8,[0,.3,.5,.8,.9][l]).toString();
55
+ const alpha = wave(1,1,t);
56
+ const p = percent(alpha, .1, .5);
57
+
58
+ // setup
59
+ x.translate(w/2,h/2);
60
+ const size = min(6, min(w,h)/99); // fit to screen
61
+ x.scale(size,size);
62
+ x.translate(-40,-35);
63
+ x.lineJoin = x.lineCap = 'round';
64
+ x.lineWidth = .1 + p*1.9;
65
+
66
+ // drawing effect
67
+ const p2 = percent(alpha,.1,1);
68
+ x.setLineDash([99*p2,99]);
69
+
70
+ // cab top
71
+ rect(7,16,18,-8,color(2,2));
72
+ rect(7,8,18,4,color(2,3));
73
+ rect(25,8,8,8,color(2,1));
74
+ rect(25,8,-18,8);
75
+ rect(25,8,8,8);
76
+
77
+ // cab
78
+ rect(25,16,7,23,color());
79
+ rect(11,39,14,-23,color(1,1));
80
+ rect(11,16,14,18,color(1,2));
81
+ rect(11,16,14,8,color(1,3));
82
+ rect(25,16,-14,24);
83
+
84
+ // cab window
85
+ rect(15,29,6,-9,color(2,2));
86
+ circle(15,21,5,0,PI/2,color(2,4),1);
87
+ rect(21,21,-6,9);
88
+
89
+ // little stack
90
+ rect(37,14,9,6,color(3,2));
91
+ rect(37,14,4.5,6,color(3,3));
92
+ rect(37,14,9,6);
93
+
94
+ // big stack
95
+ rect(50,20,10,-8,color(0,1));
96
+ rect(50,20,6.5,-8,color(0,2));
97
+ rect(50,20,3.5,-8,color(0,3));
98
+ rect(50,20,10,-8);
99
+ circle(55,2,11.4,.5,PI-.5,color(3,3));
100
+ circle(55,2,11.4,.5,PI/2,color(3,2),1);
101
+ circle(55,2,11.4,.5,PI-.5);
102
+ rect(45,7,20,-7,color(0,2));
103
+ rect(45,-1,20,4,color(0,3));
104
+ rect(45,-1,20,8);
105
+
106
+ // engine
107
+ for (let i=5; i--;)
108
+ {
109
+ // stagger radius to fix slight seam
110
+ circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
111
+ circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
112
+ circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
113
+ }
114
+
115
+ // engine outline
116
+ circle(36,30,10,PI/2,PI*3/2);
117
+ circle(48,30,10,PI/2,PI*3/2);
118
+ circle(60,30,10);
119
+ line(36,20,60,20);
120
+
121
+ // engine front light
122
+ circle(60,30,4,PI,3*PI,color(3,2));
123
+ circle(60,30,4,PI,2*PI,color(3,3));
124
+ circle(60,30,4,PI,3*PI);
125
+
126
+ // front brush
127
+ for (let i=6; i--;)
128
+ {
129
+ x.beginPath();
130
+ x.lineTo(53,54);
131
+ x.lineTo(53,40);
132
+ x.lineTo(53+(1+i*2.9)*p,40);
133
+ x.lineTo(53+(4+i*3.5)*p,54);
134
+ x.fillStyle = color(0,i%2+2);
135
+ x.fill();
136
+ i%2 && x.stroke();
137
+ }
138
+
139
+ // wheels
140
+ rect(6,40,5,5);
141
+ rect(6,40,5,5,color());
142
+ rect(15,54,38,-14,color());
143
+ for (let i=3; i--;)
144
+ for (let j=2; j--;)
145
+ {
146
+ circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
147
+ x.stroke();
148
+ circle(15*i+15,47,j?7:1,0,PI,color(i,2));
149
+ x.stroke();
150
+ }
151
+ line(6,40,68,40); // center
152
+ line(77,54,4,54); // bottom
153
+
154
+ // draw engine name
155
+ const s = engineName;
156
+ x.font = '900 16px arial';
157
+ x.textAlign = 'center';
158
+ x.textBaseline = 'top';
159
+ x.lineWidth = .1+p*3.9;
160
+ let w2 = 0;
161
+ for (let i=0; i<s.length; ++i)
162
+ w2 += x.measureText(s[i]).width;
163
+ for (let j=2; j--;)
164
+ for (let i=0, X=41-w2/2; i<s.length; ++i)
165
+ {
166
+ x.fillStyle = color(i,2);
167
+ const w = x.measureText(s[i]).width;
168
+ x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
169
+ X += w;
170
+ }
171
+
172
+ x.restore();
173
+ }
@@ -47,7 +47,7 @@ function tileCollisionTest(pos, size=vec2(), object, solidOnly=true)
47
47
  }
48
48
  }
49
49
 
50
- /** Return the exact position of the boudnary of first tile hit, undefined if nothing was hit.
50
+ /** Return the exact position of the boundary of first tile hit, undefined if nothing was hit.
51
51
  * The point will be inside the colliding tile if it hits (may have a tiny shift)
52
52
  * @param {Vector2} posStart
53
53
  * @param {Vector2} posEnd
@@ -553,7 +553,7 @@ class TileCollisionLayer extends TileLayer
553
553
  // remove from collision layers array and destroy
554
554
  const index = tileCollisionLayers.indexOf(this);
555
555
  ASSERT(index >= 0, 'tile collision layer not found in array');
556
- tileCollisionLayers.splice(index, 1);
556
+ index >= 0 && tileCollisionLayers.splice(index, 1);
557
557
  super.destroy();
558
558
  }
559
559
 
@@ -621,7 +621,7 @@ class TileCollisionLayer extends TileLayer
621
621
  return false;
622
622
  }
623
623
 
624
- /** Return the exact position of the boudnary of first tile hit, undefined if nothing was hit.
624
+ /** Return the exact position of the boundary of first tile hit, undefined if nothing was hit.
625
625
  * The point will be inside the colliding tile if it hits (may have a tiny shift)
626
626
  * @param {Vector2} posStart
627
627
  * @param {Vector2} posEnd
@@ -623,9 +623,15 @@ class Vector2
623
623
  {
624
624
  this.x = x;
625
625
  this.y = y;
626
+ ASSERT_VECTOR2_VALID(this);
626
627
  return this;
627
628
  }
628
629
 
630
+ /** Sets this vector from another vector and returns self
631
+ * @param {Vector2} v - other vector
632
+ * @return {Vector2} */
633
+ setFrom(v) { return this.set(v.x, v.y); }
634
+
629
635
  /** Returns a new vector that is a copy of this
630
636
  * @return {Vector2} */
631
637
  copy() { return new Vector2(this.x, this.y); }
@@ -688,7 +694,7 @@ class Vector2
688
694
  clampLength(length=1)
689
695
  {
690
696
  const l = this.length();
691
- return l > length ? this.scale(length/l) : this;
697
+ return l > length ? this.scale(length/l) : this.copy();
692
698
  }
693
699
 
694
700
  /** Returns the dot product of this and the vector passed in
@@ -775,6 +781,10 @@ class Vector2
775
781
  * @return {number} */
776
782
  area() { return abs(this.x * this.y); }
777
783
 
784
+ /** Returns true if this vector is (0,0)
785
+ * @return {boolean} */
786
+ isZero() { return !this.x && !this.y; }
787
+
778
788
  /** Returns a new vector that is p percent between this and the vector passed in
779
789
  * @param {Vector2} v - other vector
780
790
  * @param {number} percent
@@ -885,9 +895,15 @@ class Color
885
895
  this.g = g;
886
896
  this.b = b;
887
897
  this.a = a;
898
+ ASSERT_COLOR_VALID(this);
888
899
  return this;
889
900
  }
890
901
 
902
+ /** Sets this color from another color and returns self
903
+ * @param {Color} c - other color
904
+ * @return {Color} */
905
+ setFrom(c) { return this.set(c.r, c.g, c.b, c.a); }
906
+
891
907
  /** Returns a new color that is a copy of this
892
908
  * @return {Color} */
893
909
  copy() { return new Color(this.r, this.g, this.b, this.a); }