littlejsengine 1.9.1 → 1.9.2

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 (62) hide show
  1. package/README.md +7 -7
  2. package/{build → dist}/littlejs.d.ts +25 -20
  3. package/{build → dist}/littlejs.esm.js +160 -61
  4. package/dist/littlejs.esm.min.js +1 -0
  5. package/{build → dist}/littlejs.js +160 -61
  6. package/dist/littlejs.min.js +1 -0
  7. package/{build → dist}/littlejs.release.js +154 -59
  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/module/game.js +45 -41
  22. package/examples/module/index.html +1 -1
  23. package/examples/module/tiles.png +0 -0
  24. package/examples/particles/index.html +1 -1
  25. package/examples/platformer/data/gameTileData.tmx +143 -0
  26. package/examples/platformer/data/gameTileData.tsx +4 -0
  27. package/examples/platformer/game.js +0 -1
  28. package/examples/platformer/gameCharacter.js +1 -1
  29. package/examples/platformer/gameEffects.js +42 -110
  30. package/examples/platformer/gameLevel.js +149 -183
  31. package/examples/platformer/gameObjects.js +61 -16
  32. package/examples/platformer/gamePlayer.js +3 -2
  33. package/examples/platformer/gameTileData.js +181 -0
  34. package/examples/platformer/index.html +8 -7
  35. package/examples/platformer/tiles.png +0 -0
  36. package/examples/platformer/tilesLevel.png +0 -0
  37. package/examples/puzzle/game.js +12 -12
  38. package/examples/puzzle/index.html +2 -2
  39. package/examples/starter/build.js +1 -1
  40. package/examples/starter/game.js +6 -6
  41. package/examples/starter/index.html +13 -13
  42. package/examples/starter/tiles.png +0 -0
  43. package/examples/stress/index.html +1 -1
  44. package/examples/typescript/build.bat +4 -1
  45. package/examples/typescript/game.js +34 -31
  46. package/examples/typescript/game.ts +40 -36
  47. package/examples/typescript/index.html +1 -1
  48. package/examples/typescript/tiles.png +0 -0
  49. package/package.json +3 -3
  50. package/src/engine.js +1 -1
  51. package/src/engineAudio.js +4 -10
  52. package/src/engineBuild.js +9 -2
  53. package/src/engineDebug.js +6 -2
  54. package/src/engineInput.js +4 -4
  55. package/src/engineMedals.js +2 -2
  56. package/src/engineParticles.js +5 -5
  57. package/src/engineTileLayer.js +1 -1
  58. package/src/engineUtilities.js +136 -35
  59. package/src/engineWebGL.js +1 -1
  60. package/build/littlejs.esm.min.js +0 -1
  61. package/build/littlejs.min.js +0 -1
  62. package/index.d.ts +0 -2094
package/README.md CHANGED
@@ -54,7 +54,7 @@ LittleJS is a small but powerful game engine with many features and no dependenc
54
54
 
55
55
  ### 💥 Physics
56
56
 
57
- - Robust 2D physics engine with collision handling for axis aligned boxes
57
+ - Robust arcade physics system with collision handling
58
58
  - Very fast collision and raycasting for tile maps
59
59
 
60
60
  ### 🚀 Flexibility
@@ -91,7 +91,7 @@ These examples are for both learning and using as starter projects to create you
91
91
  - [Starter Project](https://killedbyapixel.github.io/LittleJS/examples/starter/) - Clean example with only a few things to get you started
92
92
  - [Breakout](https://killedbyapixel.github.io/LittleJS/examples/breakout/) - Block breaking game with post-processing effects
93
93
  - [Puzzle Game](https://killedbyapixel.github.io/LittleJS/examples/puzzle/) - Match 3 puzzle game with HD rendering and high score tracking
94
- - [Platformer](https://killedbyapixel.github.io/LittleJS/examples/platformer/) - Platformer/shooter with procedural generation and destruction
94
+ - [Platformer](https://killedbyapixel.github.io/LittleJS/examples/platformer/) - Platformer/shooter with level data from [Tiled Editor](https://github.com/mapeditor/tiled)
95
95
  - [Stress Test](https://killedbyapixel.github.io/LittleJS/examples/stress/) - Max sprite/object test and music system demo
96
96
  - [Particle System Designer](https://killedbyapixel.github.io/LittleJS/examples/particles/) - Particle system editor and visualizer
97
97
 
@@ -99,11 +99,11 @@ These examples are for both learning and using as starter projects to create you
99
99
 
100
100
  To easily include LittleJS in your game, you can use one of the pre-built js files.
101
101
 
102
- - [littlejs.js](https://github.com/KilledByAPixel/LittleJS/blob/main/build/littlejs.js) - The full game engine with debug mode available
103
- - [littlejs.release.js](https://github.com/KilledByAPixel/LittleJS/blob/main/build/littlejs.release.js) - The engine optimized for release builds
104
- - [littlejs.min.js](https://github.com/KilledByAPixel/LittleJS/blob/main/build/littlejs.min.js) - The engine in release mode and minified
105
- - [littlejs.esm.js](https://github.com/KilledByAPixel/LittleJS/blob/main/build/littlejs.esm.js) - The engine exported as a module with debug mode available
106
- - [littlejs.esm.min.js](https://github.com/KilledByAPixel/LittleJS/blob/main/build/littlejs.esm.min.js) - The engine exported as a minified module in release mode
102
+ - [littlejs.js](https://github.com/KilledByAPixel/LittleJS/blob/main/dist/littlejs.js) - The full game engine with debug mode available
103
+ - [littlejs.release.js](https://github.com/KilledByAPixel/LittleJS/blob/main/dist/littlejs.release.js) - The engine optimized for release builds
104
+ - [littlejs.min.js](https://github.com/KilledByAPixel/LittleJS/blob/main/dist/littlejs.min.js) - The engine in release mode and minified
105
+ - [littlejs.esm.js](https://github.com/KilledByAPixel/LittleJS/blob/main/dist/littlejs.esm.js) - The engine exported as a module with debug mode available
106
+ - [littlejs.esm.min.js](https://github.com/KilledByAPixel/LittleJS/blob/main/dist/littlejs.esm.min.js) - The engine exported as a minified module in release mode
107
107
 
108
108
  To rebuild the engine you must first run ```npm install``` to setup the necessary npm dependencies. Then call ```npm run build``` to build the engine.
109
109
 
@@ -111,9 +111,9 @@ declare module "littlejs.esm" {
111
111
  export let showWatermark: boolean;
112
112
  /** Asserts if the experssion is false, does not do anything in release builds
113
113
  * @param {Boolean} assert
114
- * @param {Object} output
114
+ * @param {Object} [output]
115
115
  * @memberof Debug */
116
- export function ASSERT(assert: boolean, output: any): void;
116
+ export function ASSERT(assert: boolean, output?: any): void;
117
117
  /** Draw a debug rectangle in world space
118
118
  * @param {Vector2} pos
119
119
  * @param {Vector2} [size=Vector2()]
@@ -619,13 +619,13 @@ declare module "littlejs.esm" {
619
619
  * @memberof Utilities */
620
620
  export function nearestPowerOfTwo(value: number): number;
621
621
  /** Returns true if two axis aligned bounding boxes are overlapping
622
- * @param {Vector2} pointA - Center of box A
623
- * @param {Vector2} sizeA - Size of box A
624
- * @param {Vector2} pointB - Center of box B
625
- * @param {Vector2} sizeB - Size of box B
626
- * @return {Boolean} - True if overlapping
622
+ * @param {Vector2} pointA - Center of box A
623
+ * @param {Vector2} sizeA - Size of box A
624
+ * @param {Vector2} pointB - Center of box B
625
+ * @param {Vector2} [sizeB=(0,0)] - Size of box B, a point if undefined
626
+ * @return {Boolean} - True if overlapping
627
627
  * @memberof Utilities */
628
- export function isOverlapping(pointA: Vector2, sizeA: Vector2, pointB: Vector2, sizeB: Vector2): boolean;
628
+ export function isOverlapping(pointA: Vector2, sizeA: Vector2, pointB: Vector2, sizeB?: Vector2): boolean;
629
629
  /** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
630
630
  * @param {Number} [frequency] - Frequency of the wave in Hz
631
631
  * @param {Number} [amplitude] - Amplitude (max height) of the wave
@@ -668,8 +668,8 @@ declare module "littlejs.esm" {
668
668
  * @memberof Random */
669
669
  export function randVector(length?: number): Vector2;
670
670
  /** Returns a random color between the two passed in colors, combine components if linear
671
- * @param {Color} [colorA=Color()]
672
- * @param {Color} [colorB=Color(0,0,0,1)]
671
+ * @param {Color} [colorA=(1,1,1,1)]
672
+ * @param {Color} [colorB=(0,0,0,1)]
673
673
  * @param {Boolean} [linear]
674
674
  * @return {Color}
675
675
  * @memberof Random */
@@ -787,6 +787,10 @@ declare module "littlejs.esm" {
787
787
  * @param {Number} angle
788
788
  * @return {Vector2} */
789
789
  rotate(angle: number): Vector2;
790
+ /** Set the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
791
+ * @param {Number} [direction]
792
+ * @param {Number} [length] */
793
+ setDirection(direction?: number, length?: number): Vector2;
790
794
  /** Returns the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
791
795
  * @return {Number} */
792
796
  direction(): number;
@@ -819,8 +823,8 @@ declare module "littlejs.esm" {
819
823
  * let a = new Color; // white
820
824
  * let b = new Color(1, 0, 0); // red
821
825
  * let c = new Color(0, 0, 0, 0); // transparent black
822
- * let d = RGB(0, 0, 1); // blue using rgb color
823
- * let e = HSL(.3, 1, .5); // green using hsl color
826
+ * let d = rgb(0, 0, 1); // blue using rgb color
827
+ * let e = hsl(.3, 1, .5); // green using hsl color
824
828
  */
825
829
  export class Color {
826
830
  /** Create a color with the rgba components passed in, white by default
@@ -952,7 +956,7 @@ declare module "littlejs.esm" {
952
956
  */
953
957
  export function vec2(x?: (number | Vector2), y?: number): Vector2;
954
958
  /**
955
- * Create a color object with RGBA values
959
+ * Create a color object with RGBA values, white by default
956
960
  * @param {Number} [r=1] - red
957
961
  * @param {Number} [g=1] - green
958
962
  * @param {Number} [b=1] - blue
@@ -962,7 +966,7 @@ declare module "littlejs.esm" {
962
966
  */
963
967
  export function rgb(r?: number, g?: number, b?: number, a?: number): Color;
964
968
  /**
965
- * Create a color object with HSLA values
969
+ * Create a color object with HSLA values, white by default
966
970
  * @param {Number} [h=0] - hue
967
971
  * @param {Number} [s=0] - saturation
968
972
  * @param {Number} [l=1] - lightness
@@ -1520,8 +1524,9 @@ declare module "littlejs.esm" {
1520
1524
  * @memberof Audio */
1521
1525
  export function getNoteFrequency(semitoneOffset: number, rootFrequency?: number): number;
1522
1526
  /** Audio context used by the engine
1527
+ * @type {AudioContext}
1523
1528
  * @memberof Audio */
1524
- export let audioContext: any;
1529
+ export let audioContext: AudioContext;
1525
1530
  /** Play cached audio samples with given settings
1526
1531
  * @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
1527
1532
  * @param {Number} [volume] - How much to scale volume by
@@ -1842,12 +1847,12 @@ declare module "littlejs.esm" {
1842
1847
  * @example
1843
1848
  * // create a particle emitter
1844
1849
  * let pos = vec2(2,3);
1845
- * let particleEmiter = new ParticleEmitter
1850
+ * let particleEmitter = new ParticleEmitter
1846
1851
  * (
1847
- * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
1848
- * tile(0, 16), // tileInfo
1849
- * new Color(1,1,1), new Color(0,0,0), // colorStartA, colorStartB
1850
- * new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
1852
+ * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
1853
+ * tile(0, 16), // tileInfo
1854
+ * rgb(1,1,1), rgb(0,0,0), // colorStartA, colorStartB
1855
+ * rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
1851
1856
  * 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
1852
1857
  * .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
1853
1858
  * .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
@@ -58,9 +58,13 @@ let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParti
58
58
 
59
59
  /** Asserts if the experssion is false, does not do anything in release builds
60
60
  * @param {Boolean} assert
61
- * @param {Object} output
61
+ * @param {Object} [output]
62
62
  * @memberof Debug */
63
- function ASSERT(assert, output) { enableAsserts && console.assert(assert, output); }
63
+ function ASSERT(assert, output)
64
+ {
65
+ if (enableAsserts)
66
+ output ? console.assert(assert, output) : console.assert(assert);
67
+ }
64
68
 
65
69
  /** Draw a debug rectangle in world space
66
70
  * @param {Vector2} pos
@@ -534,13 +538,13 @@ function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
534
538
  function nearestPowerOfTwo(value) { return 2**Math.ceil(Math.log2(value)); }
535
539
 
536
540
  /** Returns true if two axis aligned bounding boxes are overlapping
537
- * @param {Vector2} pointA - Center of box A
538
- * @param {Vector2} sizeA - Size of box A
539
- * @param {Vector2} pointB - Center of box B
540
- * @param {Vector2} sizeB - Size of box B
541
- * @return {Boolean} - True if overlapping
541
+ * @param {Vector2} pointA - Center of box A
542
+ * @param {Vector2} sizeA - Size of box A
543
+ * @param {Vector2} pointB - Center of box B
544
+ * @param {Vector2} [sizeB=(0,0)] - Size of box B, a point if undefined
545
+ * @return {Boolean} - True if overlapping
542
546
  * @memberof Utilities */
543
- function isOverlapping(pointA, sizeA, pointB, sizeB)
547
+ function isOverlapping(pointA, sizeA, pointB, sizeB=vec2())
544
548
  {
545
549
  return abs(pointA.x - pointB.x)*2 < sizeA.x + sizeB.x
546
550
  && abs(pointA.y - pointB.y)*2 < sizeA.y + sizeB.y;
@@ -600,8 +604,8 @@ function randInCircle(radius=1, minRadius=0)
600
604
  { return radius > 0 ? randVector(radius * rand(minRadius / radius, 1)**.5) : new Vector2; }
601
605
 
602
606
  /** Returns a random color between the two passed in colors, combine components if linear
603
- * @param {Color} [colorA=Color()]
604
- * @param {Color} [colorB=Color(0,0,0,1)]
607
+ * @param {Color} [colorA=(1,1,1,1)]
608
+ * @param {Color} [colorB=(0,0,0,1)]
605
609
  * @param {Boolean} [linear]
606
610
  * @return {Color}
607
611
  * @memberof Random */
@@ -672,7 +676,11 @@ class RandomGenerator
672
676
  * @memberof Utilities
673
677
  */
674
678
  function vec2(x=0, y)
675
- { return typeof x === 'number'? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y); }
679
+ {
680
+ return typeof x === 'number' ?
681
+ new Vector2(x, y == undefined? x : y) :
682
+ new Vector2(x.x, x.y);
683
+ }
676
684
 
677
685
  /**
678
686
  * Check if object is a valid Vector2
@@ -680,7 +688,7 @@ function vec2(x=0, y)
680
688
  * @return {Boolean}
681
689
  * @memberof Utilities
682
690
  */
683
- function isVector2(v) { return typeof v === 'object' && typeof v.x === 'number' && typeof v.y === 'number'; }
691
+ function isVector2(v) { return v instanceof Vector2; }
684
692
 
685
693
  /**
686
694
  * 2D Vector object with vector math library
@@ -711,27 +719,47 @@ class Vector2
711
719
  /** Returns a copy of this vector plus the vector passed in
712
720
  * @param {Vector2} v - other vector
713
721
  * @return {Vector2} */
714
- add(v) { ASSERT(isVector2(v)); return new Vector2(this.x + v.x, this.y + v.y); }
722
+ add(v)
723
+ {
724
+ ASSERT(isVector2(v));
725
+ return new Vector2(this.x + v.x, this.y + v.y);
726
+ }
715
727
 
716
728
  /** Returns a copy of this vector minus the vector passed in
717
729
  * @param {Vector2} v - other vector
718
730
  * @return {Vector2} */
719
- subtract(v) { ASSERT(isVector2(v)); return new Vector2(this.x - v.x, this.y - v.y); }
731
+ subtract(v)
732
+ {
733
+ ASSERT(isVector2(v));
734
+ return new Vector2(this.x - v.x, this.y - v.y);
735
+ }
720
736
 
721
737
  /** Returns a copy of this vector times the vector passed in
722
738
  * @param {Vector2} v - other vector
723
739
  * @return {Vector2} */
724
- multiply(v) { ASSERT(isVector2(v)); return new Vector2(this.x * v.x, this.y * v.y); }
740
+ multiply(v)
741
+ {
742
+ ASSERT(isVector2(v));
743
+ return new Vector2(this.x * v.x, this.y * v.y);
744
+ }
725
745
 
726
746
  /** Returns a copy of this vector divided by the vector passed in
727
747
  * @param {Vector2} v - other vector
728
748
  * @return {Vector2} */
729
- divide(v) { ASSERT(isVector2(v)); return new Vector2(this.x / v.x, this.y / v.y); }
749
+ divide(v)
750
+ {
751
+ ASSERT(isVector2(v));
752
+ return new Vector2(this.x / v.x, this.y / v.y);
753
+ }
730
754
 
731
755
  /** Returns a copy of this vector scaled by the vector passed in
732
756
  * @param {Number} s - scale
733
757
  * @return {Vector2} */
734
- scale(s) { ASSERT(!isVector2(s)); return new Vector2(this.x * s, this.y * s); }
758
+ scale(s)
759
+ {
760
+ ASSERT(!isVector2(s));
761
+ return new Vector2(this.x * s, this.y * s);
762
+ }
735
763
 
736
764
  /** Returns the length of this vector
737
765
  * @return {Number} */
@@ -744,32 +772,56 @@ class Vector2
744
772
  /** Returns the distance from this vector to vector passed in
745
773
  * @param {Vector2} v - other vector
746
774
  * @return {Number} */
747
- distance(v) { return this.distanceSquared(v)**.5; }
775
+ distance(v)
776
+ {
777
+ ASSERT(isVector2(v));
778
+ return this.distanceSquared(v)**.5;
779
+ }
748
780
 
749
781
  /** Returns the distance squared from this vector to vector passed in
750
782
  * @param {Vector2} v - other vector
751
783
  * @return {Number} */
752
- distanceSquared(v) { return (this.x - v.x)**2 + (this.y - v.y)**2; }
784
+ distanceSquared(v)
785
+ {
786
+ ASSERT(isVector2(v));
787
+ return (this.x - v.x)**2 + (this.y - v.y)**2;
788
+ }
753
789
 
754
790
  /** Returns a new vector in same direction as this one with the length passed in
755
791
  * @param {Number} [length]
756
792
  * @return {Vector2} */
757
- normalize(length=1) { const l = this.length(); return l ? this.scale(length/l) : new Vector2(0, length); }
793
+ normalize(length=1)
794
+ {
795
+ const l = this.length();
796
+ return l ? this.scale(length/l) : new Vector2(0, length);
797
+ }
758
798
 
759
799
  /** Returns a new vector clamped to length passed in
760
800
  * @param {Number} [length]
761
801
  * @return {Vector2} */
762
- clampLength(length=1) { const l = this.length(); return l > length ? this.scale(length/l) : this; }
802
+ clampLength(length=1)
803
+ {
804
+ const l = this.length();
805
+ return l > length ? this.scale(length/l) : this;
806
+ }
763
807
 
764
808
  /** Returns the dot product of this and the vector passed in
765
809
  * @param {Vector2} v - other vector
766
810
  * @return {Number} */
767
- dot(v) { ASSERT(isVector2(v)); return this.x*v.x + this.y*v.y; }
811
+ dot(v)
812
+ {
813
+ ASSERT(isVector2(v));
814
+ return this.x*v.x + this.y*v.y;
815
+ }
768
816
 
769
817
  /** Returns the cross product of this and the vector passed in
770
818
  * @param {Vector2} v - other vector
771
819
  * @return {Number} */
772
- cross(v) { ASSERT(isVector2(v)); return this.x*v.y - this.y*v.x; }
820
+ cross(v)
821
+ {
822
+ ASSERT(isVector2(v));
823
+ return this.x*v.y - this.y*v.x;
824
+ }
773
825
 
774
826
  /** Returns the angle of this vector, up is angle 0
775
827
  * @return {Number} */
@@ -780,7 +832,11 @@ class Vector2
780
832
  * @param {Number} [length]
781
833
  * @return {Vector2} */
782
834
  setAngle(angle=0, length=1)
783
- { this.x = length*Math.sin(angle); this.y = length*Math.cos(angle); return this; }
835
+ {
836
+ this.x = length*Math.sin(angle);
837
+ this.y = length*Math.cos(angle);
838
+ return this;
839
+ }
784
840
 
785
841
  /** Returns copy of this vector rotated by the angle passed in
786
842
  * @param {Number} angle
@@ -791,9 +847,20 @@ class Vector2
791
847
  return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
792
848
  }
793
849
 
850
+ /** Set the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
851
+ * @param {Number} [direction]
852
+ * @param {Number} [length] */
853
+ setDirection(direction, length=1)
854
+ {
855
+ ASSERT(direction==0 || direction==1 || direction==2 || direction==3);
856
+ return vec2(direction%2 ? direction-1 ? -length : length : 0,
857
+ direction%2 ? 0 : direction ? -length : length);
858
+ }
859
+
794
860
  /** Returns the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
795
861
  * @return {Number} */
796
- direction() { return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
862
+ direction()
863
+ { return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
797
864
 
798
865
  /** Returns a copy of this vector that has been inverted
799
866
  * @return {Vector2} */
@@ -812,24 +879,34 @@ class Vector2
812
879
  * @param {Number} percent
813
880
  * @return {Vector2} */
814
881
  lerp(v, percent)
815
- { ASSERT(isVector2(v)); return this.add(v.subtract(this).scale(clamp(percent))); }
882
+ {
883
+ ASSERT(isVector2(v));
884
+ return this.add(v.subtract(this).scale(clamp(percent)));
885
+ }
816
886
 
817
887
  /** Returns true if this vector is within the bounds of an array size passed in
818
888
  * @param {Vector2} arraySize
819
889
  * @return {Boolean} */
820
- arrayCheck(arraySize) { return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y; }
890
+ arrayCheck(arraySize)
891
+ {
892
+ ASSERT(isVector2(arraySize));
893
+ return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y;
894
+ }
821
895
 
822
896
  /** Returns this vector expressed as a string
823
897
  * @param {Number} digits - precision to display
824
898
  * @return {String} */
825
899
  toString(digits=3)
826
- { if (debug) { return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`; }}
900
+ {
901
+ if (debug)
902
+ return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`;
903
+ }
827
904
  }
828
905
 
829
906
  ///////////////////////////////////////////////////////////////////////////////
830
907
 
831
908
  /**
832
- * Create a color object with RGBA values
909
+ * Create a color object with RGBA values, white by default
833
910
  * @param {Number} [r=1] - red
834
911
  * @param {Number} [g=1] - green
835
912
  * @param {Number} [b=1] - blue
@@ -840,7 +917,7 @@ class Vector2
840
917
  function rgb(r, g, b, a) { return new Color(r, g, b, a); }
841
918
 
842
919
  /**
843
- * Create a color object with HSLA values
920
+ * Create a color object with HSLA values, white by default
844
921
  * @param {Number} [h=0] - hue
845
922
  * @param {Number} [s=0] - saturation
846
923
  * @param {Number} [l=1] - lightness
@@ -850,14 +927,22 @@ function rgb(r, g, b, a) { return new Color(r, g, b, a); }
850
927
  */
851
928
  function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
852
929
 
930
+ /**
931
+ * Check if object is a valid Color
932
+ * @param {any} c
933
+ * @return {Boolean}
934
+ * @memberof Utilities
935
+ */
936
+ function isColor(c) { return c instanceof Color; }
937
+
853
938
  /**
854
939
  * Color object (red, green, blue, alpha) with some helpful functions
855
940
  * @example
856
941
  * let a = new Color; // white
857
942
  * let b = new Color(1, 0, 0); // red
858
943
  * let c = new Color(0, 0, 0, 0); // transparent black
859
- * let d = RGB(0, 0, 1); // blue using rgb color
860
- * let e = HSL(.3, 1, .5); // green using hsl color
944
+ * let d = rgb(0, 0, 1); // blue using rgb color
945
+ * let e = hsl(.3, 1, .5); // green using hsl color
861
946
  */
862
947
  class Color
863
948
  {
@@ -885,22 +970,38 @@ class Color
885
970
  /** Returns a copy of this color plus the color passed in
886
971
  * @param {Color} c - other color
887
972
  * @return {Color} */
888
- add(c) { return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a); }
973
+ add(c)
974
+ {
975
+ ASSERT(isColor(c));
976
+ return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a);
977
+ }
889
978
 
890
979
  /** Returns a copy of this color minus the color passed in
891
980
  * @param {Color} c - other color
892
981
  * @return {Color} */
893
- subtract(c) { return new Color(this.r-c.r, this.g-c.g, this.b-c.b, this.a-c.a); }
982
+ subtract(c)
983
+ {
984
+ ASSERT(isColor(c));
985
+ return new Color(this.r-c.r, this.g-c.g, this.b-c.b, this.a-c.a);
986
+ }
894
987
 
895
988
  /** Returns a copy of this color times the color passed in
896
989
  * @param {Color} c - other color
897
990
  * @return {Color} */
898
- multiply(c) { return new Color(this.r*c.r, this.g*c.g, this.b*c.b, this.a*c.a); }
991
+ multiply(c)
992
+ {
993
+ ASSERT(isColor(c));
994
+ return new Color(this.r*c.r, this.g*c.g, this.b*c.b, this.a*c.a);
995
+ }
899
996
 
900
997
  /** Returns a copy of this color divided by the color passed in
901
998
  * @param {Color} c - other color
902
999
  * @return {Color} */
903
- divide(c) { return new Color(this.r/c.r, this.g/c.g, this.b/c.b, this.a/c.a); }
1000
+ divide(c)
1001
+ {
1002
+ ASSERT(isColor(c));
1003
+ return new Color(this.r/c.r, this.g/c.g, this.b/c.b, this.a/c.a);
1004
+ }
904
1005
 
905
1006
  /** Returns a copy of this color scaled by the value passed in, alpha can be scaled separately
906
1007
  * @param {Number} scale
@@ -917,7 +1018,11 @@ class Color
917
1018
  * @param {Color} c - other color
918
1019
  * @param {Number} percent
919
1020
  * @return {Color} */
920
- lerp(c, percent) { return this.add(c.subtract(this).scale(clamp(percent))); }
1021
+ lerp(c, percent)
1022
+ {
1023
+ ASSERT(isColor(c));
1024
+ return this.add(c.subtract(this).scale(clamp(percent)));
1025
+ }
921
1026
 
922
1027
  /** Sets this color given a hue, saturation, lightness, and alpha
923
1028
  * @param {Number} [h] - hue
@@ -2732,9 +2837,9 @@ if (isTouchDevice)
2732
2837
  // handle all touch events the same way
2733
2838
  ontouchstart = ontouchmove = ontouchend = (e)=>
2734
2839
  {
2735
- // fix stalled audio on mobile
2736
- if (soundEnable)
2737
- audioContext ? audioContext.resume() : zzfx(0);
2840
+ // fix stalled audio requiring user interaction
2841
+ if (soundEnable && audioContext && audioContext.state != 'running')
2842
+ zzfx(0);
2738
2843
 
2739
2844
  // check if touching and pass to mouse events
2740
2845
  const touching = e.touches.length;
@@ -2880,7 +2985,7 @@ function touchGamepadRender()
2880
2985
  const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2881
2986
  for (let i=4; i--;)
2882
2987
  {
2883
- const pos = rightCenter.add(vec2().setAngle(i*PI/2, touchGamepadSize/2));
2988
+ const pos = rightCenter.add(vec2().setDirection(i, touchGamepadSize/2));
2884
2989
  overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
2885
2990
  overlayContext.beginPath();
2886
2991
  overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
@@ -3039,22 +3144,19 @@ class SoundWave extends Sound
3039
3144
  this.randomness = randomness;
3040
3145
 
3041
3146
  if (!soundEnable) return;
3042
- if (!soundDecoderContext)
3043
- soundDecoderContext = new AudioContext;
3044
3147
 
3045
3148
  fetch(filename)
3046
3149
  .then(response => response.arrayBuffer())
3047
- .then(arrayBuffer => soundDecoderContext.decodeAudioData(arrayBuffer))
3150
+ .then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))
3048
3151
  .then(audioBuffer =>
3049
3152
  {
3050
3153
  this.sampleChannels = [];
3051
3154
  for (let i = audioBuffer.numberOfChannels; i--;)
3052
- this.sampleChannels[i] = audioBuffer.getChannelData(i);
3155
+ this.sampleChannels[i] = Array.from(audioBuffer.getChannelData(i));
3053
3156
  this.sampleRate = audioBuffer.sampleRate;
3054
3157
  });
3055
3158
  }
3056
3159
  }
3057
- let soundDecoderContext; // audio context used only to decode audio files
3058
3160
 
3059
3161
  /**
3060
3162
  * Music Object - Stores a zzfx music track for later use
@@ -3168,8 +3270,9 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
3168
3270
  ///////////////////////////////////////////////////////////////////////////////
3169
3271
 
3170
3272
  /** Audio context used by the engine
3273
+ * @type {AudioContext}
3171
3274
  * @memberof Audio */
3172
- let audioContext;
3275
+ let audioContext = new AudioContext;
3173
3276
 
3174
3277
  /** Play cached audio samples with given settings
3175
3278
  * @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
@@ -3184,10 +3287,6 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
3184
3287
  {
3185
3288
  if (!soundEnable) return;
3186
3289
 
3187
- // create audio context if needed
3188
- if (!audioContext)
3189
- audioContext = new AudioContext;
3190
-
3191
3290
  // prevent sounds from building up if they can't be played
3192
3291
  if (audioContext.state != 'running')
3193
3292
  {
@@ -3598,7 +3697,7 @@ class TileLayerData
3598
3697
  * @param {Number} [direction] - Integer direction of tile, in 90 degree increments
3599
3698
  * @param {Boolean} [mirror] - If the tile should be mirrored along the x axis
3600
3699
  * @param {Color} [color] - Color of the tile */
3601
- constructor(tile, direction=0, mirror=false, color=new Color())
3700
+ constructor(tile, direction=0, mirror=false, color=new Color)
3602
3701
  {
3603
3702
  /** @property {Number} - The tile to use, untextured if undefined */
3604
3703
  this.tile = tile;
@@ -3838,12 +3937,12 @@ class TileLayer extends EngineObject
3838
3937
  * @example
3839
3938
  * // create a particle emitter
3840
3939
  * let pos = vec2(2,3);
3841
- * let particleEmiter = new ParticleEmitter
3940
+ * let particleEmitter = new ParticleEmitter
3842
3941
  * (
3843
- * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
3844
- * tile(0, 16), // tileInfo
3845
- * new Color(1,1,1), new Color(0,0,0), // colorStartA, colorStartB
3846
- * new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
3942
+ * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
3943
+ * tile(0, 16), // tileInfo
3944
+ * rgb(1,1,1), rgb(0,0,0), // colorStartA, colorStartB
3945
+ * rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
3847
3946
  * 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
3848
3947
  * .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
3849
3948
  * .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
@@ -4250,8 +4349,8 @@ class Medal
4250
4349
  // draw containing rect and clip to that region
4251
4350
  context.save();
4252
4351
  context.beginPath();
4253
- context.fillStyle = rgb(.9,.9,.9).toString();
4254
- context.strokeStyle = rgb(0,0,0).toString();
4352
+ context.fillStyle = new Color(.9,.9,.9).toString();
4353
+ context.strokeStyle = new Color(0,0,0).toString();
4255
4354
  context.lineWidth = 3;
4256
4355
  context.rect(x, y, width, medalDisplaySize.y);
4257
4356
  context.fill();
@@ -4703,7 +4802,7 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
4703
4802
  ASSERT(typeof rgba == 'number' && typeof rgbaAdditive == 'number', 'invalid color');
4704
4803
 
4705
4804
  // flush if there is not enough room or if different blend mode
4706
- if (glInstanceCount >= gl_MAX_INSTANCES-1 || glBatchAdditive != glAdditive)
4805
+ if (glInstanceCount >= gl_MAX_INSTANCES || glBatchAdditive != glAdditive)
4707
4806
  glFlush();
4708
4807
 
4709
4808
  let offset = glInstanceCount * gl_INDICIES_PER_INSTANCE;
@@ -4883,7 +4982,7 @@ const engineName = 'LittleJS';
4883
4982
  * @type {String}
4884
4983
  * @default
4885
4984
  * @memberof Engine */
4886
- const engineVersion = '1.9.1';
4985
+ const engineVersion = '1.9.2';
4887
4986
 
4888
4987
  /** Frames per second to update objects
4889
4988
  * @type {Number}