littlejsengine 1.9.1 → 1.9.3

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 (68) hide show
  1. package/README.md +11 -9
  2. package/{build → dist}/littlejs.d.ts +48 -43
  3. package/{build → dist}/littlejs.esm.js +256 -147
  4. package/dist/littlejs.esm.min.js +1 -0
  5. package/{build → dist}/littlejs.js +256 -147
  6. package/dist/littlejs.min.js +1 -0
  7. package/{build → dist}/littlejs.release.js +249 -144
  8. package/examples/breakout/game.js +2 -2
  9. package/examples/breakout/gameObjects.js +3 -3
  10. package/examples/breakout/index.html +3 -3
  11. package/examples/breakoutTutorial/index.html +2 -2
  12. package/examples/electron/build.js +1 -1
  13. package/examples/electron/game.js +49 -38
  14. package/examples/electron/index.html +2 -2
  15. package/examples/electron/tiles.png +0 -0
  16. package/examples/empty/index.html +1 -1
  17. package/examples/js13k/build.js +1 -1
  18. package/examples/js13k/game.js +10 -9
  19. package/examples/js13k/index.html +13 -13
  20. package/examples/js13k/tiles.png +0 -0
  21. package/examples/logo.png +0 -0
  22. package/examples/module/game.js +45 -41
  23. package/examples/module/index.html +1 -1
  24. package/examples/module/tiles.png +0 -0
  25. package/examples/particles/index.html +1 -1
  26. package/examples/platformer/data/gameLevelData.tmx +143 -0
  27. package/examples/platformer/data/gameLevelData.tsx +4 -0
  28. package/examples/platformer/game.js +31 -11
  29. package/examples/platformer/gameCharacter.js +9 -7
  30. package/examples/platformer/gameEffects.js +105 -164
  31. package/examples/platformer/gameLevel.js +164 -181
  32. package/examples/platformer/gameLevelData.js +181 -0
  33. package/examples/platformer/gameObjects.js +71 -26
  34. package/examples/platformer/gamePlayer.js +3 -2
  35. package/examples/platformer/index.html +8 -7
  36. package/examples/platformer/tiles.png +0 -0
  37. package/examples/platformer/tilesLevel.png +0 -0
  38. package/examples/puzzle/game.js +12 -12
  39. package/examples/puzzle/index.html +2 -2
  40. package/examples/screenshot.jpg +0 -0
  41. package/examples/starter/build.js +1 -1
  42. package/examples/starter/game.js +6 -6
  43. package/examples/starter/index.html +13 -13
  44. package/examples/starter/tiles.png +0 -0
  45. package/examples/stress/index.html +2 -2
  46. package/examples/typescript/build.bat +4 -1
  47. package/examples/typescript/game.js +34 -31
  48. package/examples/typescript/game.ts +40 -36
  49. package/examples/typescript/index.html +1 -1
  50. package/examples/typescript/tiles.png +0 -0
  51. package/package.json +3 -3
  52. package/reference.md +409 -0
  53. package/src/engine.js +46 -41
  54. package/src/engineAudio.js +22 -19
  55. package/src/engineBuild.js +9 -2
  56. package/src/engineDebug.js +7 -3
  57. package/src/engineDraw.js +8 -7
  58. package/src/engineInput.js +5 -5
  59. package/src/engineMedals.js +3 -3
  60. package/src/engineObject.js +2 -2
  61. package/src/engineParticles.js +16 -15
  62. package/src/engineSettings.js +3 -3
  63. package/src/engineTileLayer.js +3 -3
  64. package/src/engineUtilities.js +139 -38
  65. package/src/engineWebGL.js +2 -8
  66. package/build/littlejs.esm.min.js +0 -1
  67. package/build/littlejs.min.js +0 -1
  68. package/index.d.ts +0 -2094
@@ -71,7 +71,7 @@ function min(valueA, valueB) { return Math.min(valueA, valueB); }
71
71
  * @memberof Utilities */
72
72
  function max(valueA, valueB) { return Math.max(valueA, valueB); }
73
73
 
74
- /** Returns the sign of value passed in (also returns 1 if 0)
74
+ /** Returns the sign of value passed in
75
75
  * @param {Number} value
76
76
  * @return {Number}
77
77
  * @memberof Utilities */
@@ -118,7 +118,7 @@ function lerp(percent, valueA, valueB) { return valueA + clamp(percent) * (value
118
118
  function distanceWrap(valueA, valueB, wrapSize=1)
119
119
  { const d = (valueA - valueB) % wrapSize; return d*2 % wrapSize - d; }
120
120
 
121
- /** Linearly interpolates between values passed in with wrappping
121
+ /** Linearly interpolates between values passed in with wrapping
122
122
  * @param {Number} percent
123
123
  * @param {Number} valueA
124
124
  * @param {Number} valueB
@@ -135,7 +135,7 @@ function lerpWrap(percent, valueA, valueB, wrapSize=1)
135
135
  * @memberof Utilities */
136
136
  function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*PI); }
137
137
 
138
- /** Linearly interpolates between the angles passed in with wrappping
138
+ /** Linearly interpolates between the angles passed in with wrapping
139
139
  * @param {Number} percent
140
140
  * @param {Number} angleA
141
141
  * @param {Number} angleB
@@ -156,13 +156,13 @@ function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
156
156
  function nearestPowerOfTwo(value) { return 2**Math.ceil(Math.log2(value)); }
157
157
 
158
158
  /** Returns true if two axis aligned bounding boxes are overlapping
159
- * @param {Vector2} pointA - Center of box A
160
- * @param {Vector2} sizeA - Size of box A
161
- * @param {Vector2} pointB - Center of box B
162
- * @param {Vector2} sizeB - Size of box B
163
- * @return {Boolean} - True if overlapping
159
+ * @param {Vector2} pointA - Center of box A
160
+ * @param {Vector2} sizeA - Size of box A
161
+ * @param {Vector2} pointB - Center of box B
162
+ * @param {Vector2} [sizeB=(0,0)] - Size of box B, a point if undefined
163
+ * @return {Boolean} - True if overlapping
164
164
  * @memberof Utilities */
165
- function isOverlapping(pointA, sizeA, pointB, sizeB)
165
+ function isOverlapping(pointA, sizeA, pointB, sizeB=vec2())
166
166
  {
167
167
  return abs(pointA.x - pointB.x)*2 < sizeA.x + sizeB.x
168
168
  && abs(pointA.y - pointB.y)*2 < sizeA.y + sizeB.y;
@@ -222,8 +222,8 @@ function randInCircle(radius=1, minRadius=0)
222
222
  { return radius > 0 ? randVector(radius * rand(minRadius / radius, 1)**.5) : new Vector2; }
223
223
 
224
224
  /** Returns a random color between the two passed in colors, combine components if linear
225
- * @param {Color} [colorA=Color()]
226
- * @param {Color} [colorB=Color(0,0,0,1)]
225
+ * @param {Color} [colorA=(1,1,1,1)]
226
+ * @param {Color} [colorB=(0,0,0,1)]
227
227
  * @param {Boolean} [linear]
228
228
  * @return {Color}
229
229
  * @memberof Random */
@@ -294,7 +294,11 @@ class RandomGenerator
294
294
  * @memberof Utilities
295
295
  */
296
296
  function vec2(x=0, y)
297
- { return typeof x === 'number'? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y); }
297
+ {
298
+ return typeof x === 'number' ?
299
+ new Vector2(x, y == undefined? x : y) :
300
+ new Vector2(x.x, x.y);
301
+ }
298
302
 
299
303
  /**
300
304
  * Check if object is a valid Vector2
@@ -302,7 +306,7 @@ function vec2(x=0, y)
302
306
  * @return {Boolean}
303
307
  * @memberof Utilities
304
308
  */
305
- function isVector2(v) { return typeof v === 'object' && typeof v.x === 'number' && typeof v.y === 'number'; }
309
+ function isVector2(v) { return v instanceof Vector2; }
306
310
 
307
311
  /**
308
312
  * 2D Vector object with vector math library
@@ -333,27 +337,47 @@ class Vector2
333
337
  /** Returns a copy of this vector plus the vector passed in
334
338
  * @param {Vector2} v - other vector
335
339
  * @return {Vector2} */
336
- add(v) { ASSERT(isVector2(v)); return new Vector2(this.x + v.x, this.y + v.y); }
340
+ add(v)
341
+ {
342
+ ASSERT(isVector2(v));
343
+ return new Vector2(this.x + v.x, this.y + v.y);
344
+ }
337
345
 
338
346
  /** Returns a copy of this vector minus the vector passed in
339
347
  * @param {Vector2} v - other vector
340
348
  * @return {Vector2} */
341
- subtract(v) { ASSERT(isVector2(v)); return new Vector2(this.x - v.x, this.y - v.y); }
349
+ subtract(v)
350
+ {
351
+ ASSERT(isVector2(v));
352
+ return new Vector2(this.x - v.x, this.y - v.y);
353
+ }
342
354
 
343
355
  /** Returns a copy of this vector times the vector passed in
344
356
  * @param {Vector2} v - other vector
345
357
  * @return {Vector2} */
346
- multiply(v) { ASSERT(isVector2(v)); return new Vector2(this.x * v.x, this.y * v.y); }
358
+ multiply(v)
359
+ {
360
+ ASSERT(isVector2(v));
361
+ return new Vector2(this.x * v.x, this.y * v.y);
362
+ }
347
363
 
348
364
  /** Returns a copy of this vector divided by the vector passed in
349
365
  * @param {Vector2} v - other vector
350
366
  * @return {Vector2} */
351
- divide(v) { ASSERT(isVector2(v)); return new Vector2(this.x / v.x, this.y / v.y); }
367
+ divide(v)
368
+ {
369
+ ASSERT(isVector2(v));
370
+ return new Vector2(this.x / v.x, this.y / v.y);
371
+ }
352
372
 
353
373
  /** Returns a copy of this vector scaled by the vector passed in
354
374
  * @param {Number} s - scale
355
375
  * @return {Vector2} */
356
- scale(s) { ASSERT(!isVector2(s)); return new Vector2(this.x * s, this.y * s); }
376
+ scale(s)
377
+ {
378
+ ASSERT(!isVector2(s));
379
+ return new Vector2(this.x * s, this.y * s);
380
+ }
357
381
 
358
382
  /** Returns the length of this vector
359
383
  * @return {Number} */
@@ -366,32 +390,56 @@ class Vector2
366
390
  /** Returns the distance from this vector to vector passed in
367
391
  * @param {Vector2} v - other vector
368
392
  * @return {Number} */
369
- distance(v) { return this.distanceSquared(v)**.5; }
393
+ distance(v)
394
+ {
395
+ ASSERT(isVector2(v));
396
+ return this.distanceSquared(v)**.5;
397
+ }
370
398
 
371
399
  /** Returns the distance squared from this vector to vector passed in
372
400
  * @param {Vector2} v - other vector
373
401
  * @return {Number} */
374
- distanceSquared(v) { return (this.x - v.x)**2 + (this.y - v.y)**2; }
402
+ distanceSquared(v)
403
+ {
404
+ ASSERT(isVector2(v));
405
+ return (this.x - v.x)**2 + (this.y - v.y)**2;
406
+ }
375
407
 
376
408
  /** Returns a new vector in same direction as this one with the length passed in
377
409
  * @param {Number} [length]
378
410
  * @return {Vector2} */
379
- normalize(length=1) { const l = this.length(); return l ? this.scale(length/l) : new Vector2(0, length); }
411
+ normalize(length=1)
412
+ {
413
+ const l = this.length();
414
+ return l ? this.scale(length/l) : new Vector2(0, length);
415
+ }
380
416
 
381
417
  /** Returns a new vector clamped to length passed in
382
418
  * @param {Number} [length]
383
419
  * @return {Vector2} */
384
- clampLength(length=1) { const l = this.length(); return l > length ? this.scale(length/l) : this; }
420
+ clampLength(length=1)
421
+ {
422
+ const l = this.length();
423
+ return l > length ? this.scale(length/l) : this;
424
+ }
385
425
 
386
426
  /** Returns the dot product of this and the vector passed in
387
427
  * @param {Vector2} v - other vector
388
428
  * @return {Number} */
389
- dot(v) { ASSERT(isVector2(v)); return this.x*v.x + this.y*v.y; }
429
+ dot(v)
430
+ {
431
+ ASSERT(isVector2(v));
432
+ return this.x*v.x + this.y*v.y;
433
+ }
390
434
 
391
435
  /** Returns the cross product of this and the vector passed in
392
436
  * @param {Vector2} v - other vector
393
437
  * @return {Number} */
394
- cross(v) { ASSERT(isVector2(v)); return this.x*v.y - this.y*v.x; }
438
+ cross(v)
439
+ {
440
+ ASSERT(isVector2(v));
441
+ return this.x*v.y - this.y*v.x;
442
+ }
395
443
 
396
444
  /** Returns the angle of this vector, up is angle 0
397
445
  * @return {Number} */
@@ -402,7 +450,11 @@ class Vector2
402
450
  * @param {Number} [length]
403
451
  * @return {Vector2} */
404
452
  setAngle(angle=0, length=1)
405
- { this.x = length*Math.sin(angle); this.y = length*Math.cos(angle); return this; }
453
+ {
454
+ this.x = length*Math.sin(angle);
455
+ this.y = length*Math.cos(angle);
456
+ return this;
457
+ }
406
458
 
407
459
  /** Returns copy of this vector rotated by the angle passed in
408
460
  * @param {Number} angle
@@ -413,9 +465,20 @@ class Vector2
413
465
  return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
414
466
  }
415
467
 
468
+ /** Set the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
469
+ * @param {Number} [direction]
470
+ * @param {Number} [length] */
471
+ setDirection(direction, length=1)
472
+ {
473
+ ASSERT(direction==0 || direction==1 || direction==2 || direction==3);
474
+ return vec2(direction%2 ? direction-1 ? -length : length : 0,
475
+ direction%2 ? 0 : direction ? -length : length);
476
+ }
477
+
416
478
  /** Returns the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
417
479
  * @return {Number} */
418
- direction() { return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
480
+ direction()
481
+ { return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
419
482
 
420
483
  /** Returns a copy of this vector that has been inverted
421
484
  * @return {Vector2} */
@@ -434,24 +497,34 @@ class Vector2
434
497
  * @param {Number} percent
435
498
  * @return {Vector2} */
436
499
  lerp(v, percent)
437
- { ASSERT(isVector2(v)); return this.add(v.subtract(this).scale(clamp(percent))); }
500
+ {
501
+ ASSERT(isVector2(v));
502
+ return this.add(v.subtract(this).scale(clamp(percent)));
503
+ }
438
504
 
439
505
  /** Returns true if this vector is within the bounds of an array size passed in
440
506
  * @param {Vector2} arraySize
441
507
  * @return {Boolean} */
442
- arrayCheck(arraySize) { return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y; }
508
+ arrayCheck(arraySize)
509
+ {
510
+ ASSERT(isVector2(arraySize));
511
+ return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y;
512
+ }
443
513
 
444
514
  /** Returns this vector expressed as a string
445
515
  * @param {Number} digits - precision to display
446
516
  * @return {String} */
447
517
  toString(digits=3)
448
- { if (debug) { return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`; }}
518
+ {
519
+ if (debug)
520
+ return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`;
521
+ }
449
522
  }
450
523
 
451
524
  ///////////////////////////////////////////////////////////////////////////////
452
525
 
453
526
  /**
454
- * Create a color object with RGBA values
527
+ * Create a color object with RGBA values, white by default
455
528
  * @param {Number} [r=1] - red
456
529
  * @param {Number} [g=1] - green
457
530
  * @param {Number} [b=1] - blue
@@ -462,7 +535,7 @@ class Vector2
462
535
  function rgb(r, g, b, a) { return new Color(r, g, b, a); }
463
536
 
464
537
  /**
465
- * Create a color object with HSLA values
538
+ * Create a color object with HSLA values, white by default
466
539
  * @param {Number} [h=0] - hue
467
540
  * @param {Number} [s=0] - saturation
468
541
  * @param {Number} [l=1] - lightness
@@ -472,14 +545,22 @@ function rgb(r, g, b, a) { return new Color(r, g, b, a); }
472
545
  */
473
546
  function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
474
547
 
548
+ /**
549
+ * Check if object is a valid Color
550
+ * @param {any} c
551
+ * @return {Boolean}
552
+ * @memberof Utilities
553
+ */
554
+ function isColor(c) { return c instanceof Color; }
555
+
475
556
  /**
476
557
  * Color object (red, green, blue, alpha) with some helpful functions
477
558
  * @example
478
559
  * let a = new Color; // white
479
560
  * let b = new Color(1, 0, 0); // red
480
561
  * let c = new Color(0, 0, 0, 0); // transparent black
481
- * let d = RGB(0, 0, 1); // blue using rgb color
482
- * let e = HSL(.3, 1, .5); // green using hsl color
562
+ * let d = rgb(0, 0, 1); // blue using rgb color
563
+ * let e = hsl(.3, 1, .5); // green using hsl color
483
564
  */
484
565
  class Color
485
566
  {
@@ -507,22 +588,38 @@ class Color
507
588
  /** Returns a copy of this color plus the color passed in
508
589
  * @param {Color} c - other color
509
590
  * @return {Color} */
510
- add(c) { return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a); }
591
+ add(c)
592
+ {
593
+ ASSERT(isColor(c));
594
+ return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a);
595
+ }
511
596
 
512
597
  /** Returns a copy of this color minus the color passed in
513
598
  * @param {Color} c - other color
514
599
  * @return {Color} */
515
- subtract(c) { return new Color(this.r-c.r, this.g-c.g, this.b-c.b, this.a-c.a); }
600
+ subtract(c)
601
+ {
602
+ ASSERT(isColor(c));
603
+ return new Color(this.r-c.r, this.g-c.g, this.b-c.b, this.a-c.a);
604
+ }
516
605
 
517
606
  /** Returns a copy of this color times the color passed in
518
607
  * @param {Color} c - other color
519
608
  * @return {Color} */
520
- multiply(c) { return new Color(this.r*c.r, this.g*c.g, this.b*c.b, this.a*c.a); }
609
+ multiply(c)
610
+ {
611
+ ASSERT(isColor(c));
612
+ return new Color(this.r*c.r, this.g*c.g, this.b*c.b, this.a*c.a);
613
+ }
521
614
 
522
615
  /** Returns a copy of this color divided by the color passed in
523
616
  * @param {Color} c - other color
524
617
  * @return {Color} */
525
- divide(c) { return new Color(this.r/c.r, this.g/c.g, this.b/c.b, this.a/c.a); }
618
+ divide(c)
619
+ {
620
+ ASSERT(isColor(c));
621
+ return new Color(this.r/c.r, this.g/c.g, this.b/c.b, this.a/c.a);
622
+ }
526
623
 
527
624
  /** Returns a copy of this color scaled by the value passed in, alpha can be scaled separately
528
625
  * @param {Number} scale
@@ -539,7 +636,11 @@ class Color
539
636
  * @param {Color} c - other color
540
637
  * @param {Number} percent
541
638
  * @return {Color} */
542
- lerp(c, percent) { return this.add(c.subtract(this).scale(clamp(percent))); }
639
+ lerp(c, percent)
640
+ {
641
+ ASSERT(isColor(c));
642
+ return this.add(c.subtract(this).scale(clamp(percent)));
643
+ }
543
644
 
544
645
  /** Sets this color given a hue, saturation, lightness, and alpha
545
646
  * @param {Number} [h] - hue
@@ -776,7 +877,7 @@ let tileSizeDefault = vec2(16);
776
877
  * @type {Number}
777
878
  * @default
778
879
  * @memberof Settings */
779
- let tileFixBleedScale = .3;
880
+ let tileFixBleedScale = .1;
780
881
 
781
882
  ///////////////////////////////////////////////////////////////////////////////
782
883
  // Object settings
@@ -787,7 +888,7 @@ let tileFixBleedScale = .3;
787
888
  * @memberof Settings */
788
889
  let enablePhysicsSolver = true;
789
890
 
790
- /** Default object mass for collison calcuations (how heavy objects are)
891
+ /** Default object mass for collision calcuations (how heavy objects are)
791
892
  * @type {Number}
792
893
  * @default
793
894
  * @memberof Settings */
@@ -870,7 +971,7 @@ let touchGamepadEnable = false;
870
971
  * @memberof Settings */
871
972
  let touchGamepadAnalog = true;
872
973
 
873
- /** Size of virutal gamepad for touch devices in pixels
974
+ /** Size of virtual gamepad for touch devices in pixels
874
975
  * @type {Number}
875
976
  * @default
876
977
  * @memberof Settings */
@@ -1157,7 +1258,7 @@ function setDebugKey(key) { debugKey = key; }
1157
1258
  * - Automatically adds self to object list
1158
1259
  * - Will be updated and rendered each frame
1159
1260
  * - Renders as a sprite from a tilesheet by default
1160
- * - Can have color and addtive color applied
1261
+ * - Can have color and additive color applied
1161
1262
  * - 2D Physics and collision system
1162
1263
  * - Sorted by renderOrder
1163
1264
  * - Objects can have children attached
@@ -1436,7 +1537,7 @@ class EngineObject
1436
1537
  }
1437
1538
 
1438
1539
  /** Destroy this object, destroy it's children, detach it's parent, and mark it for removal */
1439
- destroy()
1540
+ destroy()
1440
1541
  {
1441
1542
  if (this.destroyed)
1442
1543
  return;
@@ -1625,13 +1726,9 @@ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
1625
1726
  if (typeof pos === 'number')
1626
1727
  {
1627
1728
  const textureInfo = textureInfos[textureIndex];
1628
- if (textureInfo)
1629
- {
1630
- const cols = textureInfo.size.x / size.x |0;
1631
- pos = vec2((pos%cols)*size.x, (pos/cols|0)*size.y);
1632
- }
1633
- else
1634
- pos = vec2();
1729
+ ASSERT(textureInfo, 'Texture not loaded');
1730
+ const cols = textureInfo.size.x / size.x |0;
1731
+ pos = vec2((pos%cols)*size.x, (pos/cols|0)*size.y);
1635
1732
  }
1636
1733
 
1637
1734
  // return a tile info object
@@ -1720,6 +1817,11 @@ function worldToScreen(worldPos)
1720
1817
  );
1721
1818
  }
1722
1819
 
1820
+ /** Get the camera's visible area in world space
1821
+ * @return {Vector2}
1822
+ * @memberof Draw */
1823
+ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
1824
+
1723
1825
  /** Draw textured tile centered in world space, with color applied if using WebGL
1724
1826
  * @param {Vector2} pos - Center of the tile in world space
1725
1827
  * @param {Vector2} [size=(1,1)] - Size of the tile in world space
@@ -2354,9 +2456,9 @@ if (isTouchDevice)
2354
2456
  // handle all touch events the same way
2355
2457
  ontouchstart = ontouchmove = ontouchend = (e)=>
2356
2458
  {
2357
- // fix stalled audio on mobile
2358
- if (soundEnable)
2359
- audioContext ? audioContext.resume() : zzfx(0);
2459
+ // fix stalled audio requiring user interaction
2460
+ if (soundEnable && audioContext && audioContext.state != 'running')
2461
+ zzfx(0);
2360
2462
 
2361
2463
  // check if touching and pass to mouse events
2362
2464
  const touching = e.touches.length;
@@ -2397,7 +2499,7 @@ function createTouchGamepad()
2397
2499
  touchGamepadStick = vec2();
2398
2500
 
2399
2501
  const touchHandler = ontouchstart;
2400
- ontouchstart = ontouchmove = ontouchend = (e)=>
2502
+ ontouchstart = ontouchmove = ontouchend = (e)=>
2401
2503
  {
2402
2504
  // clear touch gamepad input
2403
2505
  touchGamepadStick = vec2();
@@ -2502,7 +2604,7 @@ function touchGamepadRender()
2502
2604
  const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2503
2605
  for (let i=4; i--;)
2504
2606
  {
2505
- const pos = rightCenter.add(vec2().setAngle(i*PI/2, touchGamepadSize/2));
2607
+ const pos = rightCenter.add(vec2().setDirection(i, touchGamepadSize/2));
2506
2608
  overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
2507
2609
  overlayContext.beginPath();
2508
2610
  overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
@@ -2527,7 +2629,7 @@ function touchGamepadRender()
2527
2629
 
2528
2630
 
2529
2631
  /**
2530
- * Sound Object - Stores a zzfx sound for later use and can be played positionally
2632
+ * Sound Object - Stores a sound for later use and can be played positionally
2531
2633
  *
2532
2634
  * <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
2533
2635
  * @example
@@ -2542,7 +2644,7 @@ class Sound
2542
2644
  /** Create a sound object and cache the zzfx samples for later use
2543
2645
  * @param {Array} zzfxSound - Array of zzfx parameters, ex. [.5,.5]
2544
2646
  * @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
2545
- * @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
2647
+ * @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
2546
2648
  */
2547
2649
  constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
2548
2650
  {
@@ -2661,22 +2763,19 @@ class SoundWave extends Sound
2661
2763
  this.randomness = randomness;
2662
2764
 
2663
2765
  if (!soundEnable) return;
2664
- if (!soundDecoderContext)
2665
- soundDecoderContext = new AudioContext;
2666
2766
 
2667
2767
  fetch(filename)
2668
2768
  .then(response => response.arrayBuffer())
2669
- .then(arrayBuffer => soundDecoderContext.decodeAudioData(arrayBuffer))
2769
+ .then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))
2670
2770
  .then(audioBuffer =>
2671
2771
  {
2672
2772
  this.sampleChannels = [];
2673
2773
  for (let i = audioBuffer.numberOfChannels; i--;)
2674
- this.sampleChannels[i] = audioBuffer.getChannelData(i);
2774
+ this.sampleChannels[i] = Array.from(audioBuffer.getChannelData(i));
2675
2775
  this.sampleRate = audioBuffer.sampleRate;
2676
2776
  });
2677
2777
  }
2678
2778
  }
2679
- let soundDecoderContext; // audio context used only to decode audio files
2680
2779
 
2681
2780
  /**
2682
2781
  * Music Object - Stores a zzfx music track for later use
@@ -2725,24 +2824,24 @@ class Music extends Sound
2725
2824
 
2726
2825
  /** Play the music
2727
2826
  * @param {Number} [volume=1] - How much to scale volume by
2728
- * @param {Boolean} [loop=1] - True if the music should loop
2827
+ * @param {Boolean} [loop] - True if the music should loop
2729
2828
  * @return {AudioBufferSourceNode} - The audio source node
2730
2829
  */
2731
- playMusic(volume, loop = false)
2830
+ playMusic(volume, loop=false)
2732
2831
  { return super.play(undefined, volume, 1, 1, loop); }
2733
2832
  }
2734
2833
 
2735
2834
  /** Play an mp3, ogg, or wav audio from a local file or url
2736
- * @param {String} url - Location of sound file to play
2835
+ * @param {String} filename - Location of sound file to play
2737
2836
  * @param {Number} [volume] - How much to scale volume by
2738
2837
  * @param {Boolean} [loop] - True if the music should loop
2739
2838
  * @return {HTMLAudioElement} - The audio element for this sound
2740
2839
  * @memberof Audio */
2741
- function playAudioFile(url, volume=1, loop=false)
2840
+ function playAudioFile(filename, volume=1, loop=false)
2742
2841
  {
2743
2842
  if (!soundEnable) return;
2744
2843
 
2745
- const audio = new Audio(url);
2844
+ const audio = new Audio(filename);
2746
2845
  audio.volume = soundVolume * volume;
2747
2846
  audio.loop = loop;
2748
2847
  audio.play();
@@ -2790,8 +2889,14 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
2790
2889
  ///////////////////////////////////////////////////////////////////////////////
2791
2890
 
2792
2891
  /** Audio context used by the engine
2892
+ * @type {AudioContext}
2793
2893
  * @memberof Audio */
2794
- let audioContext;
2894
+ let audioContext = new AudioContext;
2895
+
2896
+ /** Keep track if audio was suspended when last sound was played
2897
+ * @type {Boolean}
2898
+ * @memberof Audio */
2899
+ let audioSuspended = false;
2795
2900
 
2796
2901
  /** Play cached audio samples with given settings
2797
2902
  * @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
@@ -2806,16 +2911,16 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
2806
2911
  {
2807
2912
  if (!soundEnable) return;
2808
2913
 
2809
- // create audio context if needed
2810
- if (!audioContext)
2811
- audioContext = new AudioContext;
2812
-
2813
2914
  // prevent sounds from building up if they can't be played
2814
- if (audioContext.state != 'running')
2915
+ const audioWasSuspended = audioSuspended;
2916
+ if (audioSuspended = audioContext.state != 'running')
2815
2917
  {
2816
2918
  // fix stalled audio
2817
2919
  audioContext.resume();
2818
- return;
2920
+
2921
+ // prevent suspended sounds from building up
2922
+ if (audioWasSuspended)
2923
+ return;
2819
2924
  }
2820
2925
 
2821
2926
  // create buffer and source
@@ -3083,7 +3188,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
3083
3188
  /**
3084
3189
  * LittleJS Tile Layer System
3085
3190
  * - Caches arrays of tiles to off screen canvas for fast rendering
3086
- * - Unlimted numbers of layers, allocates canvases as needed
3191
+ * - Unlimited numbers of layers, allocates canvases as needed
3087
3192
  * - Interfaces with EngineObject for collision
3088
3193
  * - Collision layer is separate from visible layers
3089
3194
  * - It is recommended to have a visible layer that matches the collision
@@ -3135,7 +3240,7 @@ function getTileCollisionData(pos)
3135
3240
 
3136
3241
  /** Check if collision with another object should occur
3137
3242
  * @param {Vector2} pos
3138
- * @param {Vector2} [size=(1,1)]
3243
+ * @param {Vector2} [size=(0,0)]
3139
3244
  * @param {EngineObject} [object]
3140
3245
  * @return {Boolean}
3141
3246
  * @memberof TileCollision */
@@ -3220,7 +3325,7 @@ class TileLayerData
3220
3325
  * @param {Number} [direction] - Integer direction of tile, in 90 degree increments
3221
3326
  * @param {Boolean} [mirror] - If the tile should be mirrored along the x axis
3222
3327
  * @param {Color} [color] - Color of the tile */
3223
- constructor(tile, direction=0, mirror=false, color=new Color())
3328
+ constructor(tile, direction=0, mirror=false, color=new Color)
3224
3329
  {
3225
3330
  /** @property {Number} - The tile to use, untextured if undefined */
3226
3331
  this.tile = tile;
@@ -3460,12 +3565,12 @@ class TileLayer extends EngineObject
3460
3565
  * @example
3461
3566
  * // create a particle emitter
3462
3567
  * let pos = vec2(2,3);
3463
- * let particleEmiter = new ParticleEmitter
3568
+ * let particleEmitter = new ParticleEmitter
3464
3569
  * (
3465
- * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
3466
- * tile(0, 16), // tileInfo
3467
- * new Color(1,1,1), new Color(0,0,0), // colorStartA, colorStartB
3468
- * new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
3570
+ * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
3571
+ * tile(0, 16), // tileInfo
3572
+ * rgb(1,1,1), rgb(0,0,0), // colorStartA, colorStartB
3573
+ * rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
3469
3574
  * 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
3470
3575
  * .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
3471
3576
  * .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
@@ -3652,16 +3757,17 @@ class ParticleEmitter extends EngineObject
3652
3757
 
3653
3758
  // build particle
3654
3759
  const particle = new Particle(pos, this.tileInfo, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, this.fadeRate, this.additive, this.trailScale, this.localSpace && this, this.particleDestroyCallback);
3655
- particle.velocity = vec2().setAngle(velocityAngle, speed);
3656
- particle.fadeRate = this.fadeRate;
3657
- particle.damping = this.damping;
3658
- particle.angleDamping = this.angleDamping;
3659
- particle.elasticity = this.elasticity;
3660
- particle.friction = this.friction;
3661
- particle.gravityScale = this.gravityScale;
3662
- particle.collideTiles = this.collideTiles;
3663
- particle.renderOrder = this.renderOrder;
3664
- particle.mirror = !!randInt(2);
3760
+ particle.velocity = vec2().setAngle(velocityAngle, speed);
3761
+ particle.angleVelocity = angleSpeed;
3762
+ particle.fadeRate = this.fadeRate;
3763
+ particle.damping = this.damping;
3764
+ particle.angleDamping = this.angleDamping;
3765
+ particle.elasticity = this.elasticity;
3766
+ particle.friction = this.friction;
3767
+ particle.gravityScale = this.gravityScale;
3768
+ particle.collideTiles = this.collideTiles;
3769
+ particle.renderOrder = this.renderOrder;
3770
+ particle.mirror = !!randInt(2);
3665
3771
 
3666
3772
  // call particle create callaback
3667
3773
  this.particleCreateCallback && this.particleCreateCallback(particle);
@@ -3872,8 +3978,8 @@ class Medal
3872
3978
  // draw containing rect and clip to that region
3873
3979
  context.save();
3874
3980
  context.beginPath();
3875
- context.fillStyle = rgb(.9,.9,.9).toString();
3876
- context.strokeStyle = rgb(0,0,0).toString();
3981
+ context.fillStyle = new Color(.9,.9,.9).toString();
3982
+ context.strokeStyle = new Color(0,0,0).toString();
3877
3983
  context.lineWidth = 3;
3878
3984
  context.rect(x, y, width, medalDisplaySize.y);
3879
3985
  context.fill();
@@ -4052,7 +4158,7 @@ class Newgrounds
4052
4158
  }
4053
4159
 
4054
4160
  // build the input object
4055
- const input =
4161
+ const input =
4056
4162
  {
4057
4163
  'app_id': this.app_id,
4058
4164
  'session_id': this.session_id,
@@ -4267,8 +4373,6 @@ function glCreateTexture(image)
4267
4373
  const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
4268
4374
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
4269
4375
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
4270
- glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE);
4271
- glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE);
4272
4376
 
4273
4377
  return texture;
4274
4378
  }
@@ -4325,7 +4429,7 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
4325
4429
  ASSERT(typeof rgba == 'number' && typeof rgbaAdditive == 'number', 'invalid color');
4326
4430
 
4327
4431
  // flush if there is not enough room or if different blend mode
4328
- if (glInstanceCount >= gl_MAX_INSTANCES-1 || glBatchAdditive != glAdditive)
4432
+ if (glInstanceCount >= gl_MAX_INSTANCES || glBatchAdditive != glAdditive)
4329
4433
  glFlush();
4330
4434
 
4331
4435
  let offset = glInstanceCount * gl_INDICIES_PER_INSTANCE;
@@ -4346,7 +4450,7 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
4346
4450
  ///////////////////////////////////////////////////////////////////////////////
4347
4451
  // post processing - can be enabled to pass other canvases through a final shader
4348
4452
 
4349
- let glPostShader, glPostArrayBuffer, glPostTexture, glPostIncludeOverlay;
4453
+ let glPostShader, glPostTexture, glPostIncludeOverlay;
4350
4454
 
4351
4455
  /** Set up a post processing shader
4352
4456
  * @param {String} shaderCode
@@ -4382,7 +4486,6 @@ function glInitPostProcess(shaderCode, includeOverlay=false)
4382
4486
  );
4383
4487
 
4384
4488
  // create buffer and texture
4385
- glPostArrayBuffer = glContext.createBuffer();
4386
4489
  glPostTexture = glCreateTexture(undefined);
4387
4490
  glPostIncludeOverlay = includeOverlay;
4388
4491
 
@@ -4454,10 +4557,7 @@ gl_NEAREST = 9728,
4454
4557
  gl_LINEAR = 9729,
4455
4558
  gl_TEXTURE_MAG_FILTER = 10240,
4456
4559
  gl_TEXTURE_MIN_FILTER = 10241,
4457
- gl_TEXTURE_WRAP_S = 10242,
4458
- gl_TEXTURE_WRAP_T = 10243,
4459
4560
  gl_COLOR_BUFFER_BIT = 16384,
4460
- gl_CLAMP_TO_EDGE = 33071,
4461
4561
  gl_TEXTURE0 = 33984,
4462
4562
  gl_ARRAY_BUFFER = 34962,
4463
4563
  gl_STATIC_DRAW = 35044,
@@ -4474,7 +4574,7 @@ gl_MAX_INSTANCES = 1e4,
4474
4574
  gl_INSTANCE_BYTE_STRIDE = gl_INDICIES_PER_INSTANCE * 4, // 11 * 4
4475
4575
  gl_INSTANCE_BUFFER_SIZE = gl_MAX_INSTANCES * gl_INSTANCE_BYTE_STRIDE;
4476
4576
  /**
4477
- * LittleJS - The Tiny JavaScript Game Engine That Can!
4577
+ * LittleJS - The Tiny Fast JavaScript Game Engine
4478
4578
  * MIT License - Copyright 2021 Frank Force
4479
4579
  *
4480
4580
  * Engine Features
@@ -4505,9 +4605,9 @@ const engineName = 'LittleJS';
4505
4605
  * @type {String}
4506
4606
  * @default
4507
4607
  * @memberof Engine */
4508
- const engineVersion = '1.9.1';
4608
+ const engineVersion = '1.9.3';
4509
4609
 
4510
- /** Frames per second to update objects
4610
+ /** Frames per second to update
4511
4611
  * @type {Number}
4512
4612
  * @default
4513
4613
  * @memberof Engine */
@@ -4524,7 +4624,7 @@ const timeDelta = 1/frameRate;
4524
4624
  * @memberof Engine */
4525
4625
  let engineObjects = [];
4526
4626
 
4527
- /** Array containing only objects that are set to collide with other objects this frame (for optimization)
4627
+ /** Array with only objects set to collide with other objects this frame (for optimization)
4528
4628
  * @type {Array}
4529
4629
  * @memberof Engine */
4530
4630
  let engineObjectsCollide = [];
@@ -4534,7 +4634,7 @@ let engineObjectsCollide = [];
4534
4634
  * @memberof Engine */
4535
4635
  let frame = 0;
4536
4636
 
4537
- /** Current engine time since start in seconds, derived from frame
4637
+ /** Current engine time since start in seconds
4538
4638
  * @type {Number}
4539
4639
  * @memberof Engine */
4540
4640
  let time = 0;
@@ -4560,12 +4660,12 @@ let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
4560
4660
 
4561
4661
  ///////////////////////////////////////////////////////////////////////////////
4562
4662
 
4563
- /** Start up LittleJS engine with your callback functions
4564
- * @param {Function} gameInit - Called once after the engine starts up, setup the game
4565
- * @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state
4566
- * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
4567
- * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
4568
- * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
4663
+ /** Startup LittleJS engine with your callback functions
4664
+ * @param {Function} gameInit - Called once after the engine starts up, setup the game
4665
+ * @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state
4666
+ * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
4667
+ * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
4668
+ * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
4569
4669
  * @param {Array} [imageSources=['tiles.png']] - Image to load
4570
4670
  * @memberof Engine */
4571
4671
  function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=['tiles.png'])
@@ -4588,32 +4688,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4588
4688
  frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
4589
4689
  if (!debugSpeedUp)
4590
4690
  frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp incase of slow framerate
4591
-
4592
- if (canvasFixedSize.x)
4593
- {
4594
- // clear canvas and set fixed size
4595
- mainCanvas.width = canvasFixedSize.x;
4596
- mainCanvas.height = canvasFixedSize.y;
4597
-
4598
- // fit to window by adding space on top or bottom if necessary
4599
- const aspect = innerWidth / innerHeight;
4600
- const fixedAspect = mainCanvas.width / mainCanvas.height;
4601
- (glCanvas||mainCanvas).style.width = mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
4602
- (glCanvas||mainCanvas).style.height = mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
4603
- }
4604
- else
4605
- {
4606
- // clear canvas and set size to same as window
4607
- mainCanvas.width = min(innerWidth, canvasMaxSize.x);
4608
- mainCanvas.height = min(innerHeight, canvasMaxSize.y);
4609
- }
4610
-
4611
- // clear overlay canvas and set size
4612
- overlayCanvas.width = mainCanvas.width;
4613
- overlayCanvas.height = mainCanvas.height;
4614
-
4615
- // save canvas size
4616
- mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
4691
+ updateCanvas();
4617
4692
 
4618
4693
  if (paused)
4619
4694
  {
@@ -4687,6 +4762,35 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4687
4762
  requestAnimationFrame(engineUpdate);
4688
4763
  }
4689
4764
 
4765
+ function updateCanvas()
4766
+ {
4767
+ if (canvasFixedSize.x)
4768
+ {
4769
+ // clear canvas and set fixed size
4770
+ mainCanvas.width = canvasFixedSize.x;
4771
+ mainCanvas.height = canvasFixedSize.y;
4772
+
4773
+ // fit to window by adding space on top or bottom if necessary
4774
+ const aspect = innerWidth / innerHeight;
4775
+ const fixedAspect = mainCanvas.width / mainCanvas.height;
4776
+ (glCanvas||mainCanvas).style.width = mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
4777
+ (glCanvas||mainCanvas).style.height = mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
4778
+ }
4779
+ else
4780
+ {
4781
+ // clear canvas and set size to same as window
4782
+ mainCanvas.width = min(innerWidth, canvasMaxSize.x);
4783
+ mainCanvas.height = min(innerHeight, canvasMaxSize.y);
4784
+ }
4785
+
4786
+ // clear overlay canvas and set size
4787
+ overlayCanvas.width = mainCanvas.width;
4788
+ overlayCanvas.height = mainCanvas.height;
4789
+
4790
+ // save canvas size
4791
+ mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
4792
+ }
4793
+
4690
4794
  // setup html
4691
4795
  const styleBody =
4692
4796
  'margin:0;overflow:hidden;' + // fill the window
@@ -4711,6 +4815,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4711
4815
  const styleCanvas = 'position:absolute;' + // position
4712
4816
  'top:50%;left:50%;transform:translate(-50%,-50%)'; // center
4713
4817
  (glCanvas||mainCanvas).style.cssText = mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
4818
+ updateCanvas();
4714
4819
 
4715
4820
  // create promises for loading images
4716
4821
  const promises = imageSources.map((src, textureIndex)=>
@@ -4907,9 +5012,9 @@ function drawEngineSplashScreen(t)
4907
5012
 
4908
5013
  // big stack
4909
5014
  rect(50,20,10,-10,color(0,1));
4910
- rect(50,20,6,-10,color(0,2));
4911
- rect(50,20,3,-10,color(0,3));
4912
- rect(50,10,10,10);
5015
+ rect(50,20,6.5,-10,color(0,2));
5016
+ rect(50,20,3.5,-10,color(0,3));
5017
+ rect(50,20,10,-10);
4913
5018
  circle(55,2,11.4,.5,PI-.5,color(3,3));
4914
5019
  circle(55,2,11.4,.5,PI/2,color(3,2),1);
4915
5020
  circle(55,2,11.4,.5,PI-.5);
@@ -4928,7 +5033,7 @@ function drawEngineSplashScreen(t)
4928
5033
 
4929
5034
  // engine outline
4930
5035
  circle(36,30,10,PI/2,PI*3/2);
4931
- circle(47,30,10,PI/2,PI*3/2);
5036
+ circle(48,30,10,PI/2,PI*3/2);
4932
5037
  circle(60,30,10);
4933
5038
  line(36,20,60,20);
4934
5039