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
@@ -56,11 +56,15 @@ let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParti
56
56
  ///////////////////////////////////////////////////////////////////////////////
57
57
  // Debug helper functions
58
58
 
59
- /** Asserts if the experssion is false, does not do anything in release builds
59
+ /** Asserts if the expression 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
@@ -449,7 +453,7 @@ function min(valueA, valueB) { return Math.min(valueA, valueB); }
449
453
  * @memberof Utilities */
450
454
  function max(valueA, valueB) { return Math.max(valueA, valueB); }
451
455
 
452
- /** Returns the sign of value passed in (also returns 1 if 0)
456
+ /** Returns the sign of value passed in
453
457
  * @param {Number} value
454
458
  * @return {Number}
455
459
  * @memberof Utilities */
@@ -496,7 +500,7 @@ function lerp(percent, valueA, valueB) { return valueA + clamp(percent) * (value
496
500
  function distanceWrap(valueA, valueB, wrapSize=1)
497
501
  { const d = (valueA - valueB) % wrapSize; return d*2 % wrapSize - d; }
498
502
 
499
- /** Linearly interpolates between values passed in with wrappping
503
+ /** Linearly interpolates between values passed in with wrapping
500
504
  * @param {Number} percent
501
505
  * @param {Number} valueA
502
506
  * @param {Number} valueB
@@ -513,7 +517,7 @@ function lerpWrap(percent, valueA, valueB, wrapSize=1)
513
517
  * @memberof Utilities */
514
518
  function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*PI); }
515
519
 
516
- /** Linearly interpolates between the angles passed in with wrappping
520
+ /** Linearly interpolates between the angles passed in with wrapping
517
521
  * @param {Number} percent
518
522
  * @param {Number} angleA
519
523
  * @param {Number} angleB
@@ -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
@@ -1154,7 +1259,7 @@ let tileSizeDefault = vec2(16);
1154
1259
  * @type {Number}
1155
1260
  * @default
1156
1261
  * @memberof Settings */
1157
- let tileFixBleedScale = .3;
1262
+ let tileFixBleedScale = .1;
1158
1263
 
1159
1264
  ///////////////////////////////////////////////////////////////////////////////
1160
1265
  // Object settings
@@ -1165,7 +1270,7 @@ let tileFixBleedScale = .3;
1165
1270
  * @memberof Settings */
1166
1271
  let enablePhysicsSolver = true;
1167
1272
 
1168
- /** Default object mass for collison calcuations (how heavy objects are)
1273
+ /** Default object mass for collision calcuations (how heavy objects are)
1169
1274
  * @type {Number}
1170
1275
  * @default
1171
1276
  * @memberof Settings */
@@ -1248,7 +1353,7 @@ let touchGamepadEnable = false;
1248
1353
  * @memberof Settings */
1249
1354
  let touchGamepadAnalog = true;
1250
1355
 
1251
- /** Size of virutal gamepad for touch devices in pixels
1356
+ /** Size of virtual gamepad for touch devices in pixels
1252
1357
  * @type {Number}
1253
1358
  * @default
1254
1359
  * @memberof Settings */
@@ -1535,7 +1640,7 @@ function setDebugKey(key) { debugKey = key; }
1535
1640
  * - Automatically adds self to object list
1536
1641
  * - Will be updated and rendered each frame
1537
1642
  * - Renders as a sprite from a tilesheet by default
1538
- * - Can have color and addtive color applied
1643
+ * - Can have color and additive color applied
1539
1644
  * - 2D Physics and collision system
1540
1645
  * - Sorted by renderOrder
1541
1646
  * - Objects can have children attached
@@ -1814,7 +1919,7 @@ class EngineObject
1814
1919
  }
1815
1920
 
1816
1921
  /** Destroy this object, destroy it's children, detach it's parent, and mark it for removal */
1817
- destroy()
1922
+ destroy()
1818
1923
  {
1819
1924
  if (this.destroyed)
1820
1925
  return;
@@ -2003,13 +2108,9 @@ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
2003
2108
  if (typeof pos === 'number')
2004
2109
  {
2005
2110
  const textureInfo = textureInfos[textureIndex];
2006
- if (textureInfo)
2007
- {
2008
- const cols = textureInfo.size.x / size.x |0;
2009
- pos = vec2((pos%cols)*size.x, (pos/cols|0)*size.y);
2010
- }
2011
- else
2012
- pos = vec2();
2111
+ ASSERT(textureInfo, 'Texture not loaded');
2112
+ const cols = textureInfo.size.x / size.x |0;
2113
+ pos = vec2((pos%cols)*size.x, (pos/cols|0)*size.y);
2013
2114
  }
2014
2115
 
2015
2116
  // return a tile info object
@@ -2098,6 +2199,11 @@ function worldToScreen(worldPos)
2098
2199
  );
2099
2200
  }
2100
2201
 
2202
+ /** Get the camera's visible area in world space
2203
+ * @return {Vector2}
2204
+ * @memberof Draw */
2205
+ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
2206
+
2101
2207
  /** Draw textured tile centered in world space, with color applied if using WebGL
2102
2208
  * @param {Vector2} pos - Center of the tile in world space
2103
2209
  * @param {Vector2} [size=(1,1)] - Size of the tile in world space
@@ -2732,9 +2838,9 @@ if (isTouchDevice)
2732
2838
  // handle all touch events the same way
2733
2839
  ontouchstart = ontouchmove = ontouchend = (e)=>
2734
2840
  {
2735
- // fix stalled audio on mobile
2736
- if (soundEnable)
2737
- audioContext ? audioContext.resume() : zzfx(0);
2841
+ // fix stalled audio requiring user interaction
2842
+ if (soundEnable && audioContext && audioContext.state != 'running')
2843
+ zzfx(0);
2738
2844
 
2739
2845
  // check if touching and pass to mouse events
2740
2846
  const touching = e.touches.length;
@@ -2775,7 +2881,7 @@ function createTouchGamepad()
2775
2881
  touchGamepadStick = vec2();
2776
2882
 
2777
2883
  const touchHandler = ontouchstart;
2778
- ontouchstart = ontouchmove = ontouchend = (e)=>
2884
+ ontouchstart = ontouchmove = ontouchend = (e)=>
2779
2885
  {
2780
2886
  // clear touch gamepad input
2781
2887
  touchGamepadStick = vec2();
@@ -2880,7 +2986,7 @@ function touchGamepadRender()
2880
2986
  const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2881
2987
  for (let i=4; i--;)
2882
2988
  {
2883
- const pos = rightCenter.add(vec2().setAngle(i*PI/2, touchGamepadSize/2));
2989
+ const pos = rightCenter.add(vec2().setDirection(i, touchGamepadSize/2));
2884
2990
  overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
2885
2991
  overlayContext.beginPath();
2886
2992
  overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
@@ -2905,7 +3011,7 @@ function touchGamepadRender()
2905
3011
 
2906
3012
 
2907
3013
  /**
2908
- * Sound Object - Stores a zzfx sound for later use and can be played positionally
3014
+ * Sound Object - Stores a sound for later use and can be played positionally
2909
3015
  *
2910
3016
  * <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
2911
3017
  * @example
@@ -2920,7 +3026,7 @@ class Sound
2920
3026
  /** Create a sound object and cache the zzfx samples for later use
2921
3027
  * @param {Array} zzfxSound - Array of zzfx parameters, ex. [.5,.5]
2922
3028
  * @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
2923
- * @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
3029
+ * @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
2924
3030
  */
2925
3031
  constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
2926
3032
  {
@@ -3039,22 +3145,19 @@ class SoundWave extends Sound
3039
3145
  this.randomness = randomness;
3040
3146
 
3041
3147
  if (!soundEnable) return;
3042
- if (!soundDecoderContext)
3043
- soundDecoderContext = new AudioContext;
3044
3148
 
3045
3149
  fetch(filename)
3046
3150
  .then(response => response.arrayBuffer())
3047
- .then(arrayBuffer => soundDecoderContext.decodeAudioData(arrayBuffer))
3151
+ .then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))
3048
3152
  .then(audioBuffer =>
3049
3153
  {
3050
3154
  this.sampleChannels = [];
3051
3155
  for (let i = audioBuffer.numberOfChannels; i--;)
3052
- this.sampleChannels[i] = audioBuffer.getChannelData(i);
3156
+ this.sampleChannels[i] = Array.from(audioBuffer.getChannelData(i));
3053
3157
  this.sampleRate = audioBuffer.sampleRate;
3054
3158
  });
3055
3159
  }
3056
3160
  }
3057
- let soundDecoderContext; // audio context used only to decode audio files
3058
3161
 
3059
3162
  /**
3060
3163
  * Music Object - Stores a zzfx music track for later use
@@ -3103,24 +3206,24 @@ class Music extends Sound
3103
3206
 
3104
3207
  /** Play the music
3105
3208
  * @param {Number} [volume=1] - How much to scale volume by
3106
- * @param {Boolean} [loop=1] - True if the music should loop
3209
+ * @param {Boolean} [loop] - True if the music should loop
3107
3210
  * @return {AudioBufferSourceNode} - The audio source node
3108
3211
  */
3109
- playMusic(volume, loop = false)
3212
+ playMusic(volume, loop=false)
3110
3213
  { return super.play(undefined, volume, 1, 1, loop); }
3111
3214
  }
3112
3215
 
3113
3216
  /** Play an mp3, ogg, or wav audio from a local file or url
3114
- * @param {String} url - Location of sound file to play
3217
+ * @param {String} filename - Location of sound file to play
3115
3218
  * @param {Number} [volume] - How much to scale volume by
3116
3219
  * @param {Boolean} [loop] - True if the music should loop
3117
3220
  * @return {HTMLAudioElement} - The audio element for this sound
3118
3221
  * @memberof Audio */
3119
- function playAudioFile(url, volume=1, loop=false)
3222
+ function playAudioFile(filename, volume=1, loop=false)
3120
3223
  {
3121
3224
  if (!soundEnable) return;
3122
3225
 
3123
- const audio = new Audio(url);
3226
+ const audio = new Audio(filename);
3124
3227
  audio.volume = soundVolume * volume;
3125
3228
  audio.loop = loop;
3126
3229
  audio.play();
@@ -3168,8 +3271,14 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
3168
3271
  ///////////////////////////////////////////////////////////////////////////////
3169
3272
 
3170
3273
  /** Audio context used by the engine
3274
+ * @type {AudioContext}
3275
+ * @memberof Audio */
3276
+ let audioContext = new AudioContext;
3277
+
3278
+ /** Keep track if audio was suspended when last sound was played
3279
+ * @type {Boolean}
3171
3280
  * @memberof Audio */
3172
- let audioContext;
3281
+ let audioSuspended = false;
3173
3282
 
3174
3283
  /** Play cached audio samples with given settings
3175
3284
  * @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
@@ -3184,16 +3293,16 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
3184
3293
  {
3185
3294
  if (!soundEnable) return;
3186
3295
 
3187
- // create audio context if needed
3188
- if (!audioContext)
3189
- audioContext = new AudioContext;
3190
-
3191
3296
  // prevent sounds from building up if they can't be played
3192
- if (audioContext.state != 'running')
3297
+ const audioWasSuspended = audioSuspended;
3298
+ if (audioSuspended = audioContext.state != 'running')
3193
3299
  {
3194
3300
  // fix stalled audio
3195
3301
  audioContext.resume();
3196
- return;
3302
+
3303
+ // prevent suspended sounds from building up
3304
+ if (audioWasSuspended)
3305
+ return;
3197
3306
  }
3198
3307
 
3199
3308
  // create buffer and source
@@ -3461,7 +3570,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
3461
3570
  /**
3462
3571
  * LittleJS Tile Layer System
3463
3572
  * - Caches arrays of tiles to off screen canvas for fast rendering
3464
- * - Unlimted numbers of layers, allocates canvases as needed
3573
+ * - Unlimited numbers of layers, allocates canvases as needed
3465
3574
  * - Interfaces with EngineObject for collision
3466
3575
  * - Collision layer is separate from visible layers
3467
3576
  * - It is recommended to have a visible layer that matches the collision
@@ -3513,7 +3622,7 @@ function getTileCollisionData(pos)
3513
3622
 
3514
3623
  /** Check if collision with another object should occur
3515
3624
  * @param {Vector2} pos
3516
- * @param {Vector2} [size=(1,1)]
3625
+ * @param {Vector2} [size=(0,0)]
3517
3626
  * @param {EngineObject} [object]
3518
3627
  * @return {Boolean}
3519
3628
  * @memberof TileCollision */
@@ -3598,7 +3707,7 @@ class TileLayerData
3598
3707
  * @param {Number} [direction] - Integer direction of tile, in 90 degree increments
3599
3708
  * @param {Boolean} [mirror] - If the tile should be mirrored along the x axis
3600
3709
  * @param {Color} [color] - Color of the tile */
3601
- constructor(tile, direction=0, mirror=false, color=new Color())
3710
+ constructor(tile, direction=0, mirror=false, color=new Color)
3602
3711
  {
3603
3712
  /** @property {Number} - The tile to use, untextured if undefined */
3604
3713
  this.tile = tile;
@@ -3838,12 +3947,12 @@ class TileLayer extends EngineObject
3838
3947
  * @example
3839
3948
  * // create a particle emitter
3840
3949
  * let pos = vec2(2,3);
3841
- * let particleEmiter = new ParticleEmitter
3950
+ * let particleEmitter = new ParticleEmitter
3842
3951
  * (
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
3952
+ * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
3953
+ * tile(0, 16), // tileInfo
3954
+ * rgb(1,1,1), rgb(0,0,0), // colorStartA, colorStartB
3955
+ * rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
3847
3956
  * 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
3848
3957
  * .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
3849
3958
  * .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
@@ -4030,16 +4139,17 @@ class ParticleEmitter extends EngineObject
4030
4139
 
4031
4140
  // build particle
4032
4141
  const particle = new Particle(pos, this.tileInfo, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, this.fadeRate, this.additive, this.trailScale, this.localSpace && this, this.particleDestroyCallback);
4033
- particle.velocity = vec2().setAngle(velocityAngle, speed);
4034
- particle.fadeRate = this.fadeRate;
4035
- particle.damping = this.damping;
4036
- particle.angleDamping = this.angleDamping;
4037
- particle.elasticity = this.elasticity;
4038
- particle.friction = this.friction;
4039
- particle.gravityScale = this.gravityScale;
4040
- particle.collideTiles = this.collideTiles;
4041
- particle.renderOrder = this.renderOrder;
4042
- particle.mirror = !!randInt(2);
4142
+ particle.velocity = vec2().setAngle(velocityAngle, speed);
4143
+ particle.angleVelocity = angleSpeed;
4144
+ particle.fadeRate = this.fadeRate;
4145
+ particle.damping = this.damping;
4146
+ particle.angleDamping = this.angleDamping;
4147
+ particle.elasticity = this.elasticity;
4148
+ particle.friction = this.friction;
4149
+ particle.gravityScale = this.gravityScale;
4150
+ particle.collideTiles = this.collideTiles;
4151
+ particle.renderOrder = this.renderOrder;
4152
+ particle.mirror = !!randInt(2);
4043
4153
 
4044
4154
  // call particle create callaback
4045
4155
  this.particleCreateCallback && this.particleCreateCallback(particle);
@@ -4250,8 +4360,8 @@ class Medal
4250
4360
  // draw containing rect and clip to that region
4251
4361
  context.save();
4252
4362
  context.beginPath();
4253
- context.fillStyle = rgb(.9,.9,.9).toString();
4254
- context.strokeStyle = rgb(0,0,0).toString();
4363
+ context.fillStyle = new Color(.9,.9,.9).toString();
4364
+ context.strokeStyle = new Color(0,0,0).toString();
4255
4365
  context.lineWidth = 3;
4256
4366
  context.rect(x, y, width, medalDisplaySize.y);
4257
4367
  context.fill();
@@ -4430,7 +4540,7 @@ class Newgrounds
4430
4540
  }
4431
4541
 
4432
4542
  // build the input object
4433
- const input =
4543
+ const input =
4434
4544
  {
4435
4545
  'app_id': this.app_id,
4436
4546
  'session_id': this.session_id,
@@ -4645,8 +4755,6 @@ function glCreateTexture(image)
4645
4755
  const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
4646
4756
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
4647
4757
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
4648
- glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE);
4649
- glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE);
4650
4758
 
4651
4759
  return texture;
4652
4760
  }
@@ -4703,7 +4811,7 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
4703
4811
  ASSERT(typeof rgba == 'number' && typeof rgbaAdditive == 'number', 'invalid color');
4704
4812
 
4705
4813
  // flush if there is not enough room or if different blend mode
4706
- if (glInstanceCount >= gl_MAX_INSTANCES-1 || glBatchAdditive != glAdditive)
4814
+ if (glInstanceCount >= gl_MAX_INSTANCES || glBatchAdditive != glAdditive)
4707
4815
  glFlush();
4708
4816
 
4709
4817
  let offset = glInstanceCount * gl_INDICIES_PER_INSTANCE;
@@ -4724,7 +4832,7 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
4724
4832
  ///////////////////////////////////////////////////////////////////////////////
4725
4833
  // post processing - can be enabled to pass other canvases through a final shader
4726
4834
 
4727
- let glPostShader, glPostArrayBuffer, glPostTexture, glPostIncludeOverlay;
4835
+ let glPostShader, glPostTexture, glPostIncludeOverlay;
4728
4836
 
4729
4837
  /** Set up a post processing shader
4730
4838
  * @param {String} shaderCode
@@ -4760,7 +4868,6 @@ function glInitPostProcess(shaderCode, includeOverlay=false)
4760
4868
  );
4761
4869
 
4762
4870
  // create buffer and texture
4763
- glPostArrayBuffer = glContext.createBuffer();
4764
4871
  glPostTexture = glCreateTexture(undefined);
4765
4872
  glPostIncludeOverlay = includeOverlay;
4766
4873
 
@@ -4832,10 +4939,7 @@ gl_NEAREST = 9728,
4832
4939
  gl_LINEAR = 9729,
4833
4940
  gl_TEXTURE_MAG_FILTER = 10240,
4834
4941
  gl_TEXTURE_MIN_FILTER = 10241,
4835
- gl_TEXTURE_WRAP_S = 10242,
4836
- gl_TEXTURE_WRAP_T = 10243,
4837
4942
  gl_COLOR_BUFFER_BIT = 16384,
4838
- gl_CLAMP_TO_EDGE = 33071,
4839
4943
  gl_TEXTURE0 = 33984,
4840
4944
  gl_ARRAY_BUFFER = 34962,
4841
4945
  gl_STATIC_DRAW = 35044,
@@ -4852,7 +4956,7 @@ gl_MAX_INSTANCES = 1e4,
4852
4956
  gl_INSTANCE_BYTE_STRIDE = gl_INDICIES_PER_INSTANCE * 4, // 11 * 4
4853
4957
  gl_INSTANCE_BUFFER_SIZE = gl_MAX_INSTANCES * gl_INSTANCE_BYTE_STRIDE;
4854
4958
  /**
4855
- * LittleJS - The Tiny JavaScript Game Engine That Can!
4959
+ * LittleJS - The Tiny Fast JavaScript Game Engine
4856
4960
  * MIT License - Copyright 2021 Frank Force
4857
4961
  *
4858
4962
  * Engine Features
@@ -4883,9 +4987,9 @@ const engineName = 'LittleJS';
4883
4987
  * @type {String}
4884
4988
  * @default
4885
4989
  * @memberof Engine */
4886
- const engineVersion = '1.9.1';
4990
+ const engineVersion = '1.9.3';
4887
4991
 
4888
- /** Frames per second to update objects
4992
+ /** Frames per second to update
4889
4993
  * @type {Number}
4890
4994
  * @default
4891
4995
  * @memberof Engine */
@@ -4902,7 +5006,7 @@ const timeDelta = 1/frameRate;
4902
5006
  * @memberof Engine */
4903
5007
  let engineObjects = [];
4904
5008
 
4905
- /** Array containing only objects that are set to collide with other objects this frame (for optimization)
5009
+ /** Array with only objects set to collide with other objects this frame (for optimization)
4906
5010
  * @type {Array}
4907
5011
  * @memberof Engine */
4908
5012
  let engineObjectsCollide = [];
@@ -4912,7 +5016,7 @@ let engineObjectsCollide = [];
4912
5016
  * @memberof Engine */
4913
5017
  let frame = 0;
4914
5018
 
4915
- /** Current engine time since start in seconds, derived from frame
5019
+ /** Current engine time since start in seconds
4916
5020
  * @type {Number}
4917
5021
  * @memberof Engine */
4918
5022
  let time = 0;
@@ -4938,12 +5042,12 @@ let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
4938
5042
 
4939
5043
  ///////////////////////////////////////////////////////////////////////////////
4940
5044
 
4941
- /** Start up LittleJS engine with your callback functions
4942
- * @param {Function} gameInit - Called once after the engine starts up, setup the game
4943
- * @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state
4944
- * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
4945
- * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
4946
- * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
5045
+ /** Startup LittleJS engine with your callback functions
5046
+ * @param {Function} gameInit - Called once after the engine starts up, setup the game
5047
+ * @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state
5048
+ * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
5049
+ * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
5050
+ * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
4947
5051
  * @param {Array} [imageSources=['tiles.png']] - Image to load
4948
5052
  * @memberof Engine */
4949
5053
  function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=['tiles.png'])
@@ -4966,32 +5070,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4966
5070
  frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
4967
5071
  if (!debugSpeedUp)
4968
5072
  frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp incase of slow framerate
4969
-
4970
- if (canvasFixedSize.x)
4971
- {
4972
- // clear canvas and set fixed size
4973
- mainCanvas.width = canvasFixedSize.x;
4974
- mainCanvas.height = canvasFixedSize.y;
4975
-
4976
- // fit to window by adding space on top or bottom if necessary
4977
- const aspect = innerWidth / innerHeight;
4978
- const fixedAspect = mainCanvas.width / mainCanvas.height;
4979
- (glCanvas||mainCanvas).style.width = mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
4980
- (glCanvas||mainCanvas).style.height = mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
4981
- }
4982
- else
4983
- {
4984
- // clear canvas and set size to same as window
4985
- mainCanvas.width = min(innerWidth, canvasMaxSize.x);
4986
- mainCanvas.height = min(innerHeight, canvasMaxSize.y);
4987
- }
4988
-
4989
- // clear overlay canvas and set size
4990
- overlayCanvas.width = mainCanvas.width;
4991
- overlayCanvas.height = mainCanvas.height;
4992
-
4993
- // save canvas size
4994
- mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
5073
+ updateCanvas();
4995
5074
 
4996
5075
  if (paused)
4997
5076
  {
@@ -5065,6 +5144,35 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5065
5144
  requestAnimationFrame(engineUpdate);
5066
5145
  }
5067
5146
 
5147
+ function updateCanvas()
5148
+ {
5149
+ if (canvasFixedSize.x)
5150
+ {
5151
+ // clear canvas and set fixed size
5152
+ mainCanvas.width = canvasFixedSize.x;
5153
+ mainCanvas.height = canvasFixedSize.y;
5154
+
5155
+ // fit to window by adding space on top or bottom if necessary
5156
+ const aspect = innerWidth / innerHeight;
5157
+ const fixedAspect = mainCanvas.width / mainCanvas.height;
5158
+ (glCanvas||mainCanvas).style.width = mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
5159
+ (glCanvas||mainCanvas).style.height = mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
5160
+ }
5161
+ else
5162
+ {
5163
+ // clear canvas and set size to same as window
5164
+ mainCanvas.width = min(innerWidth, canvasMaxSize.x);
5165
+ mainCanvas.height = min(innerHeight, canvasMaxSize.y);
5166
+ }
5167
+
5168
+ // clear overlay canvas and set size
5169
+ overlayCanvas.width = mainCanvas.width;
5170
+ overlayCanvas.height = mainCanvas.height;
5171
+
5172
+ // save canvas size
5173
+ mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
5174
+ }
5175
+
5068
5176
  // setup html
5069
5177
  const styleBody =
5070
5178
  'margin:0;overflow:hidden;' + // fill the window
@@ -5089,6 +5197,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5089
5197
  const styleCanvas = 'position:absolute;' + // position
5090
5198
  'top:50%;left:50%;transform:translate(-50%,-50%)'; // center
5091
5199
  (glCanvas||mainCanvas).style.cssText = mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
5200
+ updateCanvas();
5092
5201
 
5093
5202
  // create promises for loading images
5094
5203
  const promises = imageSources.map((src, textureIndex)=>
@@ -5285,9 +5394,9 @@ function drawEngineSplashScreen(t)
5285
5394
 
5286
5395
  // big stack
5287
5396
  rect(50,20,10,-10,color(0,1));
5288
- rect(50,20,6,-10,color(0,2));
5289
- rect(50,20,3,-10,color(0,3));
5290
- rect(50,10,10,10);
5397
+ rect(50,20,6.5,-10,color(0,2));
5398
+ rect(50,20,3.5,-10,color(0,3));
5399
+ rect(50,20,10,-10);
5291
5400
  circle(55,2,11.4,.5,PI-.5,color(3,3));
5292
5401
  circle(55,2,11.4,.5,PI/2,color(3,2),1);
5293
5402
  circle(55,2,11.4,.5,PI-.5);
@@ -5306,7 +5415,7 @@ function drawEngineSplashScreen(t)
5306
5415
 
5307
5416
  // engine outline
5308
5417
  circle(36,30,10,PI/2,PI*3/2);
5309
- circle(47,30,10,PI/2,PI*3/2);
5418
+ circle(48,30,10,PI/2,PI*3/2);
5310
5419
  circle(60,30,10);
5311
5420
  line(36,20,60,20);
5312
5421