littlejsengine 1.0.14 → 1.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/build.bat +5 -1
  2. package/docs/Audio.html +2268 -0
  3. package/docs/Color.html +2989 -0
  4. package/docs/Debug.html +2933 -0
  5. package/docs/Draw.html +4092 -0
  6. package/docs/EngineObject.html +4458 -0
  7. package/docs/Input.html +2482 -0
  8. package/docs/Medal.html +1007 -0
  9. package/docs/Medals.html +628 -0
  10. package/docs/Music.html +584 -0
  11. package/docs/Newgrounds.html +1399 -0
  12. package/docs/Particle.html +4535 -0
  13. package/docs/ParticleEmitter.html +7770 -0
  14. package/docs/Random.html +1774 -0
  15. package/docs/Settings.html +2679 -0
  16. package/docs/Sound.html +1249 -0
  17. package/docs/TileCollision.html +1409 -0
  18. package/docs/TileLayer.html +6948 -0
  19. package/docs/TileLayerData.html +1052 -0
  20. package/docs/Timer.html +1183 -0
  21. package/docs/Utilities.html +3281 -0
  22. package/docs/Vector2.html +4207 -0
  23. package/docs/WebGL.html +2327 -0
  24. package/docs/engine.js.html +429 -0
  25. package/docs/engineAudio.js.html +609 -0
  26. package/docs/engineDebug.js.html +725 -0
  27. package/docs/engineDraw.js.html +419 -0
  28. package/docs/engineInput.js.html +401 -0
  29. package/docs/engineMedals.js.html +461 -0
  30. package/docs/engineObject.js.html +518 -0
  31. package/docs/engineParticles.js.html +441 -0
  32. package/docs/engineSettings.js.html +349 -0
  33. package/docs/engineTileLayer.js.html +513 -0
  34. package/docs/engineUtilities.js.html +647 -0
  35. package/docs/engineWebGL.js.html +522 -0
  36. package/docs/fonts/MavenPro-Regular.ttf +0 -0
  37. package/docs/fonts/Montserrat-Regular.ttf +0 -0
  38. package/docs/fonts/Muli-Black.ttf +0 -0
  39. package/docs/fonts/OFL-hind.txt +93 -0
  40. package/docs/fonts/OFL-montserrat.txt +93 -0
  41. package/docs/global.html +1691 -0
  42. package/docs/index.css +6 -0
  43. package/docs/index.html +247 -0
  44. package/docs/scripts/fix-code-block.js +53 -0
  45. package/docs/scripts/fix-navbar.js +25 -0
  46. package/docs/scripts/linenumber.js +25 -0
  47. package/docs/scripts/misc.js +217 -0
  48. package/docs/scripts/resize.js +85 -0
  49. package/docs/scripts/search.js +83 -0
  50. package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
  51. package/docs/scripts/third-party/fuse.js +9 -0
  52. package/docs/scripts/third-party/lang-css.js +2 -0
  53. package/docs/scripts/third-party/prettify.js +28 -0
  54. package/docs/static/favicon.png +0 -0
  55. package/docs/static/index.css +4 -0
  56. package/docs/styles/clean-jsdoc-theme-base.css +393 -0
  57. package/docs/styles/clean-jsdoc-theme-dark.css +324 -0
  58. package/docs/styles/clean-jsdoc-theme-light.css +319 -0
  59. package/docs/styles/reset.css +346 -0
  60. package/docs/styles/third-party/ionicons.min.css +11 -0
  61. package/docs/styles/third-party/prettify-jsdoc.css +111 -0
  62. package/docs/styles/third-party/prettify-tomorrow.css +5 -0
  63. package/engine/engine.all.js +1997 -744
  64. package/engine/engine.all.min.js +1 -1
  65. package/engine/engine.all.release.js +1743 -569
  66. package/engine/engine.js +107 -39
  67. package/engine/engineAudio.js +152 -34
  68. package/engine/{build.bat → engineBuild.bat} +10 -10
  69. package/engine/{buildSetup.bat → engineBuildSetup.bat} +0 -0
  70. package/engine/engineDebug.js +105 -32
  71. package/engine/engineDraw.js +137 -32
  72. package/engine/engineInput.js +111 -37
  73. package/engine/{engineMedal.js → engineMedals.js} +129 -56
  74. package/engine/engineObject.js +117 -67
  75. package/engine/engineParticles.js +282 -0
  76. package/engine/engineRelease.js +3 -9
  77. package/engine/engineSettings.js +190 -0
  78. package/engine/engineTileLayer.js +147 -44
  79. package/engine/engineUtilities.js +488 -0
  80. package/engine/engineWebGL.js +113 -74
  81. package/examples/breakout/game.js +1 -1
  82. package/examples/breakout/gameObjects.js +3 -3
  83. package/examples/breakout/index.html +3 -3
  84. package/examples/platformer/game.js +5 -7
  85. package/examples/platformer/gameEffects.js +7 -7
  86. package/examples/platformer/gameLevel.js +2 -2
  87. package/examples/platformer/gameObjects.js +4 -5
  88. package/examples/platformer/gamePlayer.js +3 -3
  89. package/examples/platformer/index.html +6 -6
  90. package/examples/puzzle/game.js +1 -2
  91. package/examples/puzzle/index.html +2 -2
  92. package/examples/stress/index.html +2 -8
  93. package/game.js +5 -11
  94. package/index.html +13 -13
  95. package/package.json +4 -4
  96. package/tiles.png +0 -0
  97. package/engine/engineConfig.js +0 -64
  98. package/engine/engineParticle.js +0 -199
  99. package/engine/engineUtil.js +0 -147
@@ -1,69 +1,137 @@
1
- /*
2
- LittleJS - The Tiny JavaScript Game Engine That Can
3
- MIT License - Copyright 2019 Frank Force
4
- */
5
- /*
6
- LittleJS Debug System
7
-
8
- Debug Features
9
- - debug overlay with mouse pick
10
- - debug primitive rendering
11
- - save screenshots
12
- */
1
+ /**
2
+ * LittleJS Debug System
3
+ * <br> - Debug overlay with mouse pick
4
+ * <br> - Debug primitive rendering
5
+ * <br> - Save screenshots to disk
6
+ * @namespace Debug
7
+ */
13
8
 
14
9
  'use strict';
15
10
 
11
+ /** True if debug is enabled
12
+ * @default
13
+ * @memberof Debug */
16
14
  const debug = 1;
15
+
16
+ /** True if asserts are enaled
17
+ * @default
18
+ * @memberof Debug */
17
19
  const enableAsserts = 1;
20
+
21
+ /** Size to render debug points by default
22
+ * @default
23
+ * @memberof Debug */
18
24
  const debugPointSize = .5;
19
25
 
26
+ /** True if watermark with FPS should be down
27
+ * @default
28
+ * @memberof Debug */
20
29
  let showWatermark = 1;
30
+
31
+ /** True if god mode is enabled, handle this however you want
32
+ * @default
33
+ * @memberof Debug */
21
34
  let godMode = 0;
22
- let debugPrimitives = [];
23
- let debugOverlay = 0;
24
- let debugPhysics = 0;
25
- let debugRaycast = 0;
26
- let debugParticles = 0;
27
- let debugGamepads = 0;
28
- let debugMedals = 0;
29
- let debugCanvas = -1;
30
- let debugTakeScreenshot;
31
- let downloadLink;
32
-
33
- // debug helper functions
35
+
36
+ // Engine internal variables not exposed to documentation
37
+ let debugPrimitives = [], debugOverlay = 0, debugPhysics = 0, debugRaycast = 0,
38
+ debugParticles = 0, debugGamepads = 0, debugMedals = 0, debugTakeScreenshot, downloadLink;
39
+
40
+ ///////////////////////////////////////////////////////////////////////////////
41
+ // Debug helper functions
42
+
43
+ /** Asserts if the experssion is false, does not do anything in release builds
44
+ * @param {Boolean} assertion
45
+ * @param {Object} output
46
+ * @memberof Debug */
34
47
  const ASSERT = enableAsserts ? (...assert)=> console.assert(...assert) : ()=>{};
35
- const debugRect = (pos, size=vec2(0), color='#fff', time=0, angle=0, fill=0)=>
48
+
49
+ /** Draw a debug rectangle in world space
50
+ * @param {Vector2} pos
51
+ * @param {Vector2} [size=new Vector2()]
52
+ * @param {String} [color='#fff']
53
+ * @param {Number} [time=0]
54
+ * @param {Number} [angle=0]
55
+ * @param {Boolean} [fill=0]
56
+ * @memberof Debug */
57
+ const debugRect = (pos, size=vec2(), color='#fff', time=0, angle=0, fill=0)=>
36
58
  {
37
59
  ASSERT(typeof color == 'string'); // pass in regular html strings as colors
38
60
  debugPrimitives.push({pos, size:vec2(size), color, time:new Timer(time), angle, fill});
39
61
  }
62
+
63
+ /** Draw a debug circle in world space
64
+ * @param {Vector2} pos
65
+ * @param {Number} [radius=0]
66
+ * @param {String} [color='#fff']
67
+ * @param {Number} [time=0]
68
+ * @param {Boolean} [fill=0]
69
+ * @memberof Debug */
40
70
  const debugCircle = (pos, radius=0, color='#fff', time=0, fill=0)=>
41
71
  {
42
72
  ASSERT(typeof color == 'string'); // pass in regular html strings as colors
43
73
  debugPrimitives.push({pos, size:radius, color, time:new Timer(time), angle:0, fill});
44
74
  }
75
+
76
+ /** Draw a debug point in world space
77
+ * @param {Vector2} pos
78
+ * @param {String} [color='#fff']
79
+ * @param {Number} [time=0]
80
+ * @param {Number} [angle=0]
81
+ * @memberof Debug */
45
82
  const debugPoint = (pos, color, time, angle)=> debugRect(pos, 0, color, time, angle);
83
+
84
+ /** Draw a debug line in world space
85
+ * @param {Vector2} posA
86
+ * @param {Vector2} posB
87
+ * @param {String} [color='#fff']
88
+ * @param {Number} [thickness=.1]
89
+ * @param {Number} [time=0]
90
+ * @memberof Debug */
46
91
  const debugLine = (posA, posB, color, thickness=.1, time)=>
47
92
  {
48
93
  const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
49
94
  const size = vec2(thickness, halfDelta.length()*2);
50
95
  debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), 1);
51
96
  }
52
- const debugAABB = (pA, pB, sA, sB, color)=>
97
+
98
+ /** Draw a debug axis aligned bounding box in world space
99
+ * @param {Vector2} posA
100
+ * @param {Vector2} sizeA
101
+ * @param {Vector2} posB
102
+ * @param {Vector2} sizeB
103
+ * @param {String} [color='#fff']
104
+ * @memberof Debug */
105
+ const debugAABB = (pA, sA, pB, sB, color)=>
53
106
  {
54
107
  const minPos = vec2(min(pA.x - sA.x/2, pB.x - sB.x/2), min(pA.y - sA.y/2, pB.y - sB.y/2));
55
108
  const maxPos = vec2(max(pA.x + sA.x/2, pB.x + sB.x/2), max(pA.y + sA.y/2, pB.y + sB.y/2));
56
109
  debugRect(minPos.lerp(maxPos,.5), maxPos.subtract(minPos), color);
57
110
  }
58
- const debugText = (text='', pos, size=1, color='#fff', time=0, angle=0, font='monospace')=>
111
+
112
+ /** Draw a debug axis aligned bounding box in world space
113
+ * @param {String} text
114
+ * @param {Vector2} pos
115
+ * @param {Number} [size=1]
116
+ * @param {String} [color='#fff']
117
+ * @param {Number} [time=0]
118
+ * @param {Number} [angle=0]
119
+ * @param {String} [font='monospace']
120
+ * @memberof Debug */
121
+ const debugText = (text, pos, size=1, color='#fff', time=0, angle=0, font='monospace')=>
59
122
  {
60
123
  ASSERT(typeof color == 'string'); // pass in regular html strings as colors
61
124
  debugPrimitives.push({text, pos, size, color, time:new Timer(time), angle, font});
62
125
  }
63
126
 
127
+ /** Clear all debug primitives in the list
128
+ * @memberof Debug */
64
129
  const debugClear = ()=> debugPrimitives = [];
65
130
 
66
- // save a canvas to disk
131
+ /** Save a canvas to disk
132
+ * @param {HTMLCanvasElement} canvas
133
+ * @param {String} [filename]
134
+ * @memberof Debug */
67
135
  const debugSaveCanvas = (canvas, filename = engineName + '.png') =>
68
136
  {
69
137
  downloadLink.download = 'screenshot.png';
@@ -72,7 +140,7 @@ const debugSaveCanvas = (canvas, filename = engineName + '.png') =>
72
140
  }
73
141
 
74
142
  ///////////////////////////////////////////////////////////////////////////////
75
- // engine debug function (called automatically)
143
+ // Engine debug function (called automatically)
76
144
 
77
145
  const debugInit = ()=>
78
146
  {
@@ -112,6 +180,11 @@ const debugRender = ()=>
112
180
 
113
181
  if (debugTakeScreenshot)
114
182
  {
183
+ // composite canvas
184
+ glCopyToContext(mainContext, 1);
185
+ mainContext.drawImage(overlayCanvas, 0, 0);
186
+ overlayCanvas.width |= 0;
187
+
115
188
  debugSaveCanvas(mainCanvas);
116
189
  debugTakeScreenshot = 0;
117
190
  }
@@ -189,8 +262,8 @@ const debugRender = ()=>
189
262
  const saveContext = mainContext;
190
263
  mainContext = overlayContext
191
264
  const raycastHitPos = tileCollisionRaycast(bestObject.pos, mousePos);
192
- raycastHitPos && drawRect(raycastHitPos.int().add(vec2(.5)), vec2(1), new Color(0,1,1,.3));
193
- drawRect(mousePos.int().add(vec2(.5)), vec2(1), new Color(0,0,1,.5));
265
+ raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), new Color(0,1,1,.3));
266
+ drawRect(mousePos.floor().add(vec2(.5)), vec2(1), new Color(0,0,1,.5));
194
267
  drawLine(mousePos, bestObject.pos, .1, !raycastHitPos ? new Color(0,1,0,.5) : new Color(1,0,0,.5));
195
268
 
196
269
  let pos = mousePos.copy(), height = vec2(0,.5);
@@ -304,7 +377,7 @@ const debugRender = ()=>
304
377
  }
305
378
 
306
379
  ///////////////////////////////////////////////////////////////////////////////
307
- // particle system editor
380
+ // particle system editor (work in progress)
308
381
  let debugParticleEditor = 0, debugParticleSystem, debugParticleSystemDiv, particleSystemCode;
309
382
 
310
383
  const debugToggleParticleEditor = ()=>
@@ -491,104 +564,422 @@ const debugParticleSettings =
491
564
  ['randomColorComponents'],
492
565
  ['renderOrder'],
493
566
  ];
494
-
495
- /*
496
- LittleJS Utility Classes and Functions
497
- - Vector2 - fast, simple, easy vector class
498
- - Color - holds a rgba color with math functions
499
- - Timer - tracks time automatically
500
- - Small math lib
501
- */
567
+ /**
568
+ * LittleJS Utility Classes and Functions
569
+ * <br> - General purpose math library
570
+ * <br> - Vector2 - fast, simple, easy 2D vector class
571
+ * <br> - Color - holds a rgba color with some math functions
572
+ * <br> - Timer - tracks time automatically
573
+ * @namespace Utilities
574
+ */
502
575
 
503
576
  'use strict';
504
577
 
578
+ /** A shortcut to get Math.PI
579
+ * @const
580
+ * @memberof Utilities */
581
+ const PI = Math.PI;
582
+
583
+ /** True if running a Chromium based browser
584
+ * @const
585
+ * @memberof Utilities */
586
+ const isChrome = window['chrome'];
587
+
588
+ /** Returns absoulte value of value passed in
589
+ * @param {Number} value
590
+ * @return {Number}
591
+ * @memberof Utilities */
592
+ const abs = (a)=> a < 0 ? -a : a;
593
+
594
+ /** Returns the sign of value passed in
595
+ * @param {Number} value
596
+ * @return {Number}
597
+ * @memberof Utilities */
598
+ const sign = (a)=> a < 0 ? -1 : 1;
599
+
600
+ /** Returns lowest of two values passed in
601
+ * @param {Number} valueA
602
+ * @param {Number} valueB
603
+ * @return {Number}
604
+ * @memberof Utilities */
605
+ const min = (a, b)=> a < b ? a : b;
606
+
607
+ /** Returns highest of two values passed in
608
+ * @param {Number} valueA
609
+ * @param {Number} valueB
610
+ * @return {Number}
611
+ * @memberof Utilities */
612
+ const max = (a, b)=> a > b ? a : b;
613
+
614
+ /** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
615
+ * @param {Number} dividend
616
+ * @param {Number} divisor
617
+ * @return {Number}
618
+ * @memberof Utilities */
619
+ const mod = (a, b)=> ((a % b) + b) % b;
620
+
621
+ /** Clamps the value beween max and min
622
+ * @param {Number} value
623
+ * @param {Number} [max=1]
624
+ * @param {Number} [min=0]
625
+ * @return {Number}
626
+ * @memberof Utilities */
627
+ const clamp = (v, max=1, min=0)=> (ASSERT(max > min), v < min ? min : v > max ? max : v);
628
+
629
+ /** Returns what percentage the value is between max and min
630
+ * @param {Number} value
631
+ * @param {Number} [max=1]
632
+ * @param {Number} [min=0]
633
+ * @return {Number}
634
+ * @memberof Utilities */
635
+ const percent = (v, max=1, min=0)=> max-min ? clamp((v-min) / (max-min)) : 0;
636
+
637
+ /** Linearly interpolates the percent value between max and min
638
+ * @param {Number} percent
639
+ * @param {Number} [max=1]
640
+ * @param {Number} [min=0]
641
+ * @return {Number}
642
+ * @memberof Utilities */
643
+ const lerp = (p, max=1, min=0)=> min + clamp(p) * (max-min);
644
+
645
+ /** Formats seconds to mm:ss style for display purposes
646
+ * @param {Number} t - time in seconds
647
+ * @return {String}
648
+ * @memberof Utilities */
649
+ const formatTime = (t)=> (t/60|0)+':'+(t%60<10?'0':'')+(t%60|0);
650
+
651
+ /** Returns the nearest power of two not less then the value
652
+ * @param {Number} value
653
+ * @return {Number}
654
+ * @memberof Utilities */
655
+ const nearestPowerOfTwo = (v)=> 2**Math.ceil(Math.log2(v));
656
+
657
+ /** Applies smoothstep function to the percentage value
658
+ * @param {Number} value
659
+ * @return {Number}
660
+ * @memberof Utilities */
661
+ const smoothStep = (p)=> p * p * (3 - 2 * p);
662
+
663
+ /** Returns true if two axis aligned bounding boxes are overlapping
664
+ * @param {Vector2} pointA - Center of box A
665
+ * @param {Vector2} sizeA - Size of box A
666
+ * @param {Vector2} pointB - Center of box B
667
+ * @param {Vector2} sizeB - Size of box B
668
+ * @return {Boolean} - True if overlapping
669
+ * @memberof Utilities */
670
+ const isOverlapping = (pA, sA, pB, sB)=> abs(pA.x - pB.x)*2 < sA.x + sB.x & abs(pA.y - pB.y)*2 < sA.y + sB.y;
671
+
672
+ /** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
673
+ * @param {Number} [frequency=1] - Frequency of the wave in Hz
674
+ * @param {Number} [amplitude=1] - Amplitude (max height) of the wave
675
+ * @param {Number} [t=time] - Value to use for time of the wave
676
+ * @return {Number} - Value waving between 0 and amplitude
677
+ * @memberof Utilities */
678
+ const wave = (frequency=1, amplitude=1, t=time)=> amplitude/2 * (1 - Math.cos(t*frequency*2*PI));
679
+
505
680
  ///////////////////////////////////////////////////////////////////////////////
506
- // helper functions
507
-
508
- const PI = Math.PI;
509
- const isChrome = window['chrome'];
510
- const abs = (a)=> a < 0 ? -a : a;
511
- const sign = (a)=> a < 0 ? -1 : 1;
512
- const min = (a, b)=> a < b ? a : b;
513
- const max = (a, b)=> a > b ? a : b;
514
- const mod = (a, b)=> ((a % b) + b) % b;
515
- const clamp = (v, max=1, min=0)=> (ASSERT(max > min), v < min ? min : v > max ? max : v);
516
- const percent = (v, max=1, min=0)=> max-min ? clamp((v-min) / (max-min)) : 0;
517
- const lerp = (p, max=1, min=0)=> min + clamp(p) * (max-min);
518
- const formatTime = (t)=> (t/60|0)+':'+(t%60<10?'0':'')+(t%60|0);
519
- const isOverlapping = (pA, sA, pB, sB)=> abs(pA.x - pB.x)*2 < sA.x + sB.x & abs(pA.y - pB.y)*2 < sA.y + sB.y;
520
- const nearestPowerOfTwo = (v)=> 2**Math.ceil(Math.log2(v));
521
- const wave = (f=1,a=1,t=time)=> a/2 * (1 - Math.cos(t*f*2*PI));
522
- const smoothStep = (p)=> p * p * (3 - 2 * p);
523
-
524
- // random functions
525
- const rand = (a=1, b=0)=> b + (a-b)*Math.random();
526
- const randInt = (a=1, b=0)=> rand(a,b)|0;
527
- const randSign = ()=> (rand(2)|0)*2-1;
681
+
682
+ /** Random global functions
683
+ * @namespace Random */
684
+
685
+ /** Returns a random value between the two values passed in
686
+ * @param {Number} [valueA=1]
687
+ * @param {Number} [valueB=0]
688
+ * @return {Number}
689
+ * @memberof Random */
690
+ const rand = (a=1, b=0)=> b + (a-b)*Math.random();
691
+
692
+ /** Returns a floored random value the two values passed in
693
+ * @param {Number} [valueA=1]
694
+ * @param {Number} [valueB=0]
695
+ * @return {Number}
696
+ * @memberof Random */
697
+ const randInt = (a=1, b=0)=> rand(a,b)|0;
698
+
699
+ /** Randomly returns either -1 or 1
700
+ * @return {Number}
701
+ * @memberof Random */
702
+ const randSign = ()=> (rand(2)|0)*2-1;
703
+
704
+ /** Returns a random Vector2 within a circular shape
705
+ * @param {Number} [radius=1]
706
+ * @param {Number} [minRadius=0]
707
+ * @return {Vector2}
708
+ * @memberof Random */
528
709
  const randInCircle = (radius=1, minRadius=0)=> radius > 0 ? randVector(radius * rand(minRadius / radius, 1)**.5) : new Vector2;
529
- const randVector = (length=1)=> new Vector2().setAngle(rand(2*PI), length);
530
- const randColor = (cA = new Color, cB = new Color(0,0,0,1), linear)=>
531
- linear ? cA.lerp(cB, rand()) : new Color(rand(cA.r,cB.r),rand(cA.g,cB.g),rand(cA.b,cB.b),rand(cA.a,cB.a));
532
710
 
533
- // seeded random numbers using xorshift
534
- let randSeed = 1;
711
+ /** Returns a random Vector2 with the passed in length
712
+ * @param {Number} [length=1]
713
+ * @return {Vector2}
714
+ * @memberof Random */
715
+ const randVector = (length=1)=> new Vector2().setAngle(rand(2*PI), length);
716
+
717
+ /** Returns a random color between the two passed in colors, combine components if linear
718
+ * @param {Color} [colorA=new Color(1,1,1,1)]
719
+ * @param {Color} [colorB=new Color(0,0,0,1)]
720
+ * @param {Boolean} [linear]
721
+ * @return {Color}
722
+ * @memberof Random */
723
+ const randColor = (cA = new Color, cB = new Color(0,0,0,1), linear)=>
724
+ linear ? cA.lerp(cB, rand()) : new Color(rand(cA.r,cB.r),rand(cA.g,cB.g),rand(cA.b,cB.b),rand(cA.a,cB.a));
725
+
726
+ /** The seed used by the randSeeded function, should not be 0
727
+ * @memberof Random */
728
+ let randSeed = 1;
729
+
730
+ /** Returns a seeded random value between the two values passed in using randSeed
731
+ * @param {Number} [valueA=1]
732
+ * @param {Number} [valueB=0]
733
+ * @return {Number}
734
+ * @memberof Random */
535
735
  const randSeeded = (a=1, b=0)=>
536
736
  {
537
- randSeed ^= randSeed << 13; randSeed ^= randSeed >>> 17; randSeed ^= randSeed << 5;
737
+ randSeed ^= randSeed << 13; randSeed ^= randSeed >>> 17; randSeed ^= randSeed << 5; // xorshift
538
738
  return b + (a-b)*abs(randSeed % 1e9)/1e9;
539
739
  }
540
740
 
541
- // create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
542
- const vec2 = (x=0, y)=> x.x == undefined? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y);
543
-
544
741
  ///////////////////////////////////////////////////////////////////////////////
742
+
743
+ /**
744
+ * Create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
745
+ * @param {Number} [x=0]
746
+ * @param {Number} [y=0]
747
+ * @return {Vector2}
748
+ * @example
749
+ * let a = vec2(0, 1); // vector with coordinates (0, 1)
750
+ * let b = vec2(a); // copy a into b
751
+ * a = vec2(5); // set a to (5, 5)
752
+ * b = vec2(); // set b to (0, 0)
753
+ * @memberof Utilities
754
+ */
755
+ const vec2 = (x=0, y)=> x.x == undefined ? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y);
756
+
757
+ /**
758
+ * 2D Vector object with vector math library
759
+ * @example
760
+ * let a = new Vector2(2, 3); // vector with coordinates (2, 3)
761
+ * let b = new Vector2; // vector with coordinates (0, 0)
762
+ * let c = vec2(4, 2); // use the vec2 function to make a Vector2
763
+ */
545
764
  class Vector2
546
765
  {
547
- constructor(x=0, y=0) { this.x = x; this.y = y; }
548
-
549
- // basic math operators, a vector or scaler can be passed in
550
- copy() { return new Vector2(this.x, this.y); }
551
- scale(s) { ASSERT(s.x==undefined); return new Vector2(this.x * s, this.y * s); }
552
- add(v) { ASSERT(v.x!=undefined); return new Vector2(this.x + v.x, this.y + v.y); }
553
- subtract(v) { ASSERT(v.x!=undefined); return new Vector2(this.x - v.x, this.y - v.y); }
554
- multiply(v) { ASSERT(v.x!=undefined); return new Vector2(this.x * v.x, this.y * v.y); }
555
- divide(v) { ASSERT(v.x!=undefined); return new Vector2(this.x / v.x, this.y / v.y); }
556
-
557
- // vector math operators
558
- length() { return this.lengthSquared()**.5; }
559
- lengthSquared() { return this.x**2 + this.y**2; }
560
- distance(p) { return this.distanceSquared(p)**.5; }
561
- distanceSquared(p) { return (this.x - p.x)**2 + (this.y - p.y)**2; }
562
- normalize(length=1) { const l = this.length(); return l ? this.scale(length/l) : new Vector2(length); }
766
+ /** Create a 2D vector with the x and y passed in, can also be created with vec2()
767
+ * @param {Number} [x=0] - X axis location
768
+ * @param {Number} [y=0] - Y axis location */
769
+ constructor(x=0, y=0)
770
+ {
771
+ /** @property {Number} - X axis location */
772
+ this.x = x;
773
+ /** @property {Number} - Y axis location */
774
+ this.y = y;
775
+ }
776
+
777
+ /** Returns a new vector that is a copy of this
778
+ * @return {Vector2} */
779
+ copy() { return new Vector2(this.x, this.y); }
780
+
781
+ /** Returns a copy of this vector plus the vector passed in
782
+ * @param {Vector2} vector
783
+ * @return {Vector2} */
784
+ add(v) { ASSERT(v.x!=undefined); return new Vector2(this.x + v.x, this.y + v.y); }
785
+
786
+ /** Returns a copy of this vector minus the vector passed in
787
+ * @param {Vector2} vector
788
+ * @return {Vector2} */
789
+ subtract(v) { ASSERT(v.x!=undefined); return new Vector2(this.x - v.x, this.y - v.y); }
790
+
791
+ /** Returns a copy of this vector times the vector passed in
792
+ * @param {Vector2} vector
793
+ * @return {Vector2} */
794
+ multiply(v) { ASSERT(v.x!=undefined); return new Vector2(this.x * v.x, this.y * v.y); }
795
+
796
+ /** Returns a copy of this vector divided by the vector passed in
797
+ * @param {Vector2} vector
798
+ * @return {Vector2} */
799
+ divide(v) { ASSERT(v.x!=undefined); return new Vector2(this.x / v.x, this.y / v.y); }
800
+
801
+ /** Returns a copy of this vector scaled by the vector passed in
802
+ * @param {Number} scale
803
+ * @return {Vector2} */
804
+ scale(s) { ASSERT(s.x==undefined); return new Vector2(this.x * s, this.y * s); }
805
+
806
+ /** Returns the length of this vector
807
+ * @return {Number} */
808
+ length() { return this.lengthSquared()**.5; }
809
+
810
+ /** Returns the length of this vector squared
811
+ * @return {Number} */
812
+ lengthSquared() { return this.x**2 + this.y**2; }
813
+
814
+ /** Returns the distance from this vector to vector passed in
815
+ * @param {Vector2} vector
816
+ * @return {Number} */
817
+ distance(v) { return this.distanceSquared(v)**.5; }
818
+
819
+ /** Returns the distance squared from this vector to vector passed in
820
+ * @param {Vector2} vector
821
+ * @return {Number} */
822
+ distanceSquared(v) { return (this.x - v.x)**2 + (this.y - v.y)**2; }
823
+
824
+ /** Returns a new vector in same direction as this one with the length passed in
825
+ * @param {Number} [length=1]
826
+ * @return {Vector2} */
827
+ normalize(length=1) { const l = this.length(); return l ? this.scale(length/l) : new Vector2(length); }
828
+
829
+ /** Returns a new vector clamped to length passed in
830
+ * @param {Number} [length=1]
831
+ * @return {Vector2} */
563
832
  clampLength(length=1) { const l = this.length(); return l > length ? this.scale(length/l) : this; }
564
- dot(v) { ASSERT(v.x!=undefined); return this.x*v.x + this.y*v.y; }
565
- cross(v) { ASSERT(v.x!=undefined); return this.x*v.y - this.y*v.x; }
566
- angle() { return Math.atan2(this.x, this.y); }
567
- setAngle(a, length=1) { this.x = length*Math.sin(a); this.y = length*Math.cos(a); return this; }
568
- rotate(a) { const c = Math.cos(a), s = Math.sin(a); return new Vector2(this.x*c-this.y*s, this.x*s+this.y*c); }
569
- direction() { return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
570
- flip() { return new Vector2(this.y, this.x); }
571
- invert() { return new Vector2(this.y, -this.x); }
572
- round() { return new Vector2(Math.round(this.x), Math.round(this.y)); }
573
- floor() { return new Vector2(Math.floor(this.x), Math.floor(this.y)); }
574
- int() { return new Vector2(this.x|0, this.y|0); }
575
- lerp(v, p) { ASSERT(v.x!=undefined); return this.add(v.subtract(this).scale(clamp(p))); }
576
- area() { return this.x * this.y; }
833
+
834
+ /** Returns the dot product of this and the vector passed in
835
+ * @param {Vector2} vector
836
+ * @return {Number} */
837
+ dot(v) { ASSERT(v.x!=undefined); return this.x*v.x + this.y*v.y; }
838
+
839
+ /** Returns the cross product of this and the vector passed in
840
+ * @param {Vector2} vector
841
+ * @return {Number} */
842
+ cross(v) { ASSERT(v.x!=undefined); return this.x*v.y - this.y*v.x; }
843
+
844
+ /** Returns the angle of this vector, up is angle 0
845
+ * @return {Number} */
846
+ angle() { return Math.atan2(this.x, this.y); }
847
+
848
+ /** Sets this vector with angle and length passed in
849
+ * @param {Number} [angle=0]
850
+ * @param {Number} [length=1] */
851
+ setAngle(a=0, length=1) { this.x = length*Math.sin(a); this.y = length*Math.cos(a); return this; }
852
+
853
+ /** Returns copy of this vector rotated by the angle passed in
854
+ * @param {Number} angle
855
+ * @return {Vector2} */
856
+ rotate(a) { const c = Math.cos(a), s = Math.sin(a); return new Vector2(this.x*c-this.y*s, this.x*s+this.y*c); }
857
+
858
+ /** Returns the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
859
+ * @return {Number} */
860
+ direction() { return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
861
+
862
+ /** Returns a copy of this vector that has been inverted
863
+ * @return {Vector2} */
864
+ invert() { return new Vector2(this.y, -this.x); }
865
+
866
+ /** Returns a copy of this vector with the axies flipped
867
+ * @return {Vector2} */
868
+ flip() { return new Vector2(this.y, this.x); }
869
+
870
+ /** Returns a copy of this vector with each axis floored
871
+ * @return {Vector2} */
872
+ floor() { return new Vector2(Math.floor(this.x), Math.floor(this.y)); }
873
+
874
+ /** Returns the area this vector covers as a rectangle
875
+ * @return {Number} */
876
+ area() { return this.x * this.y; }
877
+
878
+ /** Returns a new vector that is p percent between this and the vector passed in
879
+ * @param {Vector2} vector
880
+ * @param {Number} percent
881
+ * @return {Vector2} */
882
+ lerp(v, p) { ASSERT(v.x!=undefined); return this.add(v.subtract(this).scale(clamp(p))); }
883
+
884
+ /** Returns true if this vector is within the bounds of an array size passed in
885
+ * @param {Vector2} arraySize
886
+ * @return {Boolean} */
577
887
  arrayCheck(arraySize) { return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y; }
578
888
  }
579
889
 
580
890
  ///////////////////////////////////////////////////////////////////////////////
891
+
892
+ /**
893
+ * Color object (red, green, blue, alpha) with some helpful functions
894
+ * @example
895
+ * let a = new Color; // white
896
+ * let b = new Color(1, 0, 0); // red
897
+ * let c = new Color(0, 0, 0, 0); // transparent black
898
+ */
581
899
  class Color
582
900
  {
583
- constructor(r=1, g=1, b=1, a=1) { this.r=r; this.g=g; this.b=b; this.a=a; }
901
+ /** Create a color with the components passed in, white by default
902
+ * @param {Number} [red=1]
903
+ * @param {Number} [green=1]
904
+ * @param {Number} [blue=1]
905
+ * @param {Number} [alpha=1] */
906
+ constructor(r=1, g=1, b=1, a=1)
907
+ {
908
+ /** @property {Number} - Red */
909
+ this.r = r;
910
+ /** @property {Number} - Green */
911
+ this.g = g;
912
+ /** @property {Number} - Blue */
913
+ this.b = b;
914
+ /** @property {Number} - Alpha */
915
+ this.a = a;
916
+ }
917
+
918
+ /** Returns a new color that is a copy of this
919
+ * @return {Color} */
920
+ copy() { return new Color(this.r, this.g, this.b, this.a); }
584
921
 
585
- copy(c) { return new Color(this.r, this.g, this.b, this.a); }
586
- add(c) { return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a); }
922
+ /** Returns a copy of this color plus the color passed in
923
+ * @param {Color} color
924
+ * @return {Color} */
925
+ add(c) { return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a); }
926
+
927
+ /** Returns a copy of this color minus the color passed in
928
+ * @param {Color} color
929
+ * @return {Color} */
587
930
  subtract(c) { return new Color(this.r-c.r, this.g-c.g, this.b-c.b, this.a-c.a); }
931
+
932
+ /** Returns a copy of this color times the color passed in
933
+ * @param {Color} color
934
+ * @return {Color} */
588
935
  multiply(c) { return new Color(this.r*c.r, this.g*c.g, this.b*c.b, this.a*c.a); }
589
- scale(s,a=s){ return new Color(this.r*s, this.g*s, this.b*s, this.a*a); }
590
- clamp() { return new Color(clamp(this.r), clamp(this.g), clamp(this.b), clamp(this.a)); }
591
- lerp(c, p) { return this.add(c.subtract(this).scale(clamp(p))); }
936
+
937
+ /** Returns a copy of this color divided by the color passed in
938
+ * @param {Color} color
939
+ * @return {Color} */
940
+ divide(c) { return new Color(this.r/c.r, this.g/c.g, this.b/c.b, this.a/c.a); }
941
+
942
+ /** Returns a copy of this color scaled by the value passed in, alpha can be scaled separately
943
+ * @param {Number} scale
944
+ * @param {Number} [alphaScale=scale]
945
+ * @return {Color} */
946
+ scale(s, a=s) { return new Color(this.r*s, this.g*s, this.b*s, this.a*a); }
947
+
948
+ /** Returns a copy of this color clamped to the valid range between 0 and 1
949
+ * @return {Color} */
950
+ clamp() { return new Color(clamp(this.r), clamp(this.g), clamp(this.b), clamp(this.a)); }
951
+
952
+ /** Returns a new color that is p percent between this and the color passed in
953
+ * @param {Color} color
954
+ * @param {Number} percent
955
+ * @return {Color} */
956
+ lerp(c, p) { return this.add(c.subtract(this).scale(clamp(p))); }
957
+
958
+ /** Sets this color given a hue, saturation, lightness , and alpha
959
+ * @param {Number} [hue=0]
960
+ * @param {Number} [saturation=0]
961
+ * @param {Number} [lightness=1]
962
+ * @param {Number} [alpha=1]
963
+ * @return {Color} */
964
+ setHSLA(h=0, s=0, l=1, a=1)
965
+ {
966
+ const q = l < .5 ? l*(1+s) : l+s-l*s, p = 2*l-q,
967
+ f = (p, q, t)=>
968
+ (t = ((t%1)+1)%1) < 1/6 ? p+(q-p)*6*t :
969
+ t < 1/2 ? q :
970
+ t < 2/3 ? p+(q-p)*(2/3-t)*6 : p;
971
+
972
+ this.r = f(p, q, h + 1/3);
973
+ this.g = f(p, q, h);
974
+ this.b = f(p, q, h - 1/3);
975
+ this.a = a;
976
+ return this;
977
+ }
978
+
979
+ /** Returns a new color that has each component randomly adjusted
980
+ * @param {Number} [amount=.05]
981
+ * @param {Number} [alphaAmount=0]
982
+ * @return {Color} */
592
983
  mutate(amount=.05, alphaAmount=0)
593
984
  {
594
985
  return new Color
@@ -599,357 +990,341 @@ class Color
599
990
  this.a + rand(alphaAmount, -alphaAmount)
600
991
  ).clamp();
601
992
  }
993
+
994
+ /** Returns this color expressed as an rgba string
995
+ * @return {String} */
602
996
  rgba()
603
997
  {
604
998
  ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1 && this.a>=0 && this.a<=1);
605
999
  return `rgb(${this.r*255|0},${this.g*255|0},${this.b*255|0},${this.a})`;
606
1000
  }
1001
+
1002
+ /** Returns this color expressed as 32 bit integer value
1003
+ * @return {Number} */
607
1004
  rgbaInt()
608
1005
  {
609
1006
  ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1 && this.a>=0 && this.a<=1);
610
1007
  return (this.r*255|0) + (this.g*255<<8) + (this.b*255<<16) + (this.a*255<<24);
611
1008
  }
612
- setHSLA(h=0, s=0, l=1, a=1)
613
- {
614
- const q = l < .5 ? l*(1+s) : l+s-l*s, p = 2*l-q,
615
- f = (p, q, t)=>
616
- (t = ((t%1)+1)%1) < 1/6 ? p+(q-p)*6*t :
617
- t < 1/2 ? q :
618
- t < 2/3 ? p+(q-p)*(2/3-t)*6 : p;
619
-
620
- this.r = f(p, q, h + 1/3);
621
- this.g = f(p, q, h);
622
- this.b = f(p, q, h - 1/3);
623
- this.a = a;
624
- return this;
625
- }
626
1009
  }
627
1010
 
628
1011
  ///////////////////////////////////////////////////////////////////////////////
1012
+
1013
+ /**
1014
+ * Timer object tracks how long has passed since it was set
1015
+ * @example
1016
+ * let a = new Timer; // creates a timer that is not set
1017
+ * a.set(3); // sets the timer to 3 seconds
1018
+ *
1019
+ * let b = new Timer(1); // creates a timer with 1 second left
1020
+ * b.unset(); // unsets the timer
1021
+ */
629
1022
  class Timer
630
1023
  {
631
- constructor(timeLeft) { this.time = timeLeft == undefined ? undefined : time + timeLeft; this.setTime = timeLeft; }
1024
+ /** Create a timer object set time passed in
1025
+ * @param {Number} [timeLeft] - How much time left before the timer elapses in seconds */
1026
+ constructor(timeLeft) { this.time = timeLeft == undefined ? undefined : time + timeLeft; this.setTime = timeLeft; }
632
1027
 
1028
+ /** Set the timer with seconds passed in
1029
+ * @param {Number} [timeLeft=0] - How much time left before the timer is elapsed in seconds */
633
1030
  set(timeLeft=0) { this.time = time + timeLeft; this.setTime = timeLeft; }
634
- unset() { this.time = undefined; }
635
- isSet() { return this.time != undefined; }
636
- active() { return time <= this.time; }
637
- elapsed() { return time > this.time; }
638
- get() { return this.isSet()? time - this.time : 0; }
639
- getPercent() { return this.isSet()? percent(this.time - time, 0, this.setTime) : 0; }
640
- }
641
- /*
642
- LittleJS Engine Configuration
643
- */
644
-
645
- ///////////////////////////////////////////////////////////////////////////////
646
- // display settings
647
1031
 
648
- const maxWidth = 1920, maxHeight = 1200; // up to 1080p and 16:10
649
- let defaultFont = 'arial'; // font used for text rendering
650
- let fixedWidth = 0, fixedHeight = 0; // use native resolution
651
- let fixedFitToWindow = 1; // stretch canvas to fit window
652
- //const fixedWidth = 1280, fixedHeight = 720; // 720p
653
- //const fixedWidth = 1920, fixedHeight = 1080; // 1080p
654
- //const fixedWidth = 128, fixedHeight = 128; // PICO-8
655
- //const fixedWidth = 240, fixedHeight = 136; // TIC-80
1032
+ /** Unset the timer */
1033
+ unset() { this.time = undefined; }
656
1034
 
657
- ///////////////////////////////////////////////////////////////////////////////
658
- // tile sheet settings
659
-
660
- const defaultTileSize = vec2(16); // default size of tiles in pixels
661
- const tileBleedShrinkFix = .3; // prevent tile bleeding from neighbors
662
- let pixelated = 1; // use crisp pixels for pixel art
663
-
664
- ///////////////////////////////////////////////////////////////////////////////
665
- // webgl config
1035
+ /** Returns true if set
1036
+ * @return {Boolean} */
1037
+ isSet() { return this.time != undefined; }
666
1038
 
667
- const glEnable = 1; // can run without gl (texured coloring will be disabled)
668
- let glOverlay = 0; // fix slow rendering in some browsers by not compositing the WebGL canvas
1039
+ /** Returns true if set and has not elapsed
1040
+ * @return {Boolean} */
1041
+ active() { return time <= this.time; }
669
1042
 
670
- ///////////////////////////////////////////////////////////////////////////////
671
- // object config
1043
+ /** Returns true if set and elapsed
1044
+ * @return {Boolean} */
1045
+ elapsed() { return time > this.time; }
672
1046
 
673
- const defaultObjectSize = vec2(.999); // size of objecs, tiny bit less then 1 to fit in holes
674
- const defaultObjectMass = 1; // how heavy are objects for collison calcuations
675
- const defaultObjectDamping = .99; // how much to slow velocity by each frame 0-1
676
- const defaultObjectAngleDamping = .99; // how much to slow angular velocity each frame 0-1
677
- const defaultObjectElasticity = 0; // how much to bounce 0-1
678
- const defaultObjectFriction = .8; // how much to slow when touching 0-1
679
- const maxObjectSpeed = 1; // camp max speed to avoid fast objects missing collisions
1047
+ /** Get how long since elapsed, returns 0 if not set
1048
+ * @return {Number} */
1049
+ get() { return this.isSet()? time - this.time : 0; }
680
1050
 
681
- ///////////////////////////////////////////////////////////////////////////////
682
- // input config
1051
+ /** Get percentage elapsed based on time it was set to, returns 0 if not set
1052
+ * @return {Number} */
1053
+ getPercent() { return this.isSet()? percent(this.time - time, 0, this.setTime) : 0; }
1054
+ }
1055
+ /**
1056
+ * LittleJS Engine Settings
1057
+ * @namespace Settings
1058
+ */
683
1059
 
684
- const gamepadsEnable = 1; // should gamepads be allowed
685
- const touchInputEnable = 1; // touch input is routed to mouse
686
- const copyGamepadDirectionToStick = 1; // allow players to use dpad as analog stick
687
- const copyWASDToDpad = 1; // allow players to use WASD as direction keys
1060
+ 'use strict';
688
1061
 
689
1062
  ///////////////////////////////////////////////////////////////////////////////
690
- // audio config
691
-
692
- const soundEnable = 1; // all audio can be disabled
693
- let audioVolume = .4; // volume for sound, music and speech
694
- const defaultSoundRange = 30; // range where sound no longer plays
695
- const defaultSoundTaper = .7; // what range percent to start tapering off sound 0-1
1063
+ // Display settings
1064
+
1065
+ /** The max size of the canvas, centered if window is larger
1066
+ * @type {Vector2}
1067
+ * @default
1068
+ * @memberof Settings */
1069
+ const maxSize = vec2(1920, 1200);
1070
+
1071
+ /** Fixed size of the canvas, if enabled cavnvas size never changes
1072
+ * @type {Vector2}
1073
+ * @default
1074
+ * @memberof Settings */
1075
+ let fixedSize = vec2();
1076
+
1077
+ /** Default font used for text rendering
1078
+ * @default
1079
+ * @memberof Settings */
1080
+ let fontDefault = 'arial';
1081
+
1082
+ /** Use crisp pixels for pixel art if true
1083
+ * @default
1084
+ * @memberof Settings */
1085
+ let pixelated = 1;
696
1086
 
697
1087
  ///////////////////////////////////////////////////////////////////////////////
698
- // medals config
1088
+ // Tile sheet settings
699
1089
 
700
- const medalDisplayTime = 5; // how long to show medals
701
- const medalDisplaySlideTime = .5; // how quick to slide on/off medals
702
- const medalDisplayWidth = 640; // width of medal display
703
- const medalDisplayHeight = 99; // height of medal display
704
- const medalDisplayIconSize = 80; // size of icon in medal display
705
- /*
706
- LittleJS - The Tiny JavaScript Game Engine That Can
707
- MIT License - Copyright 2019 Frank Force
1090
+ /** Default size of tiles in pixels
1091
+ * @type {Vector2}
1092
+ * @default
1093
+ * @memberof Settings */
1094
+ const tileSizeDefault = vec2(16);
708
1095
 
709
- Engine Features
710
- - Engine and debug system are separate from game code
711
- - Object oriented with base class engine object
712
- - Engine handles core update loop
713
- - Base class object handles update, physics, collision, rendering, etc
714
- - Engine helper classes and functions like Vector2, Color, and Timer
715
- - Super fast rendering system for tile sheets
716
- - Sound effects audio with zzfx and music with zzfxm
717
- - Input processing system with gamepad and touchscreen support
718
- - Tile layer rendering and collision system
719
- - Particle effect system
720
- - Automatically calls gameInit(), gameUpdate(), gameUpdatePost(), gameRender(), gameRenderPost()
721
- - Debug tools and debug rendering system
722
- - Call engineInit() to start it up!
723
- */
1096
+ /** Prevent tile bleeding from neighbors in pixels
1097
+ * @default
1098
+ * @memberof Settings */
1099
+ const tileBleedShrinkFix = .3;
724
1100
 
725
- 'use strict';
1101
+ ///////////////////////////////////////////////////////////////////////////////
1102
+ // Object settings
1103
+
1104
+ /** Default size of objects
1105
+ * @type {Vector2}
1106
+ * @default
1107
+ * @memberof Settings */
1108
+ const objectDefaultSize = vec2(1);
1109
+
1110
+ /** Default object mass for collison calcuations (how heavy objects are)
1111
+ * @default
1112
+ * @memberof Settings */
1113
+ const objectDefaultMass = 1;
1114
+
1115
+ /** How much to slow velocity by each frame (0-1)
1116
+ * @default
1117
+ * @memberof Settings */
1118
+ const objectDefaultDamping = .99;
1119
+
1120
+ /** How much to slow angular velocity each frame (0-1)
1121
+ * @default
1122
+ * @memberof Settings */
1123
+ const objectDefaultAngleDamping = .99;
1124
+
1125
+ /** How much to bounce when a collision occurs (0-1)
1126
+ * @default
1127
+ * @memberof Settings */
1128
+ const objectDefaultElasticity = 0;
1129
+
1130
+ /** How much to slow when touching (0-1)
1131
+ * @default
1132
+ * @memberof Settings */
1133
+ const objectDefaultFriction = .8;
1134
+
1135
+ /** Clamp max speed to avoid fast objects missing collisions
1136
+ * @default
1137
+ * @memberof Settings */
1138
+ const objectMaxSpeed = 1;
1139
+
1140
+ /** How much gravity to apply to objects along the Y axis, negative is down
1141
+ * @default
1142
+ * @memberof Settings */
1143
+ let gravity = 0;
726
1144
 
727
- const engineName = 'LittleJS';
728
- const engineVersion = '1.0.14';
729
- const FPS = 60, timeDelta = 1/FPS; // engine uses a fixed time step
730
- const tileImage = new Image(); // everything uses the same tile sheet
731
-
732
- // core engine variables
733
- let mainCanvas, mainContext, overlayCanvas, overlayContext, mainCanvasSize=vec2(),
734
- engineObjects=[], engineCollideObjects=[],
735
- cameraPos=vec2(), cameraScale=max(defaultTileSize.x, defaultTileSize.y),
736
- frame=0, time=0, realTime=0, paused=0, frameTimeLastMS=0, frameTimeBufferMS=0, debugFPS=0, gravity=0,
737
- tileImageSize, tileImageSizeInverse, shrinkTilesX, shrinkTilesY, drawCount;
738
-
739
- // call this function to start the engine
740
- function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, tileImageSource)
741
- {
742
- // init engine when tiles load
743
- tileImage.onload = ()=>
744
- {
745
- // save tile image info
746
- tileImageSizeInverse = vec2(1).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
747
- debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
748
- shrinkTilesX = tileBleedShrinkFix/tileImageSize.x;
749
- shrinkTilesY = tileBleedShrinkFix/tileImageSize.y;
1145
+ ///////////////////////////////////////////////////////////////////////////////
1146
+ // Camera settings
750
1147
 
751
- // setup html
752
- document.body.appendChild(mainCanvas = document.createElement('canvas'));
753
- document.body.style = 'margin:0;overflow:hidden;background:#000';
754
- mainCanvas.style = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)' +
755
- (pixelated ? ';image-rendering:crisp-edges;image-rendering:pixelated' : ''); // pixelated rendering
756
- mainContext = mainCanvas.getContext('2d');
1148
+ /** Position of camera in world space
1149
+ * @type {Vector2}
1150
+ * @default
1151
+ * @memberof Settings */
1152
+ let cameraPos = vec2();
757
1153
 
758
- // init stuff and start engine
759
- debugInit();
760
- glInit();
1154
+ /** Scale of camera in world space
1155
+ * @default
1156
+ * @memberof Settings */
1157
+ let cameraScale = max(tileSizeDefault.x, tileSizeDefault.y);
761
1158
 
762
- // create overlay canvas for hud to appear above gl canvas
763
- document.body.appendChild(overlayCanvas = document.createElement('canvas'));
764
- overlayCanvas.style = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
765
- overlayContext = overlayCanvas.getContext('2d');
1159
+ ///////////////////////////////////////////////////////////////////////////////
1160
+ // WebGL settings
766
1161
 
767
- gameInit();
768
- engineUpdate();
769
- };
1162
+ /** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled)
1163
+ * @default
1164
+ * @memberof Settings */
1165
+ const glEnable = 1;
770
1166
 
771
- // main update loop
772
- const engineUpdate = (frameTimeMS=0)=>
773
- {
774
- requestAnimationFrame(engineUpdate);
1167
+ /** Fixes slow rendering in some browsers by not compositing the WebGL canvas
1168
+ * @default
1169
+ * @memberof Settings */
1170
+ let glOverlay = 1;
775
1171
 
776
- // update time keeping
777
- let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
778
- frameTimeLastMS = frameTimeMS;
779
- if (debug || showWatermark)
780
- debugFPS = lerp(.05, 1e3/(frameTimeDeltaMS||1), debugFPS);
781
- if (debug)
782
- frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
783
- realTime += frameTimeDeltaMS / 1e3;
784
- frameTimeBufferMS = min(frameTimeBufferMS + !paused * frameTimeDeltaMS, 50); // clamp incase of slow framerate
1172
+ ///////////////////////////////////////////////////////////////////////////////
1173
+ // Input settings
785
1174
 
786
- if (paused)
787
- {
788
- // do post update even when paused
789
- inputUpdate();
790
- debugUpdate();
791
- gameUpdatePost();
792
- inputUpdatePost();
793
- }
794
- else
795
- {
796
- // apply time delta smoothing, improves smoothness of framerate in some browsers
797
- let deltaSmooth = 0;
798
- if (frameTimeBufferMS < 0 && frameTimeBufferMS > -9)
799
- {
800
- // force an update each frame if time is close enough (not just a fast refresh rate)
801
- deltaSmooth = frameTimeBufferMS;
802
- frameTimeBufferMS = 0;
803
- }
804
-
805
- // update multiple frames if necessary in case of slow framerate
806
- for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / FPS)
807
- {
808
- // update game and objects
809
- inputUpdate();
810
- gameUpdate();
811
- engineUpdateObjects();
1175
+ /** Should gamepads be allowed
1176
+ * @default
1177
+ * @memberof Settings */
1178
+ const gamepadsEnable = 1;
812
1179
 
813
- // do post update
814
- debugUpdate();
815
- gameUpdatePost();
816
- inputUpdatePost();
817
- }
1180
+ /** If true, the dpad input is also routed to the left analog stick (for better accessability)
1181
+ * @default
1182
+ * @memberof Settings */
1183
+ const gamepadDirectionEmulateStick = 1;
818
1184
 
819
- // add the time smoothing back in
820
- frameTimeBufferMS += deltaSmooth;
821
- }
1185
+ /** If true touch input is routed to mouse functions
1186
+ * @default
1187
+ * @memberof Settings */
1188
+ const inputTouchEnable = 1;
822
1189
 
823
- if (fixedWidth)
824
- {
825
- // clear set fixed size
826
- mainCanvas.width = fixedWidth;
827
- mainCanvas.height = fixedHeight;
828
-
829
- if (fixedFitToWindow)
830
- {
831
- // fit to window by adding space on top or bottom if necessary
832
- const aspect = innerWidth / innerHeight;
833
- const fixedAspect = fixedWidth / fixedHeight;
834
- mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
835
- mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
836
- if (glCanvas)
837
- {
838
- glCanvas.style.width = mainCanvas.style.width;
839
- glCanvas.style.height = mainCanvas.style.height;
840
- }
841
- }
842
- }
843
- else
844
- {
845
- // clear and set size to same as window
846
- mainCanvas.width = min(innerWidth, maxWidth);
847
- mainCanvas.height = min(innerHeight, maxHeight);
848
- }
849
-
850
- // save canvas size and clear overlay canvas
851
- mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
852
- mainContext.imageSmoothingEnabled = !pixelated; // disable smoothing for pixel art
1190
+ /** If true the WASD keys are also routed to the direction keys (for better accessability)
1191
+ * @default
1192
+ * @memberof Settings */
1193
+ const inputWASDEmulateDirection = 1;
853
1194
 
854
- // render sort then render while removing destroyed objects
855
- glPreRender(mainCanvas.width, mainCanvas.height);
856
- gameRender();
857
- engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
858
- for (const o of engineObjects)
859
- o.destroyed || o.render();
860
- gameRenderPost();
861
- medalsRender();
862
- debugRender();
863
- glCopyToContext(mainContext);
1195
+ ///////////////////////////////////////////////////////////////////////////////
1196
+ // Audio settings
864
1197
 
865
- if (showWatermark)
866
- {
867
- // update fps
868
- overlayContext.textAlign = 'right';
869
- overlayContext.textBaseline = 'top';
870
- overlayContext.font = '1em monospace';
871
- overlayContext.fillStyle = '#000';
872
- const text = engineName + ' ' + 'v' + engineVersion + ' / '
873
- + drawCount + ' / ' + engineObjects.length + ' / ' + debugFPS.toFixed(1);
874
- overlayContext.fillText(text, mainCanvas.width-3, 3);
875
- overlayContext.fillStyle = '#fff';
876
- overlayContext.fillText(text, mainCanvas.width-2, 2);
877
- drawCount = 0;
878
- }
879
- }
1198
+ /** Volume scale to apply to all sound, music and speech
1199
+ * @default
1200
+ * @memberof Settings */
1201
+ let soundVolume = .5;
880
1202
 
881
- // set tile image source to load the image and start the engine
882
- tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
883
- }
1203
+ /** All audio code can be disabled and removed from build
1204
+ * @default
1205
+ * @memberof Settings */
1206
+ const soundEnable = 1;
884
1207
 
885
- function engineUpdateObjects()
886
- {
887
- // recursive object update
888
- const updateObject = (o)=>
889
- {
890
- if (!o.destroyed)
891
- {
892
- o.update();
893
- for (const child of o.children)
894
- updateObject(child);
895
- }
896
- }
897
- for (const o of engineObjects)
898
- o.parent || updateObject(o);
1208
+ /** Default range where sound no longer plays
1209
+ * @default
1210
+ * @memberof Settings */
1211
+ const soundDefaultRange = 30;
899
1212
 
900
- // remove destroyed objects
901
- engineObjects = engineObjects.filter(o=>!o.destroyed);
902
- engineCollideObjects = engineCollideObjects.filter(o=>!o.destroyed);
1213
+ /** Default range percent to start tapering off sound (0-1)
1214
+ * @default
1215
+ * @memberof Settings */
1216
+ const soundDefaultTaper = .7;
903
1217
 
904
- // increment frame and update time
905
- time = ++frame / FPS;
906
- }
1218
+ ///////////////////////////////////////////////////////////////////////////////
1219
+ // Medals settings
1220
+
1221
+ /** How long to show medals for in seconds
1222
+ * @default
1223
+ * @memberof Settings */
1224
+ const medalDisplayTime = 5;
1225
+
1226
+ /** How quickly to slide on/off medals in seconds
1227
+ * @default
1228
+ * @memberof Settings */
1229
+ const medalDisplaySlideTime = .5;
1230
+
1231
+ /** Width of medal display
1232
+ * @default
1233
+ * @memberof Settings */
1234
+ const medalDisplayWidth = 640;
1235
+
1236
+ /** Height of medal display
1237
+ * @default
1238
+ * @memberof Settings */
1239
+ const medalDisplayHeight = 99;
1240
+
1241
+ /** Size of icon in medal display
1242
+ * @default
1243
+ * @memberof Settings */
1244
+ const medalDisplayIconSize = 80;
907
1245
  /*
908
- LittleJS Object Base Class
909
- - Base object class used by the engine
910
- - Automatically adds self to object list
911
- - Will be updated and rendered each frame
912
- - Renders as a sprite from a tilesheet by default
913
- - Can have color and addtive color applied
914
- - 2d Physics and collision system
915
- - Sorted by renderOrder
916
- - Objects can have children attached
917
- - Parents are updated before children, and set child transform
918
- - Call destroy() to get rid of objects
1246
+ LittleJS Object System
919
1247
  */
920
1248
 
921
1249
  'use strict';
922
1250
 
1251
+ /**
1252
+ * LittleJS Object Base Object Class
1253
+ * <br> - Base object class used by the engine
1254
+ * <br> - Automatically adds self to object list
1255
+ * <br> - Will be updated and rendered each frame
1256
+ * <br> - Renders as a sprite from a tilesheet by default
1257
+ * <br> - Can have color and addtive color applied
1258
+ * <br> - 2d Physics and collision system
1259
+ * <br> - Sorted by renderOrder
1260
+ * <br> - Objects can have children attached
1261
+ * <br> - Parents are updated before children, and set child transform
1262
+ * <br> - Call destroy() to get rid of objects
1263
+ * @example
1264
+ * // create an engine object, normally you would first extend the class with your own
1265
+ * const pos = vec2(2,3);
1266
+ * const object = new EngineObject(pos);
1267
+ */
923
1268
  class EngineObject
924
1269
  {
925
- constructor(pos, size=defaultObjectSize, tileIndex=-1, tileSize=defaultTileSize, angle=0, color)
1270
+ /** Create an engine object and adds it to the list of objects
1271
+ * @param {Vector2} [position=new Vector2(0,0)] - World space position of the object
1272
+ * @param {Vector2} [size=objectDefaultSize] - World space size of the object
1273
+ * @param {Number} [tileIndex=-1] - Tile to use to render object, untextured if -1
1274
+ * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
1275
+ * @param {Number} [angle=0] - Angle to rotate the object
1276
+ * @param {Color} [color] - Color to apply to tile when rendered
1277
+ * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
1278
+ */
1279
+ constructor(pos=vec2(), size=objectDefaultSize, tileIndex=-1, tileSize=tileSizeDefault, angle=0, color, renderOrder=0)
926
1280
  {
927
1281
  // set passed in params
928
1282
  ASSERT(pos && pos.x != undefined && size.x != undefined); // ensure pos and size are vec2s
1283
+
1284
+ /** @property {Vector2} - World space position of the object */
929
1285
  this.pos = pos.copy();
1286
+ /** @property {Vector2} - World space width and height of the object */
930
1287
  this.size = size;
1288
+ /** @property {Vector2} - Size of object used for drawing, uses size if not set */
1289
+ this.drawSize;
1290
+ /** @property {Number} - Tile to use to render object, untextured if -1 */
931
1291
  this.tileIndex = tileIndex;
1292
+ /** @property {Vector2} - Size of tile in source pixels */
932
1293
  this.tileSize = tileSize;
1294
+ /** @property {Number} - Angle to rotate the object */
933
1295
  this.angle = angle;
1296
+ /** @property {Color} - Color to apply when rendered */
934
1297
  this.color = color;
935
-
936
- // set physics defaults
937
- this.mass = defaultObjectMass;
938
- this.damping = defaultObjectDamping;
939
- this.angleDamping = defaultObjectAngleDamping;
940
- this.elasticity = defaultObjectElasticity;
941
- this.friction = defaultObjectFriction;
942
-
943
- // init other object stuff
1298
+ /** @property {Color} - Additive color to apply when rendered */
1299
+ this.additiveColor;
1300
+
1301
+ // set object defaults
1302
+ /** @property {Number} [mass=objectDefaultMass] - How heavy the object is */
1303
+ this.mass = objectDefaultMass;
1304
+ /** @property {Number} [damping=objectDefaultDamping] - How much to slow down velocity each frame (0-1) */
1305
+ this.damping = objectDefaultDamping;
1306
+ /** @property {Number} [angleDamping=objectDefaultAngleDamping] - How much to slow down rotation each frame (0-1) */
1307
+ this.angleDamping = objectDefaultAngleDamping;
1308
+ /** @property {Number} [elasticity=objectDefaultElasticity] - How bouncy the object is when colliding (0-1) */
1309
+ this.elasticity = objectDefaultElasticity;
1310
+ /** @property {Number} [friction=objectDefaultFriction] - How much friction to apply when sliding (0-1) */
1311
+ this.friction = objectDefaultFriction;
1312
+ /** @property {Number} [gravityScale=1] - How much to scale gravity by for this object */
1313
+ this.gravityScale = 1;
1314
+ /** @property {Number} [renderOrder=0] - Objects are sorted by render order */
1315
+ this.renderOrder = renderOrder;
1316
+
1317
+ // init other internal object stuff
944
1318
  this.spawnTime = time;
945
- this.velocity = vec2(this.collideSolidObjects = this.renderOrder = this.angleVelocity = 0);
946
- this.collideTiles = this.gravityScale = 1;
1319
+ this.velocity = vec2(this.collideSolidObjects = this.angleVelocity = 0);
1320
+ this.collideTiles = 1;
947
1321
  this.children = [];
948
1322
 
949
1323
  // add to list of objects
950
1324
  engineObjects.push(this);
951
1325
  }
952
1326
 
1327
+ /** Update the object transform and physics, called automatically by engine once each frame */
953
1328
  update()
954
1329
  {
955
1330
  const parent = this.parent;
@@ -962,8 +1337,8 @@ class EngineObject
962
1337
  }
963
1338
 
964
1339
  // limit max speed to prevent missing collisions
965
- this.velocity.x = clamp(this.velocity.x, maxObjectSpeed, -maxObjectSpeed);
966
- this.velocity.y = clamp(this.velocity.y, maxObjectSpeed, -maxObjectSpeed);
1340
+ this.velocity.x = clamp(this.velocity.x, objectMaxSpeed, -objectMaxSpeed);
1341
+ this.velocity.y = clamp(this.velocity.y, objectMaxSpeed, -objectMaxSpeed);
967
1342
 
968
1343
  // apply physics
969
1344
  const oldPos = this.pos.copy();
@@ -1017,7 +1392,7 @@ class EngineObject
1017
1392
  if (o.mass) // push away if not fixed
1018
1393
  o.velocity = o.velocity.subtract(velocity);
1019
1394
 
1020
- debugPhysics && debugAABB(this.pos, o.pos, this.size, o.size, '#f00');
1395
+ debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f00');
1021
1396
  continue;
1022
1397
  }
1023
1398
 
@@ -1083,7 +1458,7 @@ class EngineObject
1083
1458
  this.velocity.x *= -this.elasticity;
1084
1459
  }
1085
1460
 
1086
- debugPhysics && debugAABB(this.pos, o.pos, this.size, o.size, '#f0f');
1461
+ debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f0f');
1087
1462
  }
1088
1463
  }
1089
1464
  if (this.collideTiles)
@@ -1091,8 +1466,6 @@ class EngineObject
1091
1466
  // check collision against tiles
1092
1467
  if (tileCollisionTest(this.pos, this.size, this))
1093
1468
  {
1094
- //debugPhysics && debugRect(this.pos, this.size, '#ff0');
1095
-
1096
1469
  // if already was stuck in collision, don't do anything
1097
1470
  // this should not happen unless something starts in collision
1098
1471
  if (!tileCollisionTest(oldPos, this.size, this))
@@ -1105,13 +1478,20 @@ class EngineObject
1105
1478
  // set if landed on ground
1106
1479
  this.groundObject = wasMovingDown;
1107
1480
 
1108
- // push out of collision and bounce
1109
- this.pos.y = oldPos.y;
1481
+ // bounce velocity
1110
1482
  this.velocity.y *= -this.elasticity;
1483
+
1484
+ // adjust next velocity to settle on ground
1485
+ const o = (oldPos.y - this.size.y/2|0) - (oldPos.y - this.size.y/2);
1486
+ if (o < 0 && o > -1 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
1487
+ this.velocity.y = this.damping ? (o - gravity * this.gravityScale) / this.damping : 0;
1488
+
1489
+ // move to previous position
1490
+ this.pos.y = oldPos.y;
1111
1491
  }
1112
1492
  if (isBlockedX)
1113
1493
  {
1114
- // push out of collision and bounce
1494
+ // move to previous position and bounce
1115
1495
  this.pos.x = oldPos.x;
1116
1496
  this.velocity.x *= -this.elasticity;
1117
1497
  }
@@ -1120,12 +1500,14 @@ class EngineObject
1120
1500
  }
1121
1501
  }
1122
1502
 
1503
+ /** Render the object, draws a tile by default, automatically called each frame, sorted by renderOrder */
1123
1504
  render()
1124
1505
  {
1125
1506
  // default object render
1126
1507
  drawTile(this.pos, this.drawSize || this.size, this.tileIndex, this.tileSize, this.color, this.angle, this.mirror, this.additiveColor);
1127
1508
  }
1128
1509
 
1510
+ /** Destroy this object, destroy it's children, detach it's parent, and mark it for removal */
1129
1511
  destroy()
1130
1512
  {
1131
1513
  if (this.destroyed)
@@ -1137,15 +1519,45 @@ class EngineObject
1137
1519
  for (const child of this.children)
1138
1520
  child.destroy(child.parent = 0);
1139
1521
  }
1140
- collideWithTile(data, pos) { return data > 0; }
1141
- collideWithTileRaycast(data, pos) { return data > 0; }
1522
+
1523
+ /** Called to check if a tile collision should be resolved
1524
+ * @param {Number} tileData - the value of the tile at the position
1525
+ * @param {Vector2} pos - tile where the collision occured
1526
+ * @return {Boolean} true if the collision should be resolved */
1527
+ collideWithTile(tileData, pos) { return tileData > 0; }
1528
+
1529
+ /** Called to check if a tile raycast hit
1530
+ * @param {Number} tileData - the value of the tile at the position
1531
+ * @param {Vector2} pos - tile where the raycast is
1532
+ * @return {Boolean} true if the raycast should hit */
1533
+ collideWithTileRaycast(tileData, pos) { return tileData > 0; }
1534
+
1535
+ /** Called to check if a tile raycast hit
1536
+ * @param {EngineObject} object - the object to test against
1537
+ * @return {Boolean} true if the collision should be resolved
1538
+ */
1142
1539
  collideWithObject(o) { return 1; }
1540
+
1541
+ /** How long since the object was created
1542
+ * @return {Number} */
1143
1543
  getAliveTime() { return time - this.spawnTime; }
1144
- applyAcceleration(a) { ASSERT(!this.isFixed()); this.velocity = this.velocity.add(a); }
1145
- applyForce(force) { this.applyAcceleration(force.scale(1/this.mass)); }
1146
- isFixed() { return !this.mass; }
1147
- getMirrorSign(s=1) { return this.mirror ? -s : s; }
1148
1544
 
1545
+ /** Apply acceleration to this object (adjust velocity, not affected by mass)
1546
+ * @param {Vector2} acceleration */
1547
+ applyAcceleration(a) { if (this.mass) this.velocity = this.velocity.add(a); }
1548
+
1549
+ /** Apply force to this object (adjust velocity, affected by mass)
1550
+ * @param {Vector2} force */
1551
+ applyForce(force) { this.applyAcceleration(force.scale(1/this.mass)); }
1552
+
1553
+ /** Get the direction of the mirror
1554
+ * @return {Number} -1 if this.mirror is true, or 1 if not mirrored */
1555
+ getMirrorSign() { return this.mirror ? -1 : 1; }
1556
+
1557
+ /** Attaches a child to this with a given local transform
1558
+ * @param {EngineObject} child
1559
+ * @param {Vector2} [localPos=new Vector2]
1560
+ * @param {Number} [localAngle=0] */
1149
1561
  addChild(child, localPos=vec2(), localAngle=0)
1150
1562
  {
1151
1563
  ASSERT(!child.parent && !this.children.includes(child));
@@ -1154,6 +1566,9 @@ class EngineObject
1154
1566
  child.localPos = localPos.copy();
1155
1567
  child.localAngle = localAngle;
1156
1568
  }
1569
+
1570
+ /** Removes a child from this one
1571
+ * @param {EngineObject} child */
1157
1572
  removeChild(child)
1158
1573
  {
1159
1574
  ASSERT(child.parent == this && this.children.includes(child));
@@ -1161,7 +1576,11 @@ class EngineObject
1161
1576
  child.parent = 0;
1162
1577
  }
1163
1578
 
1164
- setCollision(collideSolidObjects, isSolid, collideTiles=1)
1579
+ /** Set how this object collides
1580
+ * @param {boolean} [collideSolidObjects=0] - Does it collide with solid objects
1581
+ * @param {boolean} [isSolid=0] - Does it collide with and block other objects (expensive in large numbers)
1582
+ * @param {boolean} [collideTiles=1] - Does it collide with the tile collision */
1583
+ setCollision(collideSolidObjects=0, isSolid=0, collideTiles=1)
1165
1584
  {
1166
1585
  ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
1167
1586
 
@@ -1182,62 +1601,81 @@ class EngineObject
1182
1601
  this.collideTiles = collideTiles;
1183
1602
  }
1184
1603
  }
1185
-
1186
- function destroyAllObjects()
1187
- {
1188
- // remove all objects that are not persistent or are descendants of something persistent
1189
- for (const o of engineObjects)
1190
- o.persistent || o.parent || o.destroy();
1191
- engineObjects = engineObjects.filter(o=>!o.destroyed);
1192
- }
1193
-
1194
- function forEachObject(pos, size, callbackFunction, objects=engineObjects)
1195
- {
1196
- if (!pos)
1197
- {
1198
- // all objects
1199
- for (const o of objects)
1200
- callbackFunction(o);
1201
- }
1202
- else if (size.x != undefined)
1203
- {
1204
- // aabb test
1205
- for (const o of objects)
1206
- isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
1207
- }
1208
- else
1209
- {
1210
- // circle test
1211
- const sizeSquared = size*size;
1212
- for (const o of objects)
1213
- pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
1214
- }
1215
- }
1216
- /*
1217
- LittleJS Drawing System
1218
-
1219
- - Super fast tile sheet rendering
1220
- - Utility functions for webgl
1221
- */
1604
+ /**
1605
+ * LittleJS Drawing System
1606
+ * <br> - Hybrid with both Canvas2D and WebGL available
1607
+ * <br> - Super fast tile sheet rendering with WebGL
1608
+ * <br> - Can apply rotation, mirror, color and additive color
1609
+ * <br> - Many useful utility functions
1610
+ * @namespace Draw
1611
+ */
1222
1612
 
1223
1613
  'use strict';
1224
1614
 
1225
- // convert between screen and world coordinates
1615
+ /** Tile sheet for batch rendering system
1616
+ * @type {Image}
1617
+ * @memberof Draw */
1618
+ const tileImage = new Image();
1619
+
1620
+ /** The primary 2D canvas visible to the user
1621
+ * @type {HTMLCanvasElement}
1622
+ * @memberof Draw */
1623
+ let mainCanvas;
1624
+
1625
+ /** 2d context for mainCanvas
1626
+ * @type {CanvasRenderingContext2D}
1627
+ * @memberof Draw */
1628
+ let mainContext;
1629
+
1630
+ /** A canvas that appears on top of everything the same size as mainCanvas
1631
+ * @type {HTMLCanvasElement}
1632
+ * @memberof Draw */
1633
+ let overlayCanvas;
1634
+
1635
+ /** 2d context for overlayCanvas
1636
+ * @type {CanvasRenderingContext2D}
1637
+ * @memberof Draw */
1638
+ let overlayContext;
1639
+
1640
+ /** The size of the main canvas (and other secondary canvases)
1641
+ * @type {Vector2}
1642
+ * @memberof Draw */
1643
+ let mainCanvasSize = vec2();
1644
+
1645
+ /** Convert from screen to world space coordinates
1646
+ * @param {Vector2} screenPos
1647
+ * @return {Vector2}
1648
+ * @memberof Draw */
1226
1649
  const screenToWorld = (screenPos)=>
1227
1650
  screenPos.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos);
1651
+
1652
+ /** Convert from world to screen space coordinates
1653
+ * @param {Vector2} worldPos
1654
+ * @return {Vector2}
1655
+ * @memberof Draw */
1228
1656
  const worldToScreen = (worldPos)=>
1229
1657
  worldPos.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));
1230
1658
 
1231
- // draw textured tile centered on pos
1232
- function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=defaultTileSize, color=new Color, angle=0, mirror,
1233
- additiveColor=new Color(0,0,0,0))
1659
+ /** Draw textured tile centered in world space, with color applied if using WebGL
1660
+ * @param {Vector2} pos - Center of the tile in world space
1661
+ * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space, width and height
1662
+ * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
1663
+ * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
1664
+ * @param {Color} [color=new Color(1,1,1)] - Color to modulate with
1665
+ * @param {Number} [angle=0] - Angle to rotate by
1666
+ * @param {Boolean} [mirror=0] - If true image is flipped along the Y axis
1667
+ * @param {Color} [additiveColor=new Color(0,0,0,0)] - Additive color to be applied
1668
+ * @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
1669
+ * @memberof Draw */
1670
+ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle=0, mirror,
1671
+ additiveColor=new Color(0,0,0,0), useWebGL=glEnable)
1234
1672
  {
1235
1673
  showWatermark && ++drawCount;
1236
- if (glEnable)
1674
+ if (glEnable && useWebGL)
1237
1675
  {
1238
- if (tileIndex < 0)
1676
+ if (tileIndex < 0 || !tileImage.width)
1239
1677
  {
1240
- // if negative tile index, force untextured
1678
+ // if negative tile index or image not found, force untextured
1241
1679
  glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
1242
1680
  }
1243
1681
  else
@@ -1284,33 +1722,68 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=defaultTileSize, col
1284
1722
  }
1285
1723
  }
1286
1724
 
1287
- // draw a colored untextured rect centered on pos
1288
- function drawRect(pos, size, color, angle)
1725
+ /** Draw colored rect centered on pos
1726
+ * @param {Vector2} pos
1727
+ * @param {Vector2} [size=new Vector2(1,1)]
1728
+ * @param {Color} [color=new Color(1,1,1)]
1729
+ * @param {Number} [angle=0]
1730
+ * @param {Boolean} [useWebGL=glEnable]
1731
+ * @memberof Draw */
1732
+ function drawRect(pos, size, color, angle, useWebGL)
1289
1733
  {
1290
- drawTile(pos, size, -1, defaultTileSize, color, angle);
1734
+ drawTile(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
1291
1735
  }
1292
1736
 
1293
- // draw textured tile centered on pos in screen space
1294
- function drawTileScreenSpace(pos, size=vec2(1), tileIndex, tileSize, color, angle, mirror, additiveColor)
1737
+ /** Draw textured tile centered on pos in screen space
1738
+ * @param {Vector2} pos - Center of the tile
1739
+ * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile
1740
+ * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
1741
+ * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
1742
+ * @param {Color} [color=new Color]
1743
+ * @param {Number} [angle=0]
1744
+ * @param {Boolean} [mirror=0]
1745
+ * @param {Color} [additiveColor=new Color(0,0,0,0)]
1746
+ * @param {Boolean} [useWebGL=glEnable]
1747
+ * @memberof Draw */
1748
+ function drawTileScreenSpace(pos, size=vec2(1), tileIndex, tileSize, color, angle, mirror, additiveColor, useWebGL)
1295
1749
  {
1296
- drawTile(screenToWorld(pos), size.scale(1/cameraScale), tileIndex, tileSize, color, angle, mirror, additiveColor);
1750
+ drawTile(screenToWorld(pos), size.scale(1/cameraScale), tileIndex, tileSize, color, angle, mirror, additiveColor, useWebGL);
1297
1751
  }
1298
1752
 
1299
- // draw a colored untextured rect in screen space
1300
- function drawRectScreenSpace(pos, size, color, angle)
1753
+ /** Draw colored rectangle in screen space
1754
+ * @param {Vector2} pos
1755
+ * @param {Vector2} [size=new Vector2(1,1)]
1756
+ * @param {Color} [color=new Color(1,1,1)]
1757
+ * @param {Number} [angle=0]
1758
+ * @param {Boolean} [useWebGL=glEnable]
1759
+ * @memberof Draw */
1760
+ function drawRectScreenSpace(pos, size, color, angle, useWebGL)
1301
1761
  {
1302
- drawTileScreenSpace(pos, size, -1, defaultTileSize, color, angle);
1762
+ drawTileSrceenSpace(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
1303
1763
  }
1304
1764
 
1305
- // draw a colored line between two points
1306
- function drawLine(posA, posB, thickness=.1, color)
1765
+ /** Draw colored line between two points
1766
+ * @param {Vector2} posA
1767
+ * @param {Vector2} posB
1768
+ * @param {Number} [thickness=.1]
1769
+ * @param {Color} [color=new Color(1,1,1)]
1770
+ * @param {Boolean} [useWebGL=glEnable]
1771
+ * @memberof Draw */
1772
+ function drawLine(posA, posB, thickness=.1, color, useWebGL)
1307
1773
  {
1308
1774
  const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
1309
1775
  const size = vec2(thickness, halfDelta.length()*2);
1310
- drawRect(posA.add(halfDelta), size, color, halfDelta.angle());
1776
+ drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), 0, 0, useWebGL);
1311
1777
  }
1312
1778
 
1313
- // draw directly to the 2d canvas in world space (bipass webgl)
1779
+ /** Draw directly to a 2d canvas context in world space
1780
+ * @param {Vector2} pos
1781
+ * @param {Vector2} size
1782
+ * @param {Number} angle
1783
+ * @param {Boolean} mirror
1784
+ * @param {Function} drawFunction
1785
+ * @param {CanvasRenderingContext2D} [context=mainContext]
1786
+ * @memberof Draw */
1314
1787
  function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainContext)
1315
1788
  {
1316
1789
  // create canvas transform from world space to screen space
@@ -1324,8 +1797,16 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
1324
1797
  context.restore();
1325
1798
  }
1326
1799
 
1327
- // draw text on overlay canvas in world space
1328
- function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=defaultFont)
1800
+ /** Draw text on overlay canvas in world space
1801
+ * @param {String} text
1802
+ * @param {Vector2} pos
1803
+ * @param {Number} [size=1]
1804
+ * @param {Color} [color=new Color(1,1,1)]
1805
+ * @param {Number} [lineWidth=0]
1806
+ * @param {Color} [lineColor=new Color(0,0,0)]
1807
+ * @param {String} [textAlign='center']
1808
+ * @memberof Draw */
1809
+ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault)
1329
1810
  {
1330
1811
  pos = worldToScreen(pos);
1331
1812
  overlayContext.font = size*cameraScale + 'px '+ font;
@@ -1341,16 +1822,28 @@ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new
1341
1822
  overlayContext.fillText(text, pos.x, pos.y);
1342
1823
  }
1343
1824
 
1344
- // enable additive or regular blend mode
1345
- function setBlendMode(additive)
1825
+ /** Enable normal or additive blend mode
1826
+ * @param {Boolean} [additive=0]
1827
+ * @param {Boolean} [useWebGL=glEnable]
1828
+ * @memberof Draw */
1829
+ function setBlendMode(additive, useWebGL=glEnable)
1346
1830
  {
1347
- glEnable ? glSetBlendMode(additive) : mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
1831
+ if (glEnable && useWebGL)
1832
+ glSetBlendMode(additive);
1833
+ else
1834
+ mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
1348
1835
  }
1349
1836
 
1350
1837
  ///////////////////////////////////////////////////////////////////////////////
1351
- // fullscreen mode
1838
+ // Fullscreen mode
1352
1839
 
1840
+ /** Returns true if fullscreen mode is active
1841
+ * @return {Boolean}
1842
+ * @memberof Draw */
1353
1843
  const isFullscreen =()=> document.fullscreenElement;
1844
+
1845
+ /** Toggle fullsceen mode
1846
+ * @memberof Draw */
1354
1847
  function toggleFullscreen()
1355
1848
  {
1356
1849
  if (isFullscreen())
@@ -1368,39 +1861,107 @@ function toggleFullscreen()
1368
1861
  document.body.mozRequestFullScreen();
1369
1862
  }
1370
1863
  }
1371
- /*
1372
- LittleJS Input System
1373
- - Tracks key down, pressed, and released
1374
- - Also tracks mouse buttons, position, and wheel
1375
- - Supports multiple gamepads
1376
- */
1864
+ /**
1865
+ * LittleJS Input System
1866
+ * <br> - Tracks key down, pressed, and released
1867
+ * <br> - Also tracks mouse buttons, position, and wheel
1868
+ * <br> - Supports multiple gamepads
1869
+ * @namespace Input
1870
+ */
1377
1871
 
1378
1872
  'use strict';
1379
1873
 
1380
- // input for all devices including keyboard, mouse, and gamepad
1381
- let hadInput = 0;
1382
- const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1 ? 1 : 0;
1383
- const keyWasPressed = (key, device=0)=> inputData[device] && inputData[device][key] & 2 ? 1 : 0;
1874
+ /** Returns true if device key is down
1875
+ * @param {Number} key
1876
+ * @param {Number} [device=0]
1877
+ * @return {Boolean}
1878
+ * @memberof Input */
1879
+ const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1 ? 1 : 0;
1880
+
1881
+ /** Returns true if device key was pressed this frame
1882
+ * @param {Number} key
1883
+ * @param {Number} [device=0]
1884
+ * @return {Boolean}
1885
+ * @memberof Input */
1886
+ const keyWasPressed = (key, device=0)=> inputData[device] && inputData[device][key] & 2 ? 1 : 0;
1887
+
1888
+ /** Returns true if device key was released this frame
1889
+ * @param {Number} key
1890
+ * @param {Number} [device=0]
1891
+ * @return {Boolean}
1892
+ * @memberof Input */
1384
1893
  const keyWasReleased = (key, device=0)=> inputData[device] && inputData[device][key] & 4 ? 1 : 0;
1385
- const clearInput = ()=> inputData[0] = [];
1386
1894
 
1387
- // mouse input
1388
- const mouseIsDown = keyIsDown;
1389
- const mouseWasPressed = keyWasPressed;
1895
+ /** Clears all input
1896
+ * @memberof Input */
1897
+ const clearInput = ()=> inputData[0] = [];
1898
+
1899
+ /** Returns true if mouse button is down
1900
+ * @param {Number} button
1901
+ * @return {Boolean}
1902
+ * @memberof Input */
1903
+ const mouseIsDown = keyIsDown;
1904
+
1905
+ /** Returns true if mouse button was pressed
1906
+ * @param {Number} button
1907
+ * @return {Boolean}
1908
+ * @memberof Input */
1909
+ const mouseWasPressed = keyWasPressed;
1910
+
1911
+ /** Returns true if mouse button was released
1912
+ * @param {Number} button
1913
+ * @return {Boolean}
1914
+ * @memberof Input */
1390
1915
  const mouseWasReleased = keyWasReleased;
1391
- let mousePos = vec2();
1392
- let mousePosScreen = vec2();
1393
- let mouseWheel = 0;
1394
-
1395
- // gamepad input
1396
- let usingGamepad = 0;
1397
- const gamepadIsDown = (button, gamepad=0)=> keyIsDown (button, gamepad+1);
1398
- const gamepadWasPressed = (button, gamepad=0)=> keyWasPressed (button, gamepad+1);
1916
+
1917
+ /** Mouse pos in world space
1918
+ * @type {Vector2}
1919
+ * @memberof Input */
1920
+ let mousePos = vec2();
1921
+
1922
+ /** Mouse pos in screen space
1923
+ * @type {Vector2}
1924
+ * @memberof Input */
1925
+ let mousePosScreen = vec2();
1926
+
1927
+ /** Mouse wheel delta this frame
1928
+ * @memberof Input */
1929
+ let mouseWheel = 0;
1930
+
1931
+ /** Returns true if user is using gamepad (has more recently pressed a gamepad button)
1932
+ * @memberof Input */
1933
+ let isUsingGamepad = 0;
1934
+
1935
+ /** Returns true if gamepad button is down
1936
+ * @param {Number} button
1937
+ * @param {Number} [gamepad=0]
1938
+ * @return {Boolean}
1939
+ * @memberof Input */
1940
+ const gamepadIsDown = (button, gamepad=0)=> keyIsDown(button, gamepad+1);
1941
+
1942
+ /** Returns true if gamepad button was pressed
1943
+ * @param {Number} button
1944
+ * @param {Number} [gamepad=0]
1945
+ * @return {Boolean}
1946
+ * @memberof Input */
1947
+ const gamepadWasPressed = (button, gamepad=0)=> keyWasPressed(button, gamepad+1);
1948
+
1949
+ /** Returns true if gamepad button was released
1950
+ * @param {Number} button
1951
+ * @param {Number} [gamepad=0]
1952
+ * @return {Boolean}
1953
+ * @memberof Input */
1399
1954
  const gamepadWasReleased = (button, gamepad=0)=> keyWasReleased(button, gamepad+1);
1400
- const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepad][stick] || vec2() : vec2();
1955
+
1956
+ /** Returns gamepad stick value
1957
+ * @param {Number} stick
1958
+ * @param {Number} [gamepad=0]
1959
+ * @return {Vector2}
1960
+ * @memberof Input */
1961
+ const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepad][stick] || vec2() : vec2();
1401
1962
 
1402
1963
  ///////////////////////////////////////////////////////////////////////////////
1403
- // input update called by engine
1964
+ // Input update called by engine
1404
1965
 
1405
1966
  const inputData = [[]];
1406
1967
 
@@ -1426,23 +1987,25 @@ function inputUpdatePost()
1426
1987
  }
1427
1988
 
1428
1989
  ///////////////////////////////////////////////////////////////////////////////
1429
- // keyboard event handlers
1990
+ // Keyboard event handlers
1991
+
1430
1992
  onkeydown = e=>
1431
1993
  {
1432
1994
  if (debug && e.target != document.body) return;
1433
- e.repeat || (inputData[usingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
1434
- hadInput = 1;
1995
+ e.repeat || (inputData[isUsingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
1996
+ debug || e.preventDefault();
1435
1997
  }
1436
1998
  onkeyup = e=>
1437
1999
  {
1438
2000
  if (debug && e.target != document.body) return;
1439
2001
  inputData[0][remapKeyCode(e.keyCode)] = 4;
1440
2002
  }
1441
- const remapKeyCode = c=> copyWASDToDpad ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
2003
+ const remapKeyCode = c=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
1442
2004
 
1443
2005
  ///////////////////////////////////////////////////////////////////////////////
1444
- // mouse event handlers
1445
- onmousedown = e=> (inputData[usingGamepad = 0][e.button] = 3, hadInput = 1, onmousemove(e));
2006
+ // Mouse event handlers
2007
+
2008
+ onmousedown = e=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
1446
2009
  onmouseup = e=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
1447
2010
  onmousemove = e=>
1448
2011
  {
@@ -1456,7 +2019,7 @@ onwheel = e=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
1456
2019
  oncontextmenu = e=> !1; // prevent right click menu
1457
2020
 
1458
2021
  ///////////////////////////////////////////////////////////////////////////////
1459
- // gamepad input
2022
+ // Gamepad input
1460
2023
 
1461
2024
  const stickData = [];
1462
2025
  function gamepadsUpdate()
@@ -1490,10 +2053,10 @@ function gamepadsUpdate()
1490
2053
  {
1491
2054
  const button = gamepad.buttons[j];
1492
2055
  data[j] = button.pressed ? 1 + 2*!gamepadIsDown(j,i) : 4*gamepadIsDown(j,i);
1493
- usingGamepad |= !i && button.pressed;
2056
+ isUsingGamepad |= !i && button.pressed;
1494
2057
  }
1495
2058
 
1496
- if (copyGamepadDirectionToStick)
2059
+ if (gamepadDirectionEmulateStick)
1497
2060
  {
1498
2061
  // copy dpad to left analog stick when pressed
1499
2062
  const dpad = vec2(gamepadIsDown(15,i) - gamepadIsDown(14,i), gamepadIsDown(12,i) - gamepadIsDown(13,i));
@@ -1505,12 +2068,16 @@ function gamepadsUpdate()
1505
2068
  }
1506
2069
 
1507
2070
  ///////////////////////////////////////////////////////////////////////////////
1508
- // touch input
1509
- const isTouchDevice = touchInputEnable && window.ontouchstart !== undefined;
2071
+ // Touch input
2072
+
2073
+ /** True if a touch device has been detected
2074
+ * @const {boolean}
2075
+ * @memberof Input */
2076
+ const isTouchDevice = inputTouchEnable && window.ontouchstart !== undefined;
1510
2077
  if (isTouchDevice)
1511
2078
  {
1512
2079
  // handle all touch events the same way
1513
- let wasTouching;
2080
+ let wasTouching, hadTouchInput;
1514
2081
  ontouchstart = ontouchmove = ontouchend = e=>
1515
2082
  {
1516
2083
  e.button = 0; // all touches are left click
@@ -1519,7 +2086,7 @@ if (isTouchDevice)
1519
2086
  const touching = e.touches.length;
1520
2087
  if (touching)
1521
2088
  {
1522
- hadInput || zzfx(0) ; // fix mobile audio, force it to play a sound the first time
2089
+ hadTouchInput || zzfx(0, hadTouchInput=1) ; // fix mobile audio, force it to play a sound the first time
1523
2090
 
1524
2091
  // set event pos and pass it along
1525
2092
  e.x = e.touches[0].clientX;
@@ -1536,27 +2103,45 @@ if (isTouchDevice)
1536
2103
  return !e.cancelable;
1537
2104
  }
1538
2105
  }
1539
- /*
1540
- LittleJS Audio System
1541
- - ZzFX Sound Effects and ZzFXM Music
1542
- - Caches sounds and music for fast playback
1543
- - Can attenuate and apply stereo panning to sounds
1544
- - Ability to play mp3, ogg, and wave files
1545
- - Speech Synthesis wrapper functions
1546
- */
2106
+ /**
2107
+ * LittleJS Audio System
2108
+ * <br> - ZzFX Sound Effects and ZzFXM Music
2109
+ * <br> - Caches sounds and music for fast playback
2110
+ * <br> - Can attenuate and apply stereo panning to sounds
2111
+ * <br> - Ability to play mp3, ogg, and wave files
2112
+ * <br> - Speech synthesis wrapper functions
2113
+ * @namespace Audio
2114
+ */
1547
2115
 
1548
2116
  'use strict';
1549
2117
 
2118
+ /**
2119
+ * Sound Object - Stores a zzfx sound for later use and can be played positionally
2120
+ * @example
2121
+ * // create a sound
2122
+ * const sound_example = new Sound([.5,.5]);
2123
+ *
2124
+ * // play the sound
2125
+ * sound_example.play();
2126
+ */
1550
2127
  class Sound
1551
2128
  {
1552
- constructor(zzfxSound, range=defaultSoundRange, taper=defaultSoundTaper)
2129
+ /** Create a sound object and cache the zzfx samples for later use
2130
+ * @param {Array} zzfxSound - Array of zzfx parameters, ex. [.5,.5]
2131
+ * @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
2132
+ * @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
2133
+ */
2134
+ constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
1553
2135
  {
1554
2136
  if (!soundEnable) return;
1555
2137
 
2138
+ /** @property {Number} - World space max range of sound, will not play if camera is farther away */
1556
2139
  this.range = range;
2140
+
2141
+ /** @property {Number} - At what percentage of range should it start tapering off */
1557
2142
  this.taper = taper;
1558
2143
 
1559
- // get randomness from sound to apply when played
2144
+ // get randomness from sound parameters
1560
2145
  this.randomness = zzfxSound[1] || 0;
1561
2146
  zzfxSound[1] = 0;
1562
2147
 
@@ -1564,7 +2149,14 @@ class Sound
1564
2149
  this.cachedSamples = zzfxG(...zzfxSound);
1565
2150
  }
1566
2151
 
1567
- play(pos, volumeScale=1, pitchScale=1)
2152
+ /** Play the sound
2153
+ * @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null
2154
+ * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
2155
+ * @param {Number} [pitch=1] - How much to scale pitch by (also adjusted by this.randomness)
2156
+ * @param {Number} [randomnessScale=1] - How much to scale randomness
2157
+ * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
2158
+ */
2159
+ play(pos, volume=1, pitch=1, randomnessScale=1)
1568
2160
  {
1569
2161
  if (!soundEnable) return;
1570
2162
 
@@ -1580,7 +2172,7 @@ class Sound
1580
2172
  return; // out of range
1581
2173
 
1582
2174
  // attenuate volume by distance
1583
- volumeScale *= percent(lengthSquared**.5, range*this.taper, range);
2175
+ volume *= percent(lengthSquared**.5, range*this.taper, range);
1584
2176
  }
1585
2177
 
1586
2178
  // get pan from screen space coords
@@ -1588,13 +2180,57 @@ class Sound
1588
2180
  }
1589
2181
 
1590
2182
  // play the sound
1591
- const playbackRate = pitchScale + pitchScale * this.randomness*rand(-1,1);
1592
- return playSamples([this.cachedSamples], volumeScale, playbackRate, pan);
2183
+ const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
2184
+ return playSamples([this.cachedSamples], volume, playbackRate, pan);
2185
+ }
2186
+
2187
+ /** Play the sound as a note with a semitone offset
2188
+ * @param {Number} semitoneOffset - How many semitones to offset pitch
2189
+ * @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null
2190
+ * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
2191
+ * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
2192
+ */
2193
+ playNote(semitoneOffset, pos, volume=1)
2194
+ {
2195
+ if (!soundEnable) return;
2196
+
2197
+ return this.play(pos, volume, 2**(semitoneOffset/12), 0);
1593
2198
  }
1594
2199
  }
1595
2200
 
2201
+ /**
2202
+ * Music Object - Stores a zzfx music track for later use
2203
+ * @example
2204
+ * // create some music
2205
+ * const music_example = new Music(
2206
+ * [
2207
+ * [ // instruments
2208
+ * [,0,400] // simple note
2209
+ * ],
2210
+ * [ // patterns
2211
+ * [ // pattern 1
2212
+ * [ // channel 0
2213
+ * 0, -1, // instrument 0, left speaker
2214
+ * 1, 0, 9, 1 // channel notes
2215
+ * ],
2216
+ * [ // channel 1
2217
+ * 0, 1, // instrument 1, right speaker
2218
+ * 0, 12, 17, -1 // channel notes
2219
+ * ]
2220
+ * ],
2221
+ * ],
2222
+ * [0, 0, 0, 0], // sequence, play pattern 0 four times
2223
+ * 90 // BPM
2224
+ * ]);
2225
+ *
2226
+ * // play the music
2227
+ * music_example.play();
2228
+ */
1596
2229
  class Music
1597
2230
  {
2231
+ /** Create a music object and cache the zzfx music samples for later use
2232
+ * @param {Array} zzfxMusic - Array of zzfx music parameters
2233
+ */
1598
2234
  constructor(zzfxMusic)
1599
2235
  {
1600
2236
  if (!soundEnable) return;
@@ -1602,29 +2238,44 @@ class Music
1602
2238
  this.cachedSamples = zzfxM(...zzfxMusic);
1603
2239
  }
1604
2240
 
1605
- play(volumeScale = 1, loop = 1)
2241
+ /** Play the music
2242
+ * @param {Number} [volume=1] - How much to scale volume by
2243
+ * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
2244
+ * @return {AudioBufferSourceNode} - The audio node, can be used to stop sound later
2245
+ */
2246
+ play(volume = 1, loop = 1)
1606
2247
  {
1607
2248
  if (!soundEnable) return;
1608
2249
 
1609
- return playSamples(this.cachedSamples, volumeScale, 1, 0, loop);
2250
+ return playSamples(this.cachedSamples, volume, 1, 0, loop);
1610
2251
  }
1611
2252
  }
1612
2253
 
1613
- ///////////////////////////////////////////////////////////////////////////////
1614
-
1615
- // play mp3 or wav audio from a local file or url
1616
- function playAudioFile(url, volumeScale=1, loop=1)
2254
+ /** Play an mp3 or wav audio from a local file or url
2255
+ * @param {String} url - Location of sound file to play
2256
+ * @param {Number} [volume=1] - How much to scale volume by
2257
+ * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
2258
+ * @return {HTMLAudioElement} - The audio element for this sound
2259
+ * @memberof Audio */
2260
+ function playAudioFile(url, volume=1, loop=1)
1617
2261
  {
1618
2262
  if (!soundEnable) return;
1619
2263
 
1620
2264
  const audio = new Audio(url);
1621
- audio.volume = audioVolume * volumeScale;
2265
+ audio.volume = soundVolume * volume;
1622
2266
  audio.loop = loop;
1623
2267
  audio.play();
1624
2268
  return audio;
1625
2269
  }
1626
2270
 
1627
- // speak text with passed in settings
2271
+ /** Speak text with passed in settings
2272
+ * @param {String} text - The text to speak
2273
+ * @param {String} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
2274
+ * @param {Number} [volume=1] - How much to scale volume by
2275
+ * @param {Number} [rate=1] - How quickly to speak
2276
+ * @param {Number} [pitch=1] - How much to change the pitch by
2277
+ * @return {SpeechSynthesisUtterance} - The utterance that was spoken
2278
+ * @memberof Audio */
1628
2279
  function speak(text, language='', volume=1, rate=1, pitch=1)
1629
2280
  {
1630
2281
  if (!soundEnable || !speechSynthesis) return;
@@ -1636,22 +2287,39 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
1636
2287
  // build utterance and speak
1637
2288
  const utterance = new SpeechSynthesisUtterance(text);
1638
2289
  utterance.lang = language;
1639
- utterance.volume = volume*audioVolume*3;
2290
+ utterance.volume = 2*volume*soundVolume;
1640
2291
  utterance.rate = rate;
1641
2292
  utterance.pitch = pitch;
1642
2293
  speechSynthesis.speak(utterance);
1643
2294
  return utterance;
1644
2295
  }
1645
2296
 
1646
- // stop all queued speech
2297
+ /** Stop all queued speech
2298
+ * @memberof Audio */
1647
2299
  const stopSpeech = ()=> speechSynthesis && speechSynthesis.cancel();
1648
2300
 
1649
- ///////////////////////////////////////////////////////////////////////////////
2301
+ /** Get frequency of a note on a musical scale
2302
+ * @param {Number} semitoneOffset - How many semitones away from the root note
2303
+ * @param {Number} [rootNoteFrequency=220] - Frequency at semitone offset 0
2304
+ * @return {Number} - The frequency of the note
2305
+ * @memberof Audio */
2306
+ const getNoteFrequency = (semitoneOffset, rootFrequency=220)=> rootFrequency * 2**(semitoneOffset/12);
1650
2307
 
1651
- let audioContext; // audio context used by the engine
2308
+ ///////////////////////////////////////////////////////////////////////////////
1652
2309
 
1653
- // play cached samples with given settings
1654
- function playSamples(sampleChannels, volume=1, playbackRate=1, pan=0, loop=0)
2310
+ /** Audio context used by the engine
2311
+ * @memberof Audio */
2312
+ let audioContext;
2313
+
2314
+ /** Play cached audio samples with given settings
2315
+ * @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
2316
+ * @param {Number} [volume=1] - How much to scale volume by
2317
+ * @param {Number} [rate=1] - The playback rate to use
2318
+ * @param {Number} [pan=0] - How much to apply stereo panning
2319
+ * @param {Boolean} [loop=0] - True if the sound should loop when it reaches the end
2320
+ * @return {AudioBufferSourceNode} - The audio node of the sound played
2321
+ * @memberof Audio */
2322
+ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
1655
2323
  {
1656
2324
  if (!soundEnable) return;
1657
2325
 
@@ -1666,13 +2334,13 @@ function playSamples(sampleChannels, volume=1, playbackRate=1, pan=0, loop=0)
1666
2334
  // copy samples to buffer and setup source
1667
2335
  sampleChannels.forEach((c,i)=> buffer.getChannelData(i).set(c));
1668
2336
  source.buffer = buffer;
1669
- source.playbackRate.value = playbackRate;
2337
+ source.playbackRate.value = rate;
1670
2338
  source.loop = loop;
1671
2339
 
1672
2340
  // create pan and gain nodes
1673
2341
  source
1674
2342
  .connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, 1, -1)}))
1675
- .connect(new GainNode(audioContext, {'gain':audioVolume*volume}))
2343
+ .connect(new GainNode(audioContext, {'gain':soundVolume*volume}))
1676
2344
  .connect(audioContext.destination);
1677
2345
 
1678
2346
  // play and return sound
@@ -1683,10 +2351,20 @@ function playSamples(sampleChannels, volume=1, playbackRate=1, pan=0, loop=0)
1683
2351
  ///////////////////////////////////////////////////////////////////////////////
1684
2352
  // ZzFXMicro - Zuper Zmall Zound Zynth - v1.1.8 by Frank Force
1685
2353
 
1686
- const zzfxR = 44100; // sample rate
1687
- const zzfx = (...z) => playSamples([zzfxG(...z)]); // generate and play sound
2354
+ /** Generate and play a ZzFX sound
2355
+ * @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
2356
+ * @return {Array} - Array of audio samples
2357
+ * @memberof Audio */
2358
+ const zzfx = (...zzfxSound) => playSamples([zzfxG(...zzfxSound)]);
1688
2359
 
1689
- function zzfxG // generate samples
2360
+ /** Sample rate used for all ZzFX sounds
2361
+ * @default 44100
2362
+ * @memberof Audio */
2363
+ const zzfxR = 44100;
2364
+
2365
+ /** Generate samples for a ZzFX sound
2366
+ * @memberof Audio */
2367
+ function zzfxG
1690
2368
  (
1691
2369
  // parameters
1692
2370
  volume = 1, randomness = .05, frequency = 220, attack = 0, sustain = 0,
@@ -1730,7 +2408,7 @@ function zzfxG // generate samples
1730
2408
  1 - tremolo + tremolo*Math.sin(PI2*i/repeatTime) // tremolo
1731
2409
  : 1) *
1732
2410
  sign(s)*(abs(s)**shapeCurve) * // curve 0=square, 2=pointy
1733
- volume * audioVolume * ( // envelope
2411
+ volume * soundVolume * ( // envelope
1734
2412
  i < attack ? i/attack : // attack
1735
2413
  i < attack + decay ? // decay
1736
2414
  1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
@@ -1771,6 +2449,13 @@ function zzfxG // generate samples
1771
2449
  ///////////////////////////////////////////////////////////////////////////////
1772
2450
  // ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
1773
2451
 
2452
+ /** Generate samples for a ZzFM song with given parameters
2453
+ * @param {Array} instruments - Array of ZzFX sound paramaters
2454
+ * @param {Array} patterns - Array of pattern data
2455
+ * @param {Array} sequence - Array of pattern indexes
2456
+ * @param {Number} [BPM=125] - Playback speed of the song in BPM
2457
+ * @returns {Array} - Left and right channel sample data
2458
+ * @memberof Audio */
1774
2459
  function zzfxM(instruments, patterns, sequence, BPM = 125)
1775
2460
  {
1776
2461
  let instrumentParameters;
@@ -1868,25 +2553,31 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
1868
2553
 
1869
2554
  return [leftChannelBuffer, rightChannelBuffer];
1870
2555
  }
1871
- /*
1872
- LittleJS Tile Layer System
1873
- - Caches arrays of tiles to offscreen canvas for fast rendering
1874
- - Unlimted numbers of layers, allocates canvases as needed
1875
- - Interfaces with EngineObject for collision
1876
- - Collision layer is separate from visible layers
1877
- - Tile layers can be drawn to using their context with canvas2d
1878
- - It is recommended to have a visible layer that matches the collision
1879
- */
2556
+ /**
2557
+ * LittleJS Tile Layer System
2558
+ * <br> - Caches arrays of tiles to offscreen canvas for fast rendering
2559
+ * <br> - Unlimted numbers of layers, allocates canvases as needed
2560
+ * <br> - Interfaces with EngineObject for collision
2561
+ * <br> - Collision layer is separate from visible layers
2562
+ * <br> - Tile layers can be drawn to using their context with canvas2d
2563
+ * <br> - It is recommended to have a visible layer that matches the collision
2564
+ * @namespace TileCollision
2565
+ */
1880
2566
 
1881
2567
  'use strict';
1882
2568
 
1883
- ///////////////////////////////////////////////////////////////////////////////
1884
- // Tile Collision
1885
-
2569
+ /** The tile collision layer array, use setTileCollisionData and getTileCollisionData to access
2570
+ * @memberof TileCollision */
1886
2571
  let tileCollision = [];
2572
+
2573
+ /** Size of the tile collision layer
2574
+ * @type {Vector2}
2575
+ * @memberof TileCollision */
1887
2576
  let tileCollisionSize = vec2();
1888
- const tileLayerCanvasCache = [];
1889
2577
 
2578
+ /** Clear and initialize tile collision
2579
+ * @param {Vector2} size
2580
+ * @memberof TileCollision */
1890
2581
  function initTileCollision(size)
1891
2582
  {
1892
2583
  tileCollisionSize = size;
@@ -1895,21 +2586,34 @@ function initTileCollision(size)
1895
2586
  tileCollision[i] = 0;
1896
2587
  }
1897
2588
 
1898
- // set and get collision data
2589
+ /** Set tile collision data
2590
+ * @param {Vector2} pos
2591
+ * @param {Number} [data=0]
2592
+ * @memberof TileCollision */
1899
2593
  const setTileCollisionData = (pos, data=0)=>
1900
2594
  pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
2595
+
2596
+ /** Get tile collision data
2597
+ * @param {Vector2} pos
2598
+ * @return {Number}
2599
+ * @memberof TileCollision */
1901
2600
  const getTileCollisionData = (pos)=>
1902
2601
  pos.arrayCheck(tileCollisionSize) ? tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] : 0;
1903
2602
 
1904
- // check if there is collision in a given area
2603
+ /** Check if collision with another object should occur
2604
+ * @param {Vector2} pos
2605
+ * @param {Vector2} [size=new Vector2(1,1)]
2606
+ * @param {EngineObject} [object]
2607
+ * @return {Boolean}
2608
+ * @memberof TileCollision */
1905
2609
  function tileCollisionTest(pos, size=vec2(), object)
1906
2610
  {
1907
2611
  const minX = max(Math.floor(pos.x - size.x/2), 0);
1908
2612
  const minY = max(Math.floor(pos.y - size.y/2), 0);
1909
- const maxX = min(Math.floor(pos.x + size.x/2), tileCollisionSize.x-1);
1910
- const maxY = min(Math.floor(pos.y + size.y/2), tileCollisionSize.y-1);
1911
- for (let y = minY; y <= maxY; ++y)
1912
- for (let x = minX; x <= maxX; ++x)
2613
+ const maxX = min(pos.x + size.x/2, tileCollisionSize.x-1);
2614
+ const maxY = min(pos.y + size.y/2, tileCollisionSize.y-1);
2615
+ for (let y = minY; y < maxY; ++y)
2616
+ for (let x = minX; x < maxX; ++x)
1913
2617
  {
1914
2618
  const tileData = tileCollision[y*tileCollisionSize.x+x];
1915
2619
  if (tileData && (!object || object.collideWithTile(tileData, new Vector2(x, y))))
@@ -1917,13 +2621,18 @@ function tileCollisionTest(pos, size=vec2(), object)
1917
2621
  }
1918
2622
  }
1919
2623
 
1920
- // return the center of tile if any that is hit (this does not return the exact hit point)
1921
- // todo: a way to get the exact hit point, it must still register as inside the hit tile
2624
+ /** Return the center of tile if any that is hit (this does not return the exact hit point)
2625
+ * @param {Vector2} posStart
2626
+ * @param {Vector2} posEnd
2627
+ * @param {EngineObject} [object]
2628
+ * @return {Vector2}
2629
+ * @memberof TileCollision */
1922
2630
  function tileCollisionRaycast(posStart, posEnd, object)
1923
2631
  {
1924
2632
  // test if a ray collides with tiles from start to end
1925
- posStart = posStart.int();
1926
- posEnd = posEnd.int();
2633
+ // todo: a way to get the exact hit point, it must still register as inside the hit tile
2634
+ posStart = posStart.floor();
2635
+ posEnd = posEnd.floor();
1927
2636
  const posDelta = posEnd.subtract(posStart);
1928
2637
  const dx = abs(posDelta.x), dy = -abs(posDelta.y);
1929
2638
  const sx = sign(posDelta.x), sy = sign(posDelta.y);
@@ -1951,32 +2660,74 @@ function tileCollisionRaycast(posStart, posEnd, object)
1951
2660
  ///////////////////////////////////////////////////////////////////////////////
1952
2661
  // Tile Layer Rendering System
1953
2662
 
2663
+ // Reuse canvas autmatically when destroyed
2664
+ const tileLayerCanvasCache = [];
2665
+
2666
+ /**
2667
+ * Tile layer data object stores info about how to render a tile
2668
+ * @example
2669
+ * // create tile layer data with tile index 0 and random orientation and color
2670
+ * const tileIndex = 0;
2671
+ * const direction = randInt(4)
2672
+ * const mirror = randInt(2);
2673
+ * const color = randColor();
2674
+ * const data = new TileLayerData(tileIndex, direction, mirror, color);
2675
+ */
1954
2676
  class TileLayerData
1955
2677
  {
1956
- constructor(tile=-1, direction=0, mirror=0, color=new Color)
2678
+ /** Create a tile layer data object, one for each tile in a TileLayer
2679
+ * @param {Number} [tile] - The tile to use, untextured if undefined
2680
+ * @param {Number} [direction=0] - Integer direction of tile, in 90 degree increments
2681
+ * @param {Boolean} [mirror=0] - If the tile should be mirrored along the x axis
2682
+ * @param {Color} [color=new Color(1,1,1)] - Color of the tile */
2683
+ constructor(tile, direction=0, mirror=0, color=new Color)
1957
2684
  {
2685
+ /** @property {Number} - The tile to use, untextured if undefined */
1958
2686
  this.tile = tile;
2687
+ /** @property {Number} - Integer direction of tile, in 90 degree increments */
1959
2688
  this.direction = direction;
2689
+ /** @property {Boolean} - If the tile should be mirrored along the x axis */
1960
2690
  this.mirror = mirror;
2691
+ /** @property {Color} - Color of the tile */
1961
2692
  this.color = color;
1962
2693
  }
2694
+
2695
+ /** Set this tile to clear, it will not be rendered */
1963
2696
  clear() { this.tile = this.direction = this.mirror = 0; color = new Color; }
1964
2697
  }
1965
2698
 
2699
+ /**
2700
+ * Tile layer object - cached rendering system for tile layers
2701
+ * <br> - Each Tile layer is rendered to an off screen canvas
2702
+ * <br> - Tile layers are not rendered using WebGL to allow modifications at run time
2703
+ * <br> - Tile layers are sorted
2704
+ * @extends EngineObject
2705
+ * @example
2706
+ * // create tile collision and visible tile layer
2707
+ * initTileCollision(vec2(200,100));
2708
+ * const tileLayer = new TileLayer();
2709
+ */
1966
2710
  class TileLayer extends EngineObject
1967
2711
  {
1968
- constructor(pos, size, scale=vec2(1), layer=0)
2712
+ /** Create a tile layer object
2713
+ * @param {Vector2} [position=new Vector2(0,0)] - World space position
2714
+ * @param {Vector2} [size=objectDefaultSize] - World space size
2715
+ * @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
2716
+ * @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered
2717
+ * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
2718
+ */
2719
+ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
1969
2720
  {
1970
- super(pos, size);
2721
+ super(pos, size, -1, tileSize, 0, undefined, renderOrder);
1971
2722
 
1972
- // create new canvas if necessary
2723
+ /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
1973
2724
  this.canvas = tileLayerCanvasCache.length ? tileLayerCanvasCache.pop() : document.createElement('canvas');
2725
+ /** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
1974
2726
  this.context = this.canvas.getContext('2d');
2727
+ /** @property {Vector2} - How much to scale this layer when rendered */
1975
2728
  this.scale = scale;
1976
- this.tileSize = defaultTileSize.copy();
1977
- this.layer = layer;
1978
- this.renderOrder = layer;
1979
- this.flushGLBeforeRender = 1;
2729
+ /** @property {Boolean} [isOverlay=0] - If true this layer will render to overlay canvas and appear above all objects */
2730
+ this.isOverlay;
1980
2731
 
1981
2732
  // init tile data
1982
2733
  this.data = [];
@@ -1984,6 +2735,7 @@ class TileLayer extends EngineObject
1984
2735
  this.data.push(new TileLayerData());
1985
2736
  }
1986
2737
 
2738
+ /** Destroy this tile layer */
1987
2739
  destroy()
1988
2740
  {
1989
2741
  // add canvas back to the cache
@@ -1991,6 +2743,10 @@ class TileLayer extends EngineObject
1991
2743
  super.destroy();
1992
2744
  }
1993
2745
 
2746
+ /** Set data at a given position in the array
2747
+ * @param {Vector2} position - Local position in array
2748
+ * @param {TileLayerData} data - Data to set
2749
+ * @param {Boolean} [redraw=0] - Force the tile to redraw if true */
1994
2750
  setData(layerPos, data, redraw)
1995
2751
  {
1996
2752
  if (layerPos.arrayCheck(this.size))
@@ -2000,38 +2756,46 @@ class TileLayer extends EngineObject
2000
2756
  }
2001
2757
  }
2002
2758
 
2759
+ /** Get data at a given position in the array
2760
+ * @param {Vector2} layerPos - Local position in array
2761
+ * @return {TileLayerData} */
2003
2762
  getData(layerPos)
2004
2763
  { return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0]; }
2005
2764
 
2006
- update() {} // tile layers are not updated
2765
+ // Tile layers are not updated
2766
+ update() {}
2767
+
2768
+ // Render the tile layer, called automatically by the engine
2007
2769
  render()
2008
2770
  {
2009
2771
  ASSERT(mainContext != this.context); // must call redrawEnd() after drawing tiles
2010
2772
 
2011
- // flush and copy gl canvas because tile canvas does not use gl
2012
- this.flushGLBeforeRender && glEnable && glCopyToContext(mainContext);
2773
+ // flush and copy gl canvas because tile canvas does not use webgl
2774
+ glEnable && !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
2013
2775
 
2014
2776
  // draw the entire cached level onto the main canvas
2015
2777
  const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
2016
- mainContext.drawImage
2778
+ (this.isOverlay ? overlayContext : mainContext).drawImage
2017
2779
  (
2018
2780
  this.canvas, pos.x, pos.y,
2019
2781
  cameraScale*this.size.x*this.scale.x, cameraScale*this.size.y*this.scale.y
2020
2782
  );
2021
2783
  }
2022
2784
 
2785
+ /** Draw all the tile data to an offscreen canvas using webgl if possible */
2023
2786
  redraw()
2024
2787
  {
2025
- // draw all the tile data to an offscreen canvas using webgl if possible
2026
2788
  this.redrawStart();
2027
2789
  this.drawAllTileData();
2028
2790
  this.redrawEnd();
2029
2791
  }
2030
2792
 
2793
+ /** Call to start the redraw process
2794
+ * @param {Boolean} [clear=1] - Should it clear the canvas before drawing */
2031
2795
  redrawStart(clear = 1)
2032
2796
  {
2033
2797
  // clear and set size
2034
- const width = this.size.x * this.tileSize.x;
2798
+ const width = this.size.x * this.tileSize.x;
2035
2799
  const height = this.size.y * this.tileSize.y;
2036
2800
  if (clear)
2037
2801
  {
@@ -2052,6 +2816,7 @@ class TileLayer extends EngineObject
2052
2816
  glPreRender(width, height);
2053
2817
  }
2054
2818
 
2819
+ /** Call to end the redraw process */
2055
2820
  redrawEnd()
2056
2821
  {
2057
2822
  ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
@@ -2062,18 +2827,24 @@ class TileLayer extends EngineObject
2062
2827
  [mainCanvasSize, mainCanvas, mainContext, cameraScale, cameraPos] = this.savedRenderSettings;
2063
2828
  }
2064
2829
 
2830
+ /** Draw the tile at a given position
2831
+ * @param {Vector2} layerPos */
2065
2832
  drawTileData(layerPos)
2066
2833
  {
2067
2834
  // first clear out where the tile was
2068
- const pos = layerPos.int().add(this.pos).add(vec2(.5));
2835
+ const pos = layerPos.floor().add(this.pos).add(vec2(.5));
2069
2836
  this.drawCanvas2D(pos, vec2(1), 0, 0, (context)=>context.clearRect(-.5, -.5, 1, 1));
2070
2837
 
2071
- // draw the tile
2838
+ // draw the tile if not undefined
2072
2839
  const d = this.getData(layerPos);
2073
- ASSERT(d.tile < 0 || mainContext == this.context); // must call redrawStart() before drawing tiles
2074
- d.tile < 0 || drawTile(pos, vec2(1), d.tile || -1, this.tileSize, d.color, d.direction*PI/2, d.mirror);
2840
+ if (d.tile != undefined)
2841
+ {
2842
+ ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
2843
+ drawTile(pos, vec2(1), d.tile, this.tileSize, d.color, d.direction*PI/2, d.mirror);
2844
+ }
2075
2845
  }
2076
2846
 
2847
+ /** Draw all the tiles in this layer */
2077
2848
  drawAllTileData()
2078
2849
  {
2079
2850
  for (let x = this.size.x; x--;)
@@ -2081,7 +2852,12 @@ class TileLayer extends EngineObject
2081
2852
  this.drawTileData(vec2(x,y));
2082
2853
  }
2083
2854
 
2084
- // draw directly to the 2d canvas in world space (bipass webgl)
2855
+ /** Draw directly to the 2d canvas in world space (bipass webgl)
2856
+ * @param {Vector2} pos
2857
+ * @param {Vector2} size
2858
+ * @param {Number} angle
2859
+ * @param {Boolean} mirror
2860
+ * @param {Function} drawFunction */
2085
2861
  drawCanvas2D(pos, size, angle, mirror, drawFunction)
2086
2862
  {
2087
2863
  const context = this.context;
@@ -2095,8 +2871,15 @@ class TileLayer extends EngineObject
2095
2871
  context.restore();
2096
2872
  }
2097
2873
 
2098
- // draw a tile directly onto the layer canvas
2099
- drawTile(pos, size=vec2(1), tileIndex=0, tileSize=defaultTileSize, color=new Color, angle=0, mirror)
2874
+ /** Draw a tile directly onto the layer canvas
2875
+ * @param {Vector2} pos
2876
+ * @param {Vector2} [size=new Vector2(1,1)]
2877
+ * @param {Number} [tileIndex=-1]
2878
+ * @param {Vector2} [tileSize=tileSizeDefault]
2879
+ * @param {Color} [color=new Color(1,1,1)]
2880
+ * @param {Number} [angle=0]
2881
+ * @param {Boolean} [mirror=0] */
2882
+ drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle=0, mirror)
2100
2883
  {
2101
2884
  this.drawCanvas2D(pos, size, angle, mirror, (context)=>
2102
2885
  {
@@ -2117,6 +2900,11 @@ class TileLayer extends EngineObject
2117
2900
  });
2118
2901
  }
2119
2902
 
2903
+ /** Draw a rectangle directly onto the layer canvas
2904
+ * @param {Vector2} pos
2905
+ * @param {Vector2} [size=new Vector2(1,1)]
2906
+ * @param {Color} [color=new Color(1,1,1)]
2907
+ * @param {Number} [angle=0] */
2120
2908
  drawRect(pos, size, color, angle) { this.drawTile(pos, size, -1, 0, color, angle, 0); }
2121
2909
  }
2122
2910
  /*
@@ -2128,72 +2916,142 @@ class TileLayer extends EngineObject
2128
2916
 
2129
2917
  'use strict';
2130
2918
 
2919
+ /**
2920
+ * Particle Emitter - Spawns particles with the given settings
2921
+ * @extends EngineObject
2922
+ * @example
2923
+ * // create a particle emitter
2924
+ * let pos = vec2(2,3);
2925
+ * let particleEmiter = new ParticleEmitter
2926
+ * (
2927
+ * pos, 1, 0, 500, PI, // pos, emitSize, emitTime, emitRate, emiteCone
2928
+ * 0, vec2(16), // tileIndex, tileSize
2929
+ * new Color, new Color(0,0,0), // colorStartA, colorStartB
2930
+ * new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
2931
+ * 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
2932
+ * .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
2933
+ * .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
2934
+ * );
2935
+ */
2131
2936
  class ParticleEmitter extends EngineObject
2132
2937
  {
2938
+ /** Create a particle system with the given settings
2939
+ * @param {Vector2} position - World space position of the emitter
2940
+ * @param {Number} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect)
2941
+ * @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
2942
+ * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
2943
+ * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
2944
+ * @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied
2945
+ * @param {Number} [tileSize=tileSizeDefault] - Tile size for particles
2946
+ * @param {Color} [colorStartA=new Color(1,1,1)] - Color at start of life 1, randomized between start colors
2947
+ * @param {Color} [colorStartB=new Color(1,1,1)] - Color at start of life 2, randomized between start colors
2948
+ * @param {Color} [colorEndA=new Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
2949
+ * @param {Color} [colorEndB=new Color(1,1,1,0)] - Color at end of life 2, randomized between end colors
2950
+ * @param {Number} [particleTime=.5] - How long particles live
2951
+ * @param {Number} [sizeStart=.1] - How big are particles at start
2952
+ * @param {Number} [sizeEnd=1] - How big are particles at end
2953
+ * @param {Number} [speed=.1] - How fast are particles when spawned
2954
+ * @param {Number} [angleSpeed=.05] - How fast are particles rotating
2955
+ * @param {Number} [damping=1] - How much to dampen particle speed
2956
+ * @param {Number} [angleDamping=1] - How much to dampen particle angular speed
2957
+ * @param {Number} [gravityScale=0] - How much does gravity effect particles
2958
+ * @param {Number} [particleConeAngle=PI] - Cone for start particle angle
2959
+ * @param {Number} [fadeRate=.1] - How quick to fade in particles at start/end in percent of life
2960
+ * @param {Number} [randomness=.2] - Apply extra randomness percent
2961
+ * @param {Boolean} [collideTiles=0] - Do particles collide against tiles
2962
+ * @param {Boolean} [additive=0] - Should particles use addtive blend
2963
+ * @param {Boolean} [randomColorLinear=0] - Should color be randomized linearly or across each component
2964
+ * @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
2965
+ */
2133
2966
  constructor
2134
2967
  (
2135
- pos, // world space position of emitter
2136
- emitSize = 0, // size of emitter (float for circle diameter, vec2 for rect)
2137
- emitTime = 0, // how long to stay alive (0 is forever)
2138
- emitRate = 100, // how many particles per second to spawn
2139
- emitConeAngle = PI, // local angle to apply velocity to particles from emitter
2140
- tileIndex = -1, // index into tile sheet, if <0 no texture is applied
2141
- tileSize = defaultTileSize, // tile size for particles
2142
- colorStartA = new Color, // color at start of life
2143
- colorStartB = new Color, // randomized between start colors
2144
- colorEndA = new Color(1,1,1,0), // color at end of life
2145
- colorEndB = new Color(1,1,1,0), // randomized between end colors
2146
- particleTime = .5, // how long particles live
2147
- sizeStart = .1, // how big are particles at start
2148
- sizeEnd = 1, // how big are particles at end
2149
- speed = .1, // how fast are particles when spawned
2150
- angleSpeed = .05, // how fast are particles rotating
2151
- damping = 1, // how much to dampen particle speed
2152
- angleDamping = 1, // how much to dampen particle angular speed
2153
- gravityScale = 0, // how much does gravity effect particles
2154
- particleConeAngle = PI, // cone for start particle angle
2155
- fadeRate = .1, // how quick to fade in particles at start/end in percent of life
2156
- randomness = .2, // apply extra randomness percent
2157
- collideTiles, // do particles collide against tiles
2158
- additive, // should particles use addtive blend
2159
- randomColorLinear = 1, // should color be randomized linearly or across each component
2160
- renderOrder = additive ? 1e9 : 0// render order for particles (additive is above other stuff by default)
2968
+ pos,
2969
+ emitSize = 0,
2970
+ emitTime = 0,
2971
+ emitRate = 100,
2972
+ emitConeAngle = PI,
2973
+ tileIndex = -1,
2974
+ tileSize = tileSizeDefault,
2975
+ colorStartA = new Color,
2976
+ colorStartB = new Color,
2977
+ colorEndA = new Color(1,1,1,0),
2978
+ colorEndB = new Color(1,1,1,0),
2979
+ particleTime = .5,
2980
+ sizeStart = .1,
2981
+ sizeEnd = 1,
2982
+ speed = .1,
2983
+ angleSpeed = .05,
2984
+ damping = 1,
2985
+ angleDamping = 1,
2986
+ gravityScale = 0,
2987
+ particleConeAngle = PI,
2988
+ fadeRate = .1,
2989
+ randomness = .2,
2990
+ collideTiles,
2991
+ additive,
2992
+ randomColorLinear = 1,
2993
+ renderOrder = additive ? 1e9 : 0
2161
2994
  )
2162
2995
  {
2163
- super(pos, new Vector2, tileIndex, tileSize);
2996
+ super(pos, new Vector2, tileIndex, tileSize, 0, undefined, renderOrder);
2164
2997
 
2165
2998
  // emitter settings
2999
+ /** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
2166
3000
  this.emitSize = emitSize
3001
+ /** @property {Number} - How long to stay alive (0 is forever) */
2167
3002
  this.emitTime = emitTime;
3003
+ /** @property {Number} - How many particles per second to spawn, does not emit if 0 */
2168
3004
  this.emitRate = emitRate;
3005
+ /** @property {Number} - Local angle to apply velocity to particles from emitter */
2169
3006
  this.emitConeAngle = emitConeAngle;
2170
3007
 
2171
3008
  // color settings
3009
+ /** @property {Color} - Color at start of life 1, randomized between start colors */
2172
3010
  this.colorStartA = colorStartA;
3011
+ /** @property {Color} - Color at start of life 2, randomized between start colors */
2173
3012
  this.colorStartB = colorStartB;
3013
+ /** @property {Color} - Color at end of life 1, randomized between end colors */
2174
3014
  this.colorEndA = colorEndA;
3015
+ /** @property {Color} - Color at end of life 2, randomized between end colors */
2175
3016
  this.colorEndB = colorEndB;
3017
+ /** @property {Boolean} - Should color be randomized linearly or across each component */
2176
3018
  this.randomColorLinear = randomColorLinear;
2177
3019
 
2178
3020
  // particle settings
3021
+ /** @property {Number} - How long particles live */
2179
3022
  this.particleTime = particleTime;
3023
+ /** @property {Number} - How big are particles at start */
2180
3024
  this.sizeStart = sizeStart;
3025
+ /** @property {Number} - How big are particles at end */
2181
3026
  this.sizeEnd = sizeEnd;
3027
+ /** @property {Number} - How fast are particles when spawned */
2182
3028
  this.speed = speed;
3029
+ /** @property {Number} - How fast are particles rotating */
2183
3030
  this.angleSpeed = angleSpeed;
3031
+ /** @property {Number} - How much to dampen particle speed */
2184
3032
  this.damping = damping;
3033
+ /** @property {Number} - How much to dampen particle angular speed */
2185
3034
  this.angleDamping = angleDamping;
3035
+ /** @property {Number} - How much does gravity effect particles */
2186
3036
  this.gravityScale = gravityScale;
3037
+ /** @property {Number} - Cone for start particle angle */
2187
3038
  this.particleConeAngle = particleConeAngle;
3039
+ /** @property {Number} - How quick to fade in particles at start/end in percent of life */
2188
3040
  this.fadeRate = fadeRate;
3041
+ /** @property {Number} - Apply extra randomness percent */
2189
3042
  this.randomness = randomness;
3043
+ /** @property {Number} - Do particles collide against tiles */
2190
3044
  this.collideTiles = collideTiles;
3045
+ /** @property {Number} - Should particles use addtive blend */
2191
3046
  this.additive = additive;
2192
- this.renderOrder = renderOrder;
2193
- this.trailScale =
3047
+ /** @property {Number} - If set the partile is drawn as a trail, stretched in the drection of velocity */
3048
+ this.trailScale = 0;
3049
+
3050
+ // internal variables
2194
3051
  this.emitTimeBuffer = 0;
2195
3052
  }
2196
3053
 
3054
+ /** Update the emitter to spawn particles, called automatically by engine once each frame */
2197
3055
  update()
2198
3056
  {
2199
3057
  // only do default update to apply parent transforms
@@ -2216,6 +3074,8 @@ class ParticleEmitter extends EngineObject
2216
3074
  debugParticles && debugRect(this.pos, vec2(this.emitSize), '#0f0', 0, this.angle);
2217
3075
  }
2218
3076
 
3077
+ /** Spawn one particle
3078
+ * @return {Particle} */
2219
3079
  emitParticle()
2220
3080
  {
2221
3081
  // spawn a particle
@@ -2267,16 +3127,27 @@ class ParticleEmitter extends EngineObject
2267
3127
  return particle;
2268
3128
  }
2269
3129
 
2270
- render() {} // emitters are not rendered
3130
+ // Particle emitters are not rendered, only the particles are
3131
+ render() {}
2271
3132
  }
2272
3133
 
2273
3134
  ///////////////////////////////////////////////////////////////////////////////
2274
- // particle object
2275
-
3135
+ /**
3136
+ * Particle Object - Created automatically by Particle Emitters
3137
+ * @extends EngineObject
3138
+ */
2276
3139
  class Particle extends EngineObject
2277
3140
  {
3141
+ /**
3142
+ * Create a particle with the given settings
3143
+ * @param {Vector2} position - World space position of the particle
3144
+ * @param {Number} [tileIndex=-1] - Tile to use to render, untextured if -1
3145
+ * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
3146
+ * @param {Number} [angle=0] - Angle to rotate the particle
3147
+ */
2278
3148
  constructor(pos, tileIndex, tileSize, angle) { super(pos, new Vector2, tileIndex, tileSize, angle); }
2279
3149
 
3150
+ /** Render the particle, automatically called each frame, sorted by renderOrder */
2280
3151
  render()
2281
3152
  {
2282
3153
  // modulate size and color
@@ -2318,27 +3189,64 @@ class Particle extends EngineObject
2318
3189
  }
2319
3190
  }
2320
3191
  }
2321
- /*
2322
- LittleJS Medal System
2323
- - Tracks and displays medals
2324
- - Saves medals to local storage
2325
- - Newgrounds and OS13k integration
2326
- */
3192
+ /**
3193
+ * LittleJS Medal System
3194
+ * <br> - Tracks and displays medals
3195
+ * <br> - Saves medals to local storage
3196
+ * <br> - Newgrounds and OS13k integration
3197
+ * @namespace Medals
3198
+ */
2327
3199
 
2328
3200
  'use strict';
2329
3201
 
2330
- const medals = [], medalsDisplayQueue = [];
2331
- let medalsGameName, medalsPreventUnlock, medalsDisplayTimer, newgrounds;
3202
+ /** List of all medals
3203
+ * @memberof Medals */
3204
+ const medals = [];
3205
+
3206
+ /** Set to stop medals from being unlockable (like if cheats are enabled)
3207
+ * @memberof Medals */
3208
+ let medalsPreventUnlock;
3209
+
3210
+ /** This can used to enable Newgrounds functionality
3211
+ * @type {Newgrounds}
3212
+ * @memberof Medals */
3213
+ let newgrounds;
3214
+
3215
+ // Engine internal variables not exposed to documentation
3216
+ let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimer;
3217
+
3218
+ ///////////////////////////////////////////////////////////////////////////////
2332
3219
 
2333
- function medalsInit(gameName)
3220
+ /** Initialize medals with a save name used for storage
3221
+ * <br> - Checks if medals are unlocked
3222
+ * <br> - Call this after creating all medals
3223
+ * @param {String} saveName
3224
+ * @memberof Medals */
3225
+ function medalsInit(saveName)
2334
3226
  {
2335
3227
  // check if medals are unlocked
2336
- medalsGameName = gameName;
3228
+ medalsSaveName = saveName;
2337
3229
  debugMedals || medals.forEach(medal=> medal.unlocked = localStorage[medal.storageKey()]);
2338
3230
  }
2339
3231
 
3232
+ /**
3233
+ * Medal Object - Tracks an unlockable medal
3234
+ * @example
3235
+ * // create a medal
3236
+ * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
3237
+ *
3238
+ * // unlock the medal
3239
+ * medal_example.unlock();
3240
+ */
2340
3241
  class Medal
2341
3242
  {
3243
+ /** Create an medal object and adds it to the list of medals
3244
+ * @param {Number} id - The unique identifier of the medal
3245
+ * @param {String} name - Name of the medal
3246
+ * @param {String} [description] - Description of the medal
3247
+ * @param {String} [icon='🏆'] - Icon for the medal
3248
+ * @param {String} [src] - Image location for the medal
3249
+ */
2342
3250
  constructor(id, name, description='', icon='🏆', src)
2343
3251
  {
2344
3252
  ASSERT(id >= 0 && !medals[id]);
@@ -2357,26 +3265,25 @@ class Medal
2357
3265
  }
2358
3266
  }
2359
3267
 
3268
+ /** Unlocks a medal if not already unlocked */
2360
3269
  unlock()
2361
3270
  {
2362
3271
  if (medalsPreventUnlock || this.unlocked)
2363
3272
  return;
2364
3273
 
2365
3274
  // save the medal
2366
- ASSERT(medalsGameName); // game name must be set
3275
+ ASSERT(medalsSaveName); // game name must be set
2367
3276
  localStorage[this.storageKey()] = this.unlocked = 1;
2368
3277
  medalsDisplayQueue.push(this);
2369
3278
 
2370
3279
  // save for newgrounds and OS13K
2371
3280
  newgrounds && newgrounds.unlockMedal(this.id);
2372
- localStorage['OS13kTrophy,' + this.icon + ',' + medalsGameName + ',' + this.name] = this.description;
2373
- }
2374
-
2375
- storageKey()
2376
- {
2377
- return medalsGameName + '_medal_' + this.id;
3281
+ localStorage['OS13kTrophy,' + this.icon + ',' + medalsSaveName + ',' + this.name] = this.description;
2378
3282
  }
2379
3283
 
3284
+ /** Render a medal
3285
+ * @param {Number} [hidePercent=0] - How much to slide the medal off screen
3286
+ */
2380
3287
  render(hidePercent=0)
2381
3288
  {
2382
3289
  const context = overlayContext;
@@ -2397,25 +3304,36 @@ class Medal
2397
3304
 
2398
3305
  // draw the text
2399
3306
  context.textAlign = 'left';
2400
- context.font = '3em '+ defaultFont;
3307
+ context.font = '3em '+ fontDefault;
2401
3308
  context.fillText(this.name, x+medalDisplayIconSize+25, y+35);
2402
- context.font = '1.5em '+ defaultFont;
3309
+ context.font = '1.5em '+ fontDefault;
2403
3310
  context.restore(context.fillText(this.description, x+medalDisplayIconSize+25, y+70));
2404
3311
  }
2405
3312
 
3313
+ /** Render the icon for a medal
3314
+ * @param {Number} x - Screen space X position
3315
+ * @param {Number} y - Screen space Y position
3316
+ * @param {Number} [size=medalDisplayIconSize] - Screen space size
3317
+ */
2406
3318
  renderIcon(x, y, size=medalDisplayIconSize)
2407
3319
  {
2408
3320
  // draw the image or icon
2409
3321
  const context = overlayContext;
2410
3322
  context.textAlign = 'center';
2411
3323
  context.textBaseline = 'middle';
2412
- context.font = size*.6 + 'px '+ defaultFont;
3324
+ context.font = size*.6 + 'px '+ fontDefault;
2413
3325
  context.fillStyle = '#000';
2414
3326
  if (this.image)
2415
3327
  context.drawImage(this.image, x-size/2, y-size/2, size, size);
2416
3328
  else
2417
3329
  context.fillText(this.icon, x, y); // show icon if there is no image
2418
3330
  }
3331
+
3332
+ // Get local storage key used by the medal
3333
+ storageKey()
3334
+ {
3335
+ return medalsSaveName + '_medal_' + this.id;
3336
+ }
2419
3337
  }
2420
3338
 
2421
3339
  // engine automatically renders medals
@@ -2426,9 +3344,9 @@ function medalsRender()
2426
3344
 
2427
3345
  // update first medal in queue
2428
3346
  const medal = medalsDisplayQueue[0];
2429
- const time = realTime - medalsDisplayTimer;
3347
+ const time = timeReal - medalsDisplayTimer;
2430
3348
  if (!medalsDisplayTimer)
2431
- medalsDisplayTimer = realTime;
3349
+ medalsDisplayTimer = timeReal;
2432
3350
  else if (time > medalDisplayTime)
2433
3351
  medalsDisplayQueue.shift(medalsDisplayTimer = 0);
2434
3352
  else
@@ -2443,15 +3361,26 @@ function medalsRender()
2443
3361
  }
2444
3362
 
2445
3363
  ///////////////////////////////////////////////////////////////////////////////
2446
- // Newgrounds API wrapper
2447
3364
 
3365
+ /**
3366
+ * Newgrounds API wrapper object
3367
+ * @example
3368
+ * // create a newgrounds object, replace the app id and cipher with your own
3369
+ * const app_id = '53123:1ZuSTQ9l';
3370
+ * const cipher = 'enF0vGH@Mj/FRASKL23Q==';
3371
+ * newgrounds = new Newgrounds(app_id, cipher);
3372
+ */
2448
3373
  class Newgrounds
2449
3374
  {
3375
+ /** Create a newgrounds object
3376
+ * @param {Number} app_id - The newgrounds App ID
3377
+ * @param {String} [cipher] - The encryption Key (AES-128/Base64) */
2450
3378
  constructor(app_id, cipher)
2451
3379
  {
2452
3380
  ASSERT(!newgrounds && app_id);
2453
3381
  this.app_id = app_id;
2454
3382
  this.cipher = cipher;
3383
+ this.host = location ? location.hostname : '';
2455
3384
 
2456
3385
  // create an instance of CryptoJS for encrypted calls
2457
3386
  cipher && (this.cryptoJS = CryptoJS());
@@ -2473,11 +3402,16 @@ class Newgrounds
2473
3402
  if (medal)
2474
3403
  {
2475
3404
  // copy newgrounds medal data
2476
- medal.name = newgroundsMedal['name'];
3405
+ medal.image = new Image();
3406
+ medal.image.src = newgroundsMedal['icon'];
3407
+ medal.name = newgroundsMedal['name'];
2477
3408
  medal.description = newgroundsMedal['description'];
2478
- medal.unlocked = newgroundsMedal['unlocked'];
2479
- medal.image = new Image();
2480
- medal.image.src = newgroundsMedal['icon'];
3409
+ medal.unlocked = newgroundsMedal['unlocked'];
3410
+ medal.difficulty = newgroundsMedal['difficulty'];
3411
+ medal.value = newgroundsMedal['value'];
3412
+
3413
+ if (medal.value)
3414
+ medal.description = medal.description + ' (' + medal.value + ')';
2481
3415
  }
2482
3416
  }
2483
3417
 
@@ -2487,30 +3421,55 @@ class Newgrounds
2487
3421
  debugMedals && console.log(this.scoreboards);
2488
3422
  }
2489
3423
 
2490
- unlockMedal(id)
2491
- {
2492
- return this.call('Medal.unlock', {'id':id}, 1);
2493
- }
2494
-
2495
- postScore(id, value)
2496
- {
2497
- return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1);
2498
- }
2499
-
3424
+ /** Send message to unlock a medal by id
3425
+ * @param {Number} id - The medal id */
3426
+ unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, 1); }
3427
+
3428
+ /** Send message to post score
3429
+ * @param {Number} id - The scoreboard id
3430
+ * @param {Number} value - The score value */
3431
+ postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); }
3432
+
3433
+ /** Send message to log a view */
3434
+ logView() { return this.call('App.logView', {'host':this.host}, 1); }
3435
+
3436
+ /** Get scores from a scoreboard
3437
+ * @param {Number} id - The scoreboard id
3438
+ * @param {String} [user=0] - A user's id or name
3439
+ * @param {Number} [social=0] - If true, only social scores will be loaded
3440
+ * @param {Number} [skip=0] - Number of scores to skip before start
3441
+ * @param {Number} [limit=10] - Number of scores to include in the list
3442
+ * @return {Object} - The response JSON object
3443
+ */
2500
3444
  getScores(id, user=0, social=0, skip=0, limit=10)
2501
- {
2502
- return this.call('ScoreBoard.getScores',
2503
- {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit});
2504
- }
2505
-
3445
+ { return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
3446
+
3447
+ /** Send a message to call a component of the Newgrounds API
3448
+ * @param {String} component - Name of the component
3449
+ * @param {Object} [parameters=0] - Parameters to use for call
3450
+ * @param {Boolean} [async=0] - If true, wait for response before continuing (will cause stall)
3451
+ * @return {Object} - The response JSON object
3452
+ */
2506
3453
  call(component, parameters=0, async=0)
2507
3454
  {
3455
+ const call = {'component':component, 'parameters':parameters};
3456
+ if (this.cipher)
3457
+ {
3458
+ // encrypt using AES-128 Base64 with cryptoJS
3459
+ const cryptoJS = this.cryptoJS;
3460
+ const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
3461
+ const iv = cryptoJS['lib']['WordArray']['random'](16);
3462
+ const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
3463
+ call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
3464
+ call['parameters'] = 0;
3465
+ }
3466
+
2508
3467
  // build the input object
2509
3468
  const input =
2510
3469
  {
2511
3470
  'app_id': this.app_id,
2512
3471
  'session_id': this.session_id,
2513
- 'call': this.encryptCall({'component':component, 'parameters':parameters})
3472
+ 'call': call
2514
3473
  };
2515
3474
 
2516
3475
  // build post data
@@ -2525,21 +3484,6 @@ class Newgrounds
2525
3484
  debugMedals && console.log(xmlHttp.responseText);
2526
3485
  return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
2527
3486
  }
2528
-
2529
- encryptCall(call)
2530
- {
2531
- if (!this.cipher)
2532
- return call;
2533
-
2534
- // encrypt using AES-128 Base64 with cryptoJS
2535
- const cryptoJS = this.cryptoJS;
2536
- const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
2537
- const iv = cryptoJS['lib']['WordArray']['random'](16);
2538
- const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
2539
- call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
2540
- call['parameters'] = 0;
2541
- return call;
2542
- }
2543
3487
  }
2544
3488
 
2545
3489
  ///////////////////////////////////////////////////////////////////////////////
@@ -2547,21 +3491,40 @@ class Newgrounds
2547
3491
  // Copyright (c) 2009-2013 Jeff Mott Copyright (c) 2013-2016 Evan Vosberg
2548
3492
 
2549
3493
  const CryptoJS=()=>eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",...']charCodeAtUinyxpf',"for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h<t,o=o%h+(i?t:h-t)*(o>>17)-!i*t,f.map((n,x)=>(U=r[n]+=(i*h/2-r[n]<<13)/((C[n]+=C[n]<5)+1/20)>>13,a[x]+=y[x]*(i-t/h))),p=p*2+i)for(f='010202103203210431053105410642065206541'.split(t=0).map((n,x)=>(U=0,[...n].map((n,x)=>(U=U*997+(c[e-n]|0)|0)),h*32-1&U*997+p+!!A*129)*12+x);o<h*32;o=o*64|M.charCodeAt(d++)&63);for(C=String.fromCharCode(...c);r=/[\0-#?@\\\\~]/.exec(C);)with(C.split(r))C=join(shift());return C")([],[],1<<17,[0,0,0,0,0,0,0,0,0,0,0,0],new Uint16Array(51e6).fill(1<<15),new Uint8Array(51e6),0,0,0,0));
2550
- /*
2551
- LittleJS WebGL Interface
2552
- - All webgl used by the engine is wrapped up here
2553
- - Can be disabled with glEnable to revert to 2D canvas rendering
2554
- - Batches sprite rendering on GPU for incredibly fast performance
2555
- - Sprite transform math is done in the shader where possible
2556
- - For normal stuff you won't need to call any functions in this file
2557
- - For advanced stuff there are helper functions to create shaders, textures, etc
2558
- */
3494
+ /**
3495
+ * LittleJS WebGL Interface
3496
+ * <br> - All webgl used by the engine is wrapped up here
3497
+ * <br> - Can be disabled with glEnable to revert to 2D canvas rendering
3498
+ * <br> - Batches sprite rendering on GPU for incredibly fast performance
3499
+ * <br> - Sprite transform math is done in the shader where possible
3500
+ * <br> - For normal stuff you won't need to call any functions in this file
3501
+ * <br> - For advanced stuff there are helper functions to create shaders, textures, etc
3502
+ * @namespace WebGL
3503
+ */
2559
3504
 
2560
3505
  'use strict';
2561
3506
 
2562
- let glCanvas, glContext, glTileTexture, glActiveTexture, glShader,
2563
- glPositionData, glColorData, glBatchCount, glDirty, glAdditive;
3507
+ /** The WebGL canvas which appears above the main canvas and below the overlay canvas
3508
+ * @type {HTMLCanvasElement}
3509
+ * @memberof WebGL */
3510
+ let glCanvas;
3511
+
3512
+ /** 2d context for glCanvas
3513
+ * @type {WebGLRenderingContext}
3514
+ * @memberof WebGL */
3515
+ let glContext;
3516
+
3517
+ /** Main tile sheet texture automatically loaded by engine
3518
+ * @type {WebGLTexture}
3519
+ * @memberof WebGL */
3520
+ let glTileTexture;
3521
+
3522
+ // WebGL internal variables not exposed to documentation
3523
+ let glActiveTexture, glShader, glPositionData, glColorData, glBatchCount, glBatchAdditive, glDirty, glAdditive;
2564
3524
 
3525
+ ///////////////////////////////////////////////////////////////////////////////
3526
+
3527
+ // Init WebGL, called automatically by the engine
2565
3528
  function glInit()
2566
3529
  {
2567
3530
  if (!glEnable) return;
@@ -2573,7 +3536,7 @@ function glInit()
2573
3536
 
2574
3537
  if (glOverlay)
2575
3538
  {
2576
- // firefox is much faster without copying the gl buffer so we just overlay it with some tradeoffs
3539
+ // some browsers are much faster without copying the gl buffer so we just overlay it instead
2577
3540
  document.body.appendChild(glCanvas);
2578
3541
  glCanvas.style = mainCanvas.style.cssText;
2579
3542
  }
@@ -2624,20 +3587,21 @@ function glInit()
2624
3587
  initVertexAttribArray('b', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
2625
3588
  }
2626
3589
 
3590
+ /** Set the WebGl blend mode, normally you should call setBlendMode instead
3591
+ * @param {Boolean} [additive=0]
3592
+ * @memberof WebGL */
2627
3593
  function glSetBlendMode(additive)
2628
3594
  {
2629
3595
  if (!glEnable) return;
2630
3596
 
2631
- if (additive != glAdditive)
2632
- glFlush();
2633
-
2634
3597
  // setup blending
2635
3598
  glAdditive = additive;
2636
- const destBlend = additive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
2637
- glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
2638
- glContext.enable(gl_BLEND);
2639
3599
  }
2640
3600
 
3601
+ /** Set the WebGl texture, not normally necessary unless multiple tile sheets are used
3602
+ * <br> - This may also flush the gl buffer resulting in more draw calls and worse performance
3603
+ * @param {WebGLTexture} [texture=glTileTexture]
3604
+ * @memberof WebGL */
2641
3605
  function glSetTexture(texture=glTileTexture)
2642
3606
  {
2643
3607
  if (!glEnable) return;
@@ -2648,6 +3612,11 @@ function glSetTexture(texture=glTileTexture)
2648
3612
  glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = texture);
2649
3613
  }
2650
3614
 
3615
+ /** Compile WebGL shader of the given type, will throw errors if in debug mode
3616
+ * @param {String} source
3617
+ * @param type
3618
+ * @return {WebGLShader}
3619
+ * @memberof WebGL */
2651
3620
  function glCompileShader(source, type)
2652
3621
  {
2653
3622
  if (!glEnable) return;
@@ -2663,6 +3632,11 @@ function glCompileShader(source, type)
2663
3632
  return shader;
2664
3633
  }
2665
3634
 
3635
+ /** Create WebGL program with given shaders
3636
+ * @param {WebGLShader} vsSource
3637
+ * @param {WebGLShader} fsSource
3638
+ * @return {WebGLProgram}
3639
+ * @memberof WebGL */
2666
3640
  function glCreateProgram(vsSource, fsSource)
2667
3641
  {
2668
3642
  if (!glEnable) return;
@@ -2679,6 +3653,12 @@ function glCreateProgram(vsSource, fsSource)
2679
3653
  return program;
2680
3654
  }
2681
3655
 
3656
+ /** Create WebGL buffer
3657
+ * @param bufferType
3658
+ * @param size
3659
+ * @param usage
3660
+ * @return {WebGLBuffer}
3661
+ * @memberof WebGL */
2682
3662
  function glCreateBuffer(bufferType, size, usage)
2683
3663
  {
2684
3664
  if (!glEnable) return;
@@ -2690,15 +3670,19 @@ function glCreateBuffer(bufferType, size, usage)
2690
3670
  return buffer;
2691
3671
  }
2692
3672
 
3673
+ /** Create WebGL texture from an image and set the texture settings
3674
+ * @param {Image} image
3675
+ * @return {WebGLTexture}
3676
+ * @memberof WebGL */
2693
3677
  function glCreateTexture(image)
2694
3678
  {
2695
- if (!glEnable) return;
3679
+ if (!glEnable || !image || !image.width) return;
2696
3680
 
2697
3681
  // build the texture
2698
3682
  const texture = glContext.createTexture();
2699
3683
  glContext.bindTexture(gl_TEXTURE_2D, texture);
2700
3684
  glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
2701
-
3685
+
2702
3686
  // use point filtering for pixelated rendering
2703
3687
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, pixelated ? gl_NEAREST : gl_LINEAR);
2704
3688
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, pixelated ? gl_NEAREST : gl_LINEAR);
@@ -2707,6 +3691,7 @@ function glCreateTexture(image)
2707
3691
  return texture;
2708
3692
  }
2709
3693
 
3694
+ // called automatically by engine before render
2710
3695
  function glPreRender(width, height)
2711
3696
  {
2712
3697
  if (!glEnable) return;
@@ -2734,24 +3719,33 @@ function glPreRender(width, height)
2734
3719
  );
2735
3720
  }
2736
3721
 
3722
+ /** Draw all sprites and clear out the buffer, called automatically by the system whenever necessary
3723
+ * @memberof WebGL */
2737
3724
  function glFlush()
2738
3725
  {
2739
3726
  if (!glEnable || !glBatchCount) return;
2740
3727
 
3728
+ const destBlend = glBatchAdditive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
3729
+ glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
3730
+ glContext.enable(gl_BLEND);
3731
+
2741
3732
  // draw all the sprites in the batch and reset the buffer
2742
3733
  glContext.bufferSubData(gl_ARRAY_BUFFER, 0,
2743
3734
  glPositionData.subarray(0, glBatchCount * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT));
2744
3735
  glContext.drawArrays(gl_TRIANGLES, 0, glBatchCount * gl_VERTICES_PER_QUAD);
2745
3736
  glBatchCount = 0;
3737
+ glBatchAdditive = glAdditive;
2746
3738
  }
2747
3739
 
3740
+ /** Draw any sprites still in the buffer, copy to main canvas and clear
3741
+ * @param {CanvasRenderingContext2D} context
3742
+ * @param {Boolean} [forceDraw=0]
3743
+ * @memberof WebGL */
2748
3744
  function glCopyToContext(context, forceDraw)
2749
3745
  {
2750
3746
  if (!glEnable || !glDirty) return;
2751
3747
 
2752
- // draw any sprites still in the buffer, copy to main canvas and clear
2753
3748
  glFlush();
2754
-
2755
3749
  if (!glOverlay || forceDraw)
2756
3750
  {
2757
3751
  // do not draw/clear in overlay mode because the canvas is visible
@@ -2760,12 +3754,25 @@ function glCopyToContext(context, forceDraw)
2760
3754
  }
2761
3755
  }
2762
3756
 
2763
- function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgba=0xffffffff, rgbaAdditive=0x00000000)
3757
+ /** Add a sprite to the gl draw list, used by all gl draw functions
3758
+ * @param x
3759
+ * @param y
3760
+ * @param sizeX
3761
+ * @param sizeY
3762
+ * @param [angle=0]
3763
+ * @param [uv0X=0]
3764
+ * @param [uv0Y=0]
3765
+ * @param [uv1X=1]
3766
+ * @param [uv1Y=1]
3767
+ * @param [rgba=0xffffffff]
3768
+ * @param [rgbaAdditive=0]
3769
+ * @memberof WebGL */
3770
+ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgba=0xffffffff, rgbaAdditive=0)
2764
3771
  {
2765
3772
  if (!glEnable) return;
2766
-
2767
- // flush if there is no room for more verts
2768
- if (glBatchCount == gl_MAX_BATCH)
3773
+
3774
+ // flush if there is no room for more verts or if different blend mode
3775
+ if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
2769
3776
  glFlush();
2770
3777
 
2771
3778
  // setup 2 triangles to form a quad
@@ -2774,69 +3781,45 @@ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgb
2774
3781
 
2775
3782
  // vertex 0
2776
3783
  glPositionData[offset++] = angle;
2777
- glPositionData[offset++] = x;
2778
- glPositionData[offset++] = y;
2779
- glPositionData[offset++] = -sizeX;
2780
- glPositionData[offset++] = -sizeY;
2781
- glPositionData[offset++] = uv0X;
2782
- glPositionData[offset++] = uv1Y;
2783
- glColorData[offset++] = rgba;
2784
- glColorData[offset++] = rgbaAdditive;
3784
+ glPositionData[offset++] = x; glPositionData[offset++] = y;
3785
+ glPositionData[offset++] = -sizeX; glPositionData[offset++] = -sizeY;
3786
+ glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
3787
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
2785
3788
 
2786
3789
  // vertex 1
2787
3790
  glPositionData[offset++] = angle;
2788
- glPositionData[offset++] = x;
2789
- glPositionData[offset++] = y;
2790
- glPositionData[offset++] = sizeX;
2791
- glPositionData[offset++] = sizeY;
2792
- glPositionData[offset++] = uv1X;
2793
- glPositionData[offset++] = uv0Y;
2794
- glColorData[offset++] = rgba;
2795
- glColorData[offset++] = rgbaAdditive;
3791
+ glPositionData[offset++] = x; glPositionData[offset++] = y;
3792
+ glPositionData[offset++] = sizeX; glPositionData[offset++] = sizeY;
3793
+ glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
3794
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
2796
3795
 
2797
3796
  // vertex 2
2798
3797
  glPositionData[offset++] = angle;
2799
- glPositionData[offset++] = x;
2800
- glPositionData[offset++] = y;
2801
- glPositionData[offset++] = -sizeX;
2802
- glPositionData[offset++] = sizeY;
2803
- glPositionData[offset++] = uv0X;
2804
- glPositionData[offset++] = uv0Y;
2805
- glColorData[offset++] = rgba;
2806
- glColorData[offset++] = rgbaAdditive;
3798
+ glPositionData[offset++] = x; glPositionData[offset++] = y;
3799
+ glPositionData[offset++] = -sizeX; glPositionData[offset++] = sizeY;
3800
+ glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
3801
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
2807
3802
 
2808
3803
  // vertex 0
2809
3804
  glPositionData[offset++] = angle;
2810
- glPositionData[offset++] = x;
2811
- glPositionData[offset++] = y;
2812
- glPositionData[offset++] = -sizeX;
2813
- glPositionData[offset++] = -sizeY;
2814
- glPositionData[offset++] = uv0X;
2815
- glPositionData[offset++] = uv1Y;
2816
- glColorData[offset++] = rgba;
2817
- glColorData[offset++] = rgbaAdditive;
3805
+ glPositionData[offset++] = x; glPositionData[offset++] = y;
3806
+ glPositionData[offset++] = -sizeX; glPositionData[offset++] = -sizeY;
3807
+ glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
3808
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
2818
3809
 
2819
3810
  // vertex 3
2820
3811
  glPositionData[offset++] = angle;
2821
- glPositionData[offset++] = x;
2822
- glPositionData[offset++] = y;
2823
- glPositionData[offset++] = sizeX;
2824
- glPositionData[offset++] = -sizeY;
2825
- glPositionData[offset++] = uv1X;
2826
- glPositionData[offset++] = uv1Y;
2827
- glColorData[offset++] = rgba;
2828
- glColorData[offset++] = rgbaAdditive;
3812
+ glPositionData[offset++] = x; glPositionData[offset++] = y;
3813
+ glPositionData[offset++] = sizeX; glPositionData[offset++] = -sizeY;
3814
+ glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
3815
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
2829
3816
 
2830
3817
  // vertex 1
2831
3818
  glPositionData[offset++] = angle;
2832
- glPositionData[offset++] = x;
2833
- glPositionData[offset++] = y;
2834
- glPositionData[offset++] = sizeX;
2835
- glPositionData[offset++] = sizeY;
2836
- glPositionData[offset++] = uv1X;
2837
- glPositionData[offset++] = uv0Y;
2838
- glColorData[offset++] = rgba;
2839
- glColorData[offset++] = rgbaAdditive;
3819
+ glPositionData[offset++] = x; glPositionData[offset++] = y;
3820
+ glPositionData[offset++] = sizeX; glPositionData[offset++] = sizeY;
3821
+ glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
3822
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
2840
3823
  }
2841
3824
 
2842
3825
  ///////////////////////////////////////////////////////////////////////////////
@@ -2871,3 +3854,273 @@ gl_VERTICES_PER_QUAD = 6,
2871
3854
  gl_INDICIES_PER_VERT = 9,
2872
3855
  gl_MAX_BATCH = 1<<16,
2873
3856
  gl_VERTEX_BYTE_STRIDE = 4 + (4 * 2) * 3 + (4) * 2; // float + vec2 * 3 + (char * 4) * 2
3857
+ /*
3858
+ LittleJS - The Tiny JavaScript Game Engine That Can!
3859
+ MIT License - Copyright 2021 Frank Force
3860
+
3861
+ Engine Features
3862
+ - Object oriented system with base class engine object
3863
+ - Base class object handles update, physics, collision, rendering, etc
3864
+ - Engine helper classes and functions like Vector2, Color, and Timer
3865
+ - Super fast rendering system for tile sheets
3866
+ - Sound effects audio with zzfx and music with zzfxm
3867
+ - Input processing system with gamepad and touchscreen support
3868
+ - Tile layer rendering and collision system
3869
+ - Particle effect system
3870
+ - Medal system tracks and displays achievements
3871
+ - Debug tools and debug rendering system
3872
+ - Call engineInit() to start it up!
3873
+ */
3874
+
3875
+ 'use strict';
3876
+
3877
+ /** Name of engine */
3878
+ const engineName = 'LittleJS';
3879
+
3880
+ /** Version of engine */
3881
+ const engineVersion = '1.1.8';
3882
+
3883
+ /** Frames per second to update objects
3884
+ * @default */
3885
+ const FPS = 60;
3886
+
3887
+ /** How many seconds each frame lasts, engine uses a fixed time step
3888
+ * @default 1/60 */
3889
+ const timeDelta = 1/FPS;
3890
+
3891
+ /** Array containing all engine objects */
3892
+ let engineObjects = [];
3893
+
3894
+ /** Array containing only objects that are set to collide with other objects (for optimization) */
3895
+ let engineCollideObjects = [];
3896
+
3897
+ /** Current update frame, used to calculate time */
3898
+ let frame = 0;
3899
+
3900
+ /** Current engine time since start in seconds, derived from frame */
3901
+ let time = 0;
3902
+
3903
+ /** Actual clock time since start in seconds (not affected by pause or frame rate clamping) */
3904
+ let timeReal = 0;
3905
+
3906
+ /** Is the game paused? Causes time and objects to not be updated. */
3907
+ let paused = 0;
3908
+
3909
+ // Engine internal variables not exposed to documentation
3910
+ let frameTimeLastMS = 0, frameTimeBufferMS = 0, debugFPS = 0,
3911
+ shrinkTilesX, shrinkTilesY, drawCount, tileImageSize, tileImageSizeInverse;
3912
+
3913
+ ///////////////////////////////////////////////////////////////////////////////
3914
+
3915
+ /** Start up LittleJS engine with your callback functions
3916
+ * @param {Function} gameInit - Called once after the engine starts up, setup the game
3917
+ * @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state
3918
+ * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
3919
+ * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
3920
+ * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
3921
+ * @param {String} [tileImageSource] - Tile image to use, everything starts when the image is finished loading
3922
+ */
3923
+ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, tileImageSource)
3924
+ {
3925
+ // init engine when tiles load or fail to load
3926
+ tileImage.onerror = tileImage.onload = ()=>
3927
+ {
3928
+ // save tile image info
3929
+ tileImageSizeInverse = vec2(1).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
3930
+ debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
3931
+ shrinkTilesX = tileBleedShrinkFix/tileImageSize.x;
3932
+ shrinkTilesY = tileBleedShrinkFix/tileImageSize.y;
3933
+
3934
+ // setup html
3935
+ document.body.appendChild(mainCanvas = document.createElement('canvas'));
3936
+ document.body.style = 'margin:0;overflow:hidden;background:#000';
3937
+ mainCanvas.style = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)' +
3938
+ (pixelated ? ';image-rendering:crisp-edges;image-rendering:pixelated' : ''); // pixelated rendering
3939
+ mainContext = mainCanvas.getContext('2d');
3940
+
3941
+ // init stuff and start engine
3942
+ debugInit();
3943
+ glInit();
3944
+
3945
+ // create overlay canvas for hud to appear above gl canvas
3946
+ document.body.appendChild(overlayCanvas = document.createElement('canvas'));
3947
+ overlayCanvas.style = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
3948
+ overlayContext = overlayCanvas.getContext('2d');
3949
+
3950
+ gameInit();
3951
+ engineUpdate();
3952
+ };
3953
+
3954
+ // main update loop
3955
+ const engineUpdate = (frameTimeMS=0)=>
3956
+ {
3957
+ requestAnimationFrame(engineUpdate);
3958
+
3959
+ // update time keeping
3960
+ let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
3961
+ frameTimeLastMS = frameTimeMS;
3962
+ if (debug || showWatermark)
3963
+ debugFPS = lerp(.05, 1e3/(frameTimeDeltaMS||1), debugFPS);
3964
+ if (debug)
3965
+ frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
3966
+ timeReal += frameTimeDeltaMS / 1e3;
3967
+ frameTimeBufferMS = min(frameTimeBufferMS + !paused * frameTimeDeltaMS, 50); // clamp incase of slow framerate
3968
+
3969
+ if (paused)
3970
+ {
3971
+ // do post update even when paused
3972
+ inputUpdate();
3973
+ debugUpdate();
3974
+ gameUpdatePost();
3975
+ inputUpdatePost();
3976
+ }
3977
+ else
3978
+ {
3979
+ // apply time delta smoothing, improves smoothness of framerate in some browsers
3980
+ let deltaSmooth = 0;
3981
+ if (frameTimeBufferMS < 0 && frameTimeBufferMS > -9)
3982
+ {
3983
+ // force an update each frame if time is close enough (not just a fast refresh rate)
3984
+ deltaSmooth = frameTimeBufferMS;
3985
+ frameTimeBufferMS = 0;
3986
+ }
3987
+
3988
+ // update multiple frames if necessary in case of slow framerate
3989
+ for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / FPS)
3990
+ {
3991
+ // update game and objects
3992
+ inputUpdate();
3993
+ gameUpdate();
3994
+ engineObjectsUpdate();
3995
+
3996
+ // do post update
3997
+ debugUpdate();
3998
+ gameUpdatePost();
3999
+ inputUpdatePost();
4000
+ }
4001
+
4002
+ // add the time smoothing back in
4003
+ frameTimeBufferMS += deltaSmooth;
4004
+ }
4005
+
4006
+ if (fixedSize.x)
4007
+ {
4008
+ // clear set fixed size
4009
+ mainCanvas.width = fixedSize.x;
4010
+ mainCanvas.height = fixedSize.y;
4011
+
4012
+ // fit to window by adding space on top or bottom if necessary
4013
+ const aspect = innerWidth / innerHeight;
4014
+ const fixedAspect = mainCanvas.width / mainCanvas.height;
4015
+ mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
4016
+ mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
4017
+ if (glCanvas)
4018
+ {
4019
+ glCanvas.style.width = mainCanvas.style.width;
4020
+ glCanvas.style.height = mainCanvas.style.height;
4021
+ }
4022
+ }
4023
+ else
4024
+ {
4025
+ // clear and set size to same as window
4026
+ mainCanvas.width = min(innerWidth, maxSize.x);
4027
+ mainCanvas.height = min(innerHeight, maxSize.y);
4028
+ }
4029
+
4030
+ // save canvas size and clear overlay canvas
4031
+ mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
4032
+ mainContext.imageSmoothingEnabled = !pixelated; // disable smoothing for pixel art
4033
+
4034
+ // render sort then render while removing destroyed objects
4035
+ glPreRender(mainCanvas.width, mainCanvas.height);
4036
+ gameRender();
4037
+ engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
4038
+ for (const o of engineObjects)
4039
+ o.destroyed || o.render();
4040
+ gameRenderPost();
4041
+ medalsRender();
4042
+ debugRender();
4043
+ glCopyToContext(mainContext);
4044
+
4045
+ if (showWatermark)
4046
+ {
4047
+ // update fps
4048
+ overlayContext.textAlign = 'right';
4049
+ overlayContext.textBaseline = 'top';
4050
+ overlayContext.font = '1em monospace';
4051
+ overlayContext.fillStyle = '#000';
4052
+ const text = engineName + ' ' + 'v' + engineVersion + ' / '
4053
+ + drawCount + ' / ' + engineObjects.length + ' / ' + debugFPS.toFixed(1);
4054
+ overlayContext.fillText(text, mainCanvas.width-3, 3);
4055
+ overlayContext.fillStyle = '#fff';
4056
+ overlayContext.fillText(text, mainCanvas.width-2, 2);
4057
+ drawCount = 0;
4058
+ }
4059
+ }
4060
+
4061
+ // set tile image source to load the image and start the engine
4062
+ tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
4063
+ }
4064
+
4065
+
4066
+ ///////////////////////////////////////////////////////////////////////////////
4067
+
4068
+ /** Calls update on each engine object (recursively if child), removes destroyed objects, and updated time */
4069
+ function engineObjectsUpdate()
4070
+ {
4071
+ // recursive object update
4072
+ const updateObject = (o)=>
4073
+ {
4074
+ if (!o.destroyed)
4075
+ {
4076
+ o.update();
4077
+ for (const child of o.children)
4078
+ updateObject(child);
4079
+ }
4080
+ }
4081
+ for (const o of engineObjects)
4082
+ o.parent || updateObject(o);
4083
+
4084
+ // remove destroyed objects
4085
+ engineObjects = engineObjects.filter(o=>!o.destroyed);
4086
+ engineCollideObjects = engineCollideObjects.filter(o=>!o.destroyed);
4087
+
4088
+ // increment frame and update time
4089
+ time = ++frame / FPS;
4090
+ }
4091
+
4092
+ /** Detroy and remove all objects that are not persistent or descendants of a persistent object */
4093
+ function engineObjectsDestroy()
4094
+ {
4095
+ for (const o of engineObjects)
4096
+ o.persistent || o.parent || o.destroy();
4097
+ engineObjects = engineObjects.filter(o=>!o.destroyed);
4098
+ }
4099
+
4100
+ /** Triggers a callback for each object within a given area
4101
+ * @param {Vector2} [pos] - Center of test area
4102
+ * @param {Number} [size] - Radius of circle if float, rectangle size if Vector2
4103
+ * @param {Function} [callbackFunction] - Calls this function on every object that passes the test
4104
+ * @param {Array} [objects=engineObjects] - List of objects to check */
4105
+ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
4106
+ {
4107
+ if (!pos)
4108
+ {
4109
+ // all objects
4110
+ for (const o of objects)
4111
+ callbackFunction(o);
4112
+ }
4113
+ else if (size.x != undefined)
4114
+ {
4115
+ // aabb test
4116
+ for (const o of objects)
4117
+ isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
4118
+ }
4119
+ else
4120
+ {
4121
+ // circle test
4122
+ const sizeSquared = size*size;
4123
+ for (const o of objects)
4124
+ pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
4125
+ }
4126
+ }