littlejsengine 1.6.0 → 1.6.6

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 (56) hide show
  1. package/README.md +138 -34
  2. package/build/littlejs.d.ts +104 -92
  3. package/build/littlejs.esm.js +414 -342
  4. package/build/littlejs.esm.min.js +1 -1
  5. package/build/littlejs.js +365 -296
  6. package/build/littlejs.min.js +1 -1
  7. package/build/littlejs.release.js +359 -291
  8. package/examples/breakout/game.js +5 -0
  9. package/examples/breakout/gameObjects.js +41 -48
  10. package/examples/breakout/index.html +3 -3
  11. package/examples/breakoutTutorial/README.md +514 -0
  12. package/examples/breakoutTutorial/game.js +181 -0
  13. package/examples/breakoutTutorial/images/1.png +0 -0
  14. package/examples/breakoutTutorial/images/10.png +0 -0
  15. package/examples/breakoutTutorial/images/11.png +0 -0
  16. package/examples/breakoutTutorial/images/2.png +0 -0
  17. package/examples/breakoutTutorial/images/3.png +0 -0
  18. package/examples/breakoutTutorial/images/4.png +0 -0
  19. package/examples/breakoutTutorial/images/5.png +0 -0
  20. package/examples/breakoutTutorial/images/6.png +0 -0
  21. package/examples/breakoutTutorial/images/7.png +0 -0
  22. package/examples/breakoutTutorial/images/8.png +0 -0
  23. package/examples/breakoutTutorial/images/9.png +0 -0
  24. package/examples/breakoutTutorial/index.html +10 -0
  25. package/examples/empty/game.js +10 -0
  26. package/examples/favicon.png +0 -0
  27. package/examples/module/index.html +1 -1
  28. package/examples/particles/index.html +1 -1
  29. package/examples/platformer/gameLevel.js +1 -1
  30. package/examples/platformer/gamePlayer.js +1 -1
  31. package/examples/platformer/index.html +6 -6
  32. package/examples/puzzle/game.js +1 -1
  33. package/examples/puzzle/index.html +2 -2
  34. package/examples/starter/build.bat +3 -2
  35. package/examples/starter/game.js +9 -9
  36. package/examples/starter/index.html +13 -13
  37. package/examples/stress/index.html +1 -1
  38. package/examples/typescript/game.js +89 -89
  39. package/examples/typescript/game.ts +10 -10
  40. package/examples/typescript/index.html +1 -1
  41. package/package.json +4 -4
  42. package/src/engine.js +29 -31
  43. package/src/engineAudio.js +53 -29
  44. package/src/engineBuild.bat +3 -1
  45. package/src/engineDebug.js +25 -24
  46. package/src/engineDraw.js +43 -39
  47. package/src/engineExport.js +49 -46
  48. package/src/engineInput.js +83 -74
  49. package/src/engineMedals.js +31 -8
  50. package/src/engineObject.js +25 -25
  51. package/src/engineParticles.js +3 -6
  52. package/src/engineRelease.js +19 -19
  53. package/src/engineSettings.js +4 -4
  54. package/src/engineTileLayer.js +18 -14
  55. package/src/engineUtilities.js +43 -34
  56. package/src/engineWebGL.js +8 -8
package/build/littlejs.js CHANGED
@@ -1,15 +1,10 @@
1
- /*
2
- LittleJS - Debug Build
3
- MIT License - Copyright 2021 Frank Force
4
- */
5
-
6
1
  /**
7
2
  * LittleJS Debug System
8
- * <br> - Press ~ to show debug overlay with mouse pick
9
- * <br> - Number keys toggle debug functions
10
- * <br> - +/- apply time scale
11
- * <br> - Debug primitive rendering
12
- * <br> - Save a 2d canvas as an image
3
+ * - Press ~ to show debug overlay with mouse pick
4
+ * - Number keys toggle debug functions
5
+ * - +/- apply time scale
6
+ * - Debug primitive rendering
7
+ * - Save a 2d canvas as an image
13
8
  * @namespace Debug
14
9
  */
15
10
 
@@ -45,6 +40,12 @@ let showWatermark = 1;
45
40
  * @memberof Debug */
46
41
  let godMode = 0;
47
42
 
43
+ /** Key code used to toggle debug mode, Esc by default
44
+ * @type {Boolean}
45
+ * @default
46
+ * @memberof Debug */
47
+ let debugKey = 27;
48
+
48
49
  // Engine internal variables not exposed to documentation
49
50
  let debugPrimitives = [], debugOverlay = 0, debugPhysics = 0, debugRaycast = 0,
50
51
  debugParticles = 0, debugGamepads = 0, debugMedals = 0, debugTakeScreenshot, downloadLink;
@@ -56,7 +57,7 @@ debugParticles = 0, debugGamepads = 0, debugMedals = 0, debugTakeScreenshot, dow
56
57
  * @param {Boolean} assertion
57
58
  * @param {Object} output
58
59
  * @memberof Debug */
59
- const ASSERT = enableAsserts ? (...assert)=> console.assert(...assert) : ()=>{};
60
+ function ASSERT(...assert) { enableAsserts && console.assert(...assert); }
60
61
 
61
62
  /** Draw a debug rectangle in world space
62
63
  * @param {Vector2} pos
@@ -66,7 +67,7 @@ const ASSERT = enableAsserts ? (...assert)=> console.assert(...assert) : ()=>{};
66
67
  * @param {Number} [angle=0]
67
68
  * @param {Boolean} [fill=false]
68
69
  * @memberof Debug */
69
- const debugRect = (pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)=>
70
+ function debugRect(pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)
70
71
  {
71
72
  ASSERT(typeof color == 'string'); // pass in regular html strings as colors
72
73
  debugPrimitives.push({pos, size:vec2(size), color, time:new Timer(time), angle, fill});
@@ -79,7 +80,7 @@ const debugRect = (pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)=
79
80
  * @param {Number} [time=0]
80
81
  * @param {Boolean} [fill=false]
81
82
  * @memberof Debug */
82
- const debugCircle = (pos, radius=0, color='#fff', time=0, fill=false)=>
83
+ function debugCircle(pos, radius=0, color='#fff', time=0, fill=false)
83
84
  {
84
85
  ASSERT(typeof color == 'string'); // pass in regular html strings as colors
85
86
  debugPrimitives.push({pos, size:radius, color, time:new Timer(time), angle:0, fill});
@@ -91,7 +92,7 @@ const debugCircle = (pos, radius=0, color='#fff', time=0, fill=false)=>
91
92
  * @param {Number} [time=0]
92
93
  * @param {Number} [angle=0]
93
94
  * @memberof Debug */
94
- const debugPoint = (pos, color, time, angle)=> debugRect(pos, 0, color, time, angle);
95
+ function debugPoint(pos, color, time, angle) {debugRect(pos, 0, color, time, angle);}
95
96
 
96
97
  /** Draw a debug line in world space
97
98
  * @param {Vector2} posA
@@ -100,7 +101,7 @@ const debugPoint = (pos, color, time, angle)=> debugRect(pos, 0, color, time, an
100
101
  * @param {Number} [thickness=.1]
101
102
  * @param {Number} [time=0]
102
103
  * @memberof Debug */
103
- const debugLine = (posA, posB, color, thickness=.1, time)=>
104
+ function debugLine(posA, posB, color, thickness=.1, time)
104
105
  {
105
106
  const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
106
107
  const size = vec2(thickness, halfDelta.length()*2);
@@ -114,7 +115,7 @@ const debugLine = (posA, posB, color, thickness=.1, time)=>
114
115
  * @param {Vector2} sizeB
115
116
  * @param {String} [color='#fff']
116
117
  * @memberof Debug */
117
- const debugAABB = (pA, sA, pB, sB, color)=>
118
+ function debugAABB(pA, sA, pB, sB, color)
118
119
  {
119
120
  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));
120
121
  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));
@@ -130,7 +131,7 @@ const debugAABB = (pA, sA, pB, sB, color)=>
130
131
  * @param {Number} [angle=0]
131
132
  * @param {String} [font='monospace']
132
133
  * @memberof Debug */
133
- const debugText = (text, pos, size=1, color='#fff', time=0, angle=0, font='monospace')=>
134
+ function debugText(text, pos, size=1, color='#fff', time=0, angle=0, font='monospace')
134
135
  {
135
136
  ASSERT(typeof color == 'string'); // pass in regular html strings as colors
136
137
  debugPrimitives.push({text, pos, size, color, time:new Timer(time), angle, font});
@@ -138,13 +139,13 @@ const debugText = (text, pos, size=1, color='#fff', time=0, angle=0, font='monos
138
139
 
139
140
  /** Clear all debug primitives in the list
140
141
  * @memberof Debug */
141
- const debugClear = ()=> debugPrimitives = [];
142
+ function debugClear() { debugPrimitives = []; }
142
143
 
143
144
  /** Save a canvas to disk
144
145
  * @param {HTMLCanvasElement} canvas
145
146
  * @param {String} [filename]
146
147
  * @memberof Debug */
147
- const debugSaveCanvas = (canvas, filename = engineName + '.png') =>
148
+ function debugSaveCanvas(canvas, filename = engineName + '.png')
148
149
  {
149
150
  downloadLink.download = 'screenshot.png';
150
151
  downloadLink.href = canvas.toDataURL('image/png').replace('image/png','image/octet-stream');
@@ -154,19 +155,19 @@ const debugSaveCanvas = (canvas, filename = engineName + '.png') =>
154
155
  ///////////////////////////////////////////////////////////////////////////////
155
156
  // Engine debug function (called automatically)
156
157
 
157
- const debugInit = ()=>
158
+ function debugInit()
158
159
  {
159
160
  // create link for saving screenshots
160
161
  document.body.appendChild(downloadLink = document.createElement('a'));
161
162
  downloadLink.style.display = 'none';
162
163
  }
163
164
 
164
- const debugUpdate = ()=>
165
+ function debugUpdate()
165
166
  {
166
167
  if (!debug)
167
168
  return;
168
169
 
169
- if (keyWasPressed(192)) // ~
170
+ if (keyWasPressed(debugKey)) // Esc
170
171
  debugOverlay = !debugOverlay;
171
172
  if (debugOverlay)
172
173
  {
@@ -189,7 +190,7 @@ const debugUpdate = ()=>
189
190
  }
190
191
  }
191
192
 
192
- const debugRender = ()=>
193
+ function debugRender()
193
194
  {
194
195
  glCopyToContext(mainContext);
195
196
 
@@ -351,7 +352,7 @@ const debugRender = ()=>
351
352
  overlayContext.fillText('Time: ' + formatTime(time), x, y += h);
352
353
  overlayContext.fillText('---------', x, y += h);
353
354
  overlayContext.fillStyle = '#f00';
354
- overlayContext.fillText('~: Debug Overlay', x, y += h);
355
+ overlayContext.fillText('ESC: Debug Overlay', x, y += h);
355
356
  overlayContext.fillStyle = debugPhysics ? '#f00' : '#fff';
356
357
  overlayContext.fillText('1: Debug Physics', x, y += h);
357
358
  overlayContext.fillStyle = debugParticles ? '#f00' : '#fff';
@@ -393,10 +394,10 @@ const debugRender = ()=>
393
394
  }
394
395
  /**
395
396
  * LittleJS Utility Classes and Functions
396
- * <br> - General purpose math library
397
- * <br> - Vector2 - fast, simple, easy 2D vector class
398
- * <br> - Color - holds a rgba color with some math functions
399
- * <br> - Timer - tracks time automatically
397
+ * - General purpose math library
398
+ * - Vector2 - fast, simple, easy 2D vector class
399
+ * - Color - holds a rgba color with some math functions
400
+ * - Timer - tracks time automatically
400
401
  * @namespace Utilities
401
402
  */
402
403
 
@@ -412,34 +413,34 @@ const PI = Math.PI;
412
413
  * @param {Number} value
413
414
  * @return {Number}
414
415
  * @memberof Utilities */
415
- const abs = (a)=> a < 0 ? -a : a;
416
+ function abs(a) { return a < 0 ? -a : a; }
416
417
 
417
418
  /** Returns lowest of two values passed in
418
419
  * @param {Number} valueA
419
420
  * @param {Number} valueB
420
421
  * @return {Number}
421
422
  * @memberof Utilities */
422
- const min = (a, b)=> a < b ? a : b;
423
+ function min(a, b) { return a < b ? a : b; }
423
424
 
424
425
  /** Returns highest of two values passed in
425
426
  * @param {Number} valueA
426
427
  * @param {Number} valueB
427
428
  * @return {Number}
428
429
  * @memberof Utilities */
429
- const max = (a, b)=> a > b ? a : b;
430
+ function max(a, b) { return a > b ? a : b; }
430
431
 
431
432
  /** Returns the sign of value passed in (also returns 1 if 0)
432
433
  * @param {Number} value
433
434
  * @return {Number}
434
435
  * @memberof Utilities */
435
- const sign = (a)=> a < 0 ? -1 : 1;
436
+ function sign(a) { return a < 0 ? -1 : 1; }
436
437
 
437
438
  /** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
438
439
  * @param {Number} dividend
439
440
  * @param {Number} [divisor=1]
440
441
  * @return {Number}
441
442
  * @memberof Utilities */
442
- const mod = (a, b=1)=> ((a % b) + b) % b;
443
+ function mod(a, b=1) { return ((a % b) + b) % b; }
443
444
 
444
445
  /** Clamps the value beween max and min
445
446
  * @param {Number} value
@@ -447,7 +448,8 @@ const mod = (a, b=1)=> ((a % b) + b) % b;
447
448
  * @param {Number} [max=1]
448
449
  * @return {Number}
449
450
  * @memberof Utilities */
450
- const clamp = (v, min=0, max=1)=> v < min ? min : v > max ? max : v;
451
+ function clamp(v, min=0, max=1)
452
+ { return v < min ? min : v > max ? max : v; }
451
453
 
452
454
  /** Returns what percentage the value is between max and min
453
455
  * @param {Number} value
@@ -455,7 +457,8 @@ const clamp = (v, min=0, max=1)=> v < min ? min : v > max ? max : v;
455
457
  * @param {Number} [max=1]
456
458
  * @return {Number}
457
459
  * @memberof Utilities */
458
- const percent = (v, min=0, max=1)=> max-min ? clamp((v-min) / (max-min)) : 0;
460
+ function percent(v, min=0, max=1)
461
+ { return max-min ? clamp((v-min) / (max-min)) : 0; }
459
462
 
460
463
  /** Linearly interpolates the percent value between max and min
461
464
  * @param {Number} percent
@@ -463,19 +466,19 @@ const percent = (v, min=0, max=1)=> max-min ? clamp((v-min) / (max-min)) : 0;
463
466
  * @param {Number} [max=1]
464
467
  * @return {Number}
465
468
  * @memberof Utilities */
466
- const lerp = (p, min=0, max=1)=> min + clamp(p) * (max-min);
469
+ function lerp(p, min=0, max=1){ return min + clamp(p) * (max-min); }
467
470
 
468
471
  /** Applies smoothstep function to the percentage value
469
472
  * @param {Number} value
470
473
  * @return {Number}
471
474
  * @memberof Utilities */
472
- const smoothStep = (p)=> p * p * (3 - 2 * p);
475
+ function smoothStep(p) { return p * p * (3 - 2 * p); }
473
476
 
474
477
  /** Returns the nearest power of two not less then the value
475
478
  * @param {Number} value
476
479
  * @return {Number}
477
480
  * @memberof Utilities */
478
- const nearestPowerOfTwo = (v)=> 2**Math.ceil(Math.log2(v));
481
+ function nearestPowerOfTwo(v) { return 2**Math.ceil(Math.log2(v)); }
479
482
 
480
483
  /** Returns true if two axis aligned bounding boxes are overlapping
481
484
  * @param {Vector2} pointA - Center of box A
@@ -484,7 +487,8 @@ const nearestPowerOfTwo = (v)=> 2**Math.ceil(Math.log2(v));
484
487
  * @param {Vector2} [sizeB] - Size of box B
485
488
  * @return {Boolean} - True if overlapping
486
489
  * @memberof Utilities */
487
- 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;
490
+ function isOverlapping(pA, sA, pB, sB)
491
+ { return abs(pA.x - pB.x)*2 < sA.x + sB.x && abs(pA.y - pB.y)*2 < sA.y + sB.y; }
488
492
 
489
493
  /** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
490
494
  * @param {Number} [frequency=1] - Frequency of the wave in Hz
@@ -492,13 +496,14 @@ const isOverlapping = (pA, sA, pB, sB)=> abs(pA.x - pB.x)*2 < sA.x + sB.x && abs
492
496
  * @param {Number} [t=time] - Value to use for time of the wave
493
497
  * @return {Number} - Value waving between 0 and amplitude
494
498
  * @memberof Utilities */
495
- const wave = (frequency=1, amplitude=1, t=time)=> amplitude/2 * (1 - Math.cos(t*frequency*2*PI));
499
+ function wave(frequency=1, amplitude=1, t=time)
500
+ { return amplitude/2 * (1 - Math.cos(t*frequency*2*PI)); }
496
501
 
497
502
  /** Formats seconds to mm:ss style for display purposes
498
503
  * @param {Number} t - time in seconds
499
504
  * @return {String}
500
505
  * @memberof Utilities */
501
- const formatTime = (t)=> (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0);
506
+ function formatTime(t) { return (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0); }
502
507
 
503
508
  ///////////////////////////////////////////////////////////////////////////////
504
509
 
@@ -510,32 +515,33 @@ const formatTime = (t)=> (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0);
510
515
  * @param {Number} [valueB=0]
511
516
  * @return {Number}
512
517
  * @memberof Random */
513
- const rand = (a=1, b=0)=> b + (a-b)*Math.random();
518
+ function rand(a=1, b=0) { return b + (a-b)*Math.random(); }
514
519
 
515
520
  /** Returns a floored random value the two values passed in
516
521
  * @param {Number} [valueA=1]
517
522
  * @param {Number} [valueB=0]
518
523
  * @return {Number}
519
524
  * @memberof Random */
520
- const randInt = (a=1, b=0)=> rand(a,b)|0;
525
+ function randInt(a=1, b=0) { return rand(a,b)|0; }
521
526
 
522
527
  /** Randomly returns either -1 or 1
523
528
  * @return {Number}
524
529
  * @memberof Random */
525
- const randSign = ()=> randInt(2) * 2 - 1;
530
+ function randSign() { return randInt(2) * 2 - 1; }
526
531
 
527
532
  /** Returns a random Vector2 within a circular shape
528
533
  * @param {Number} [radius=1]
529
534
  * @param {Number} [minRadius=0]
530
535
  * @return {Vector2}
531
536
  * @memberof Random */
532
- const randInCircle = (radius=1, minRadius=0)=> radius > 0 ? randVector(radius * rand(minRadius / radius, 1)**.5) : new Vector2;
537
+ function randInCircle(radius=1, minRadius=0)
538
+ { return radius > 0 ? randVector(radius * rand(minRadius / radius, 1)**.5) : new Vector2; }
533
539
 
534
540
  /** Returns a random Vector2 with the passed in length
535
541
  * @param {Number} [length=1]
536
542
  * @return {Vector2}
537
543
  * @memberof Random */
538
- const randVector = (length=1)=> new Vector2().setAngle(rand(2*PI), length);
544
+ function randVector(length=1) { return new Vector2().setAngle(rand(2*PI), length); }
539
545
 
540
546
  /** Returns a random color between the two passed in colors, combine components if linear
541
547
  * @param {Color} [colorA=Color()]
@@ -543,8 +549,8 @@ const randVector = (length=1)=> new Vector2().setAngle(rand(2*PI), length);
543
549
  * @param {Boolean} [linear]
544
550
  * @return {Color}
545
551
  * @memberof Random */
546
- const randColor = (cA = new Color, cB = new Color(0,0,0,1), linear)=>
547
- 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));
552
+ function randColor(cA = new Color, cB = new Color(0,0,0,1), linear)
553
+ { return 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)); }
548
554
 
549
555
  /** Seed used by the randSeeded function
550
556
  * @type {Number}
@@ -555,16 +561,19 @@ let randSeed = 1;
555
561
  /** Set seed used by the randSeeded function, should not be 0
556
562
  * @param {Number} seed
557
563
  * @memberof Random */
558
- const setRandSeed = (seed)=> randSeed = seed;
564
+ function setRandSeed(seed) { randSeed = seed; }
559
565
 
560
566
  /** Returns a seeded random value between the two values passed in using randSeed
561
567
  * @param {Number} [valueA=1]
562
568
  * @param {Number} [valueB=0]
563
569
  * @return {Number}
564
570
  * @memberof Random */
565
- const randSeeded = (a=1, b=0)=>
571
+ function randSeeded(a=1, b=0)
566
572
  {
567
- randSeed ^= randSeed << 13; randSeed ^= randSeed >>> 17; randSeed ^= randSeed << 5; // xorshift
573
+ // xorshift algorithm
574
+ randSeed ^= randSeed << 13;
575
+ randSeed ^= randSeed >>> 17;
576
+ randSeed ^= randSeed << 5;
568
577
  return b + (a-b) * abs(randSeed % 1e9) / 1e9;
569
578
  }
570
579
 
@@ -582,7 +591,8 @@ const randSeeded = (a=1, b=0)=>
582
591
  * b = vec2(); // set b to (0, 0)
583
592
  * @memberof Utilities
584
593
  */
585
- const vec2 = (x=0, y)=> x.x == undefined ? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y);
594
+ function vec2(x=0, y)
595
+ { return x.x == undefined ? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y); }
586
596
 
587
597
  /**
588
598
  * Check if object is a valid Vector2
@@ -590,11 +600,11 @@ const vec2 = (x=0, y)=> x.x == undefined ? new Vector2(x, y == undefined? x : y)
590
600
  * @return {Boolean}
591
601
  * @memberof Utilities
592
602
  */
593
- const isVector2 = (v)=> !isNaN(v.x) && !isNaN(v.y);
603
+ function isVector2(v) { return !isNaN(v.x) && !isNaN(v.y); }
594
604
 
595
605
  /**
596
606
  * 2D Vector object with vector math library
597
- * <br> - Functions do not change this so they can be chained together
607
+ * - Functions do not change this so they can be chained together
598
608
  * @example
599
609
  * let a = new Vector2(2, 3); // vector with coordinates (2, 3)
600
610
  * let b = new Vector2; // vector with coordinates (0, 0)
@@ -741,7 +751,7 @@ class Vector2
741
751
  * @return {Color}
742
752
  * @memberof Utilities
743
753
  */
744
- const colorRGBA = (r, g, b, a)=> new Color(r, g, b, a);
754
+ function rgb(r, g, b, a) { return new Color(r, g, b, a); }
745
755
 
746
756
  /**
747
757
  * Create a color object with HSLA values
@@ -752,7 +762,7 @@ const colorRGBA = (r, g, b, a)=> new Color(r, g, b, a);
752
762
  * @return {Color}
753
763
  * @memberof Utilities
754
764
  */
755
- const colorHSLA = (h, s, l, a)=> new Color().setHSLA(h, s, l, a);
765
+ function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
756
766
 
757
767
  /**
758
768
  * Color object (red, green, blue, alpha) with some helpful functions
@@ -760,8 +770,8 @@ const colorHSLA = (h, s, l, a)=> new Color().setHSLA(h, s, l, a);
760
770
  * let a = new Color; // white
761
771
  * let b = new Color(1, 0, 0); // red
762
772
  * let c = new Color(0, 0, 0, 0); // transparent black
763
- * let d = colorRGBA(0, 0, 1); // blue using rgb color
764
- * let e = colorHSLA(.3, 1, .5); // green using hsl color
773
+ * let d = RGB(0, 0, 1); // blue using rgb color
774
+ * let e = HSL(.3, 1, .5); // green using hsl color
765
775
  */
766
776
  class Color
767
777
  {
@@ -1011,11 +1021,11 @@ let canvasMaxSize = vec2(1920, 1200);
1011
1021
  * @memberof Settings */
1012
1022
  let canvasFixedSize = vec2();
1013
1023
 
1014
- /** Disables anti aliasing for pixel art if true
1024
+ /** Disables filtering for crisper pixel art if true
1015
1025
  * @type {Boolean}
1016
1026
  * @default
1017
1027
  * @memberof Settings */
1018
- let cavasPixelated = 1;
1028
+ let canvasPixelated = 1;
1019
1029
 
1020
1030
  /** Default font used for text rendering
1021
1031
  * @type {String}
@@ -1132,8 +1142,8 @@ let gamepadDirectionEmulateStick = 1;
1132
1142
  let inputWASDEmulateDirection = 1;
1133
1143
 
1134
1144
  /** True if touch gamepad should appear on mobile devices
1135
- * <br> - Supports left analog stick, 4 face buttons and start button (button 9)
1136
- * <br> - Must be set by end of gameInit to be activated
1145
+ * - Supports left analog stick, 4 face buttons and start button (button 9)
1146
+ * - Must be set by end of gameInit to be activated
1137
1147
  * @type {Boolean}
1138
1148
  * @default 0
1139
1149
  * @memberof Settings */
@@ -1222,32 +1232,32 @@ let medalDisplayIconSize = 50;
1222
1232
  * @default 0
1223
1233
  * @memberof Settings */
1224
1234
  let medalsPreventUnlock;
1225
- /*
1226
- LittleJS Object System
1227
- */
1235
+ /**
1236
+ * LittleJS Object System
1237
+ */
1228
1238
 
1229
1239
  'use strict';
1230
1240
 
1231
1241
  /**
1232
1242
  * LittleJS Object Base Object Class
1233
- * <br> - Base object class used by the engine
1234
- * <br> - Automatically adds self to object list
1235
- * <br> - Will be updated and rendered each frame
1236
- * <br> - Renders as a sprite from a tilesheet by default
1237
- * <br> - Can have color and addtive color applied
1238
- * <br> - 2d Physics and collision system
1239
- * <br> - Sorted by renderOrder
1240
- * <br> - Objects can have children attached
1241
- * <br> - Parents are updated before children, and set child transform
1242
- * <br> - Call destroy() to get rid of objects
1243
- * <br>
1244
- * <br>The physics system used by objects is simple and fast with some caveats...
1245
- * <br> - Collision uses the axis aligned size, the object's rotation angle is only for rendering
1246
- * <br> - Objects are guaranteed to not intersect tile collision from physics
1247
- * <br> - If an object starts or is moved inside tile collision, it will not collide with that tile
1248
- * <br> - Collision for objects can be set to be solid to block other objects
1249
- * <br> - Objects may get pushed into overlapping other solid objects, if so they will push away
1250
- * <br> - Solid objects are more performance intensive and should be used sparingly
1243
+ * - Base object class used by the engine
1244
+ * - Automatically adds self to object list
1245
+ * - Will be updated and rendered each frame
1246
+ * - Renders as a sprite from a tilesheet by default
1247
+ * - Can have color and addtive color applied
1248
+ * - 2d Physics and collision system
1249
+ * - Sorted by renderOrder
1250
+ * - Objects can have children attached
1251
+ * - Parents are updated before children, and set child transform
1252
+ * - Call destroy() to get rid of objects
1253
+ *
1254
+ * The physics system used by objects is simple and fast with some caveats...
1255
+ * - Collision uses the axis aligned size, the object's rotation angle is only for rendering
1256
+ * - Objects are guaranteed to not intersect tile collision from physics
1257
+ * - If an object starts or is moved inside tile collision, it will not collide with that tile
1258
+ * - Collision for objects can be set to be solid to block other objects
1259
+ * - Objects may get pushed into overlapping other solid objects, if so they will push away
1260
+ * - Solid objects are more performance intensive and should be used sparingly
1251
1261
  * @example
1252
1262
  * // create an engine object, normally you would first extend the class with your own
1253
1263
  * const pos = vec2(2,3);
@@ -1336,6 +1346,7 @@ class EngineObject
1336
1346
  this.velocity.y += gravity * this.gravityScale;
1337
1347
  this.pos.x += this.velocity.x *= this.damping;
1338
1348
  this.pos.y += this.velocity.y *= this.damping;
1349
+ this.angle += this.angleVelocity *= this.angleDamping;
1339
1350
 
1340
1351
  // physics sanity checks
1341
1352
  ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
@@ -1392,6 +1403,7 @@ class EngineObject
1392
1403
  const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity; // prefer to push up if small delta
1393
1404
  const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
1394
1405
  const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
1406
+ const elasticity = max(this.elasticity, o.elasticity);
1395
1407
 
1396
1408
  if (smallStepUp | isBlockedY | !isBlockedX) // resolve y collision
1397
1409
  {
@@ -1404,7 +1416,7 @@ class EngineObject
1404
1416
  this.groundObject = o;
1405
1417
 
1406
1418
  // bounce if other object is fixed or grounded
1407
- this.velocity.y *= -this.elasticity;
1419
+ this.velocity.y *= -elasticity;
1408
1420
  }
1409
1421
  else if (o.mass)
1410
1422
  {
@@ -1418,7 +1430,6 @@ class EngineObject
1418
1430
  + this.velocity.y * 2 * this.mass / (this.mass + o.mass);
1419
1431
 
1420
1432
  // lerp betwen elastic or inelastic based on elasticity
1421
- const elasticity = max(this.elasticity, o.elasticity);
1422
1433
  this.velocity.y = lerp(elasticity, inelastic, elastic0);
1423
1434
  o.velocity.y = lerp(elasticity, inelastic, elastic1);
1424
1435
  }
@@ -1439,12 +1450,11 @@ class EngineObject
1439
1450
  + this.velocity.x * 2 * this.mass / (this.mass + o.mass);
1440
1451
 
1441
1452
  // lerp betwen elastic or inelastic based on elasticity
1442
- const elasticity = max(this.elasticity, o.elasticity);
1443
1453
  this.velocity.x = lerp(elasticity, inelastic, elastic0);
1444
1454
  o.velocity.x = lerp(elasticity, inelastic, elastic1);
1445
1455
  }
1446
1456
  else // bounce if other object is fixed
1447
- this.velocity.x *= -this.elasticity;
1457
+ this.velocity.x *= -elasticity;
1448
1458
  }
1449
1459
  debugOverlay && debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f0f');
1450
1460
  }
@@ -1600,23 +1610,23 @@ class EngineObject
1600
1610
  }
1601
1611
  /**
1602
1612
  * LittleJS Drawing System
1603
- * <br> - Hybrid with both Canvas2D and WebGL available
1604
- * <br> - Super fast tile sheet rendering with WebGL
1605
- * <br> - Can apply rotation, mirror, color and additive color
1606
- * <br> - Many useful utility functions
1607
- * <br>
1608
- * <br>LittleJS uses a hybrid rendering solution with the best of both Canvas2D and WebGL.
1609
- * <br>There are 3 canvas/contexts available to draw to...
1610
- * <br> - mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
1611
- * <br> - glCanvas - Used by the accelerated WebGL batch rendering system.
1612
- * <br> - overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
1613
- * <br>
1614
- * <br>The WebGL rendering system is very fast with some caveats...
1615
- * <br> - The default setup supports only 1 tile sheet, to support more call glCreateTexture and glSetTexture
1616
- * <br> - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
1617
- * <br> - Group additive rendering together using renderOrder to mitigate this issue
1618
- * <br>
1619
- * <br>The LittleJS rendering solution is intentionally simple, feel free to adjust it for your needs!
1613
+ * - Hybrid with both Canvas2D and WebGL available
1614
+ * - Super fast tile sheet rendering with WebGL
1615
+ * - Can apply rotation, mirror, color and additive color
1616
+ * - Many useful utility functions
1617
+ *
1618
+ * LittleJS uses a hybrid rendering solution with the best of both Canvas2D and WebGL.
1619
+ * There are 3 canvas/contexts available to draw to...
1620
+ * mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
1621
+ * glCanvas - Used by the accelerated WebGL batch rendering system.
1622
+ * overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
1623
+ *
1624
+ * The WebGL rendering system is very fast with some caveats...
1625
+ * - The default setup supports only 1 tile sheet, to support more call glCreateTexture and glSetTexture
1626
+ * - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
1627
+ * - Group additive rendering together using renderOrder to mitigate this issue
1628
+ *
1629
+ * The LittleJS rendering solution is intentionally simple, feel free to adjust it for your needs!
1620
1630
  * @namespace Draw
1621
1631
  */
1622
1632
 
@@ -1660,7 +1670,7 @@ let tileImageSize, tileImageFixBleed, drawCount;
1660
1670
  * @param {Vector2} screenPos
1661
1671
  * @return {Vector2}
1662
1672
  * @memberof Draw */
1663
- const screenToWorld = (screenPos)=>
1673
+ function screenToWorld(screenPos)
1664
1674
  {
1665
1675
  ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
1666
1676
  return screenPos.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos);
@@ -1671,7 +1681,7 @@ const screenToWorld = (screenPos)=>
1671
1681
  * @param {Vector2} worldPos
1672
1682
  * @return {Vector2}
1673
1683
  * @memberof Draw */
1674
- const worldToScreen = (worldPos)=>
1684
+ function worldToScreen(worldPos)
1675
1685
  {
1676
1686
  ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
1677
1687
  return worldPos.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));
@@ -1826,6 +1836,23 @@ function setBlendMode(additive, useWebGL=glEnable)
1826
1836
  mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
1827
1837
  }
1828
1838
 
1839
+ /** Draw text on overlay canvas in world space
1840
+ * Automatically splits new lines into rows
1841
+ * @param {String} text
1842
+ * @param {Vector2} pos
1843
+ * @param {Number} [size=1]
1844
+ * @param {Color} [color=Color()]
1845
+ * @param {Number} [lineWidth=0]
1846
+ * @param {Color} [lineColor=Color(0,0,0)]
1847
+ * @param {String} [textAlign='center']
1848
+ * @param {String} [font=fontDefault]
1849
+ * @param {CanvasRenderingContext2D} [context=overlayContext]
1850
+ * @memberof Draw */
1851
+ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context)
1852
+ {
1853
+ drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, context);
1854
+ }
1855
+
1829
1856
  /** Draw text on overlay canvas in screen space
1830
1857
  * Automatically splits new lines into rows
1831
1858
  * @param {String} text
@@ -1835,6 +1862,8 @@ function setBlendMode(additive, useWebGL=glEnable)
1835
1862
  * @param {Number} [lineWidth=0]
1836
1863
  * @param {Color} [lineColor=Color(0,0,0)]
1837
1864
  * @param {String} [textAlign='center']
1865
+ * @param {String} [font=fontDefault]
1866
+ * @param {CanvasRenderingContext2D} [context=overlayContext]
1838
1867
  * @memberof Draw */
1839
1868
  function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
1840
1869
  {
@@ -1855,30 +1884,15 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
1855
1884
  });
1856
1885
  }
1857
1886
 
1858
- /** Draw text on overlay canvas in world space
1859
- * Automatically splits new lines into rows
1860
- * @param {String} text
1861
- * @param {Vector2} pos
1862
- * @param {Number} [size=1]
1863
- * @param {Color} [color=Color()]
1864
- * @param {Number} [lineWidth=0]
1865
- * @param {Color} [lineColor=Color(0,0,0)]
1866
- * @param {String} [textAlign='center']
1867
- * @memberof Draw */
1868
- function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font)
1869
- {
1870
- drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, mainContext);
1871
- }
1872
-
1873
1887
  ///////////////////////////////////////////////////////////////////////////////
1874
1888
 
1875
1889
  let engineFontImage;
1876
1890
 
1877
1891
  /**
1878
1892
  * Font Image Object - Draw text on a 2D canvas by using characters in an image
1879
- * <br> - 96 characters (from space to tilde) are stored in an image
1880
- * <br> - Uses a default 8x8 font if none is supplied
1881
- * <br> - You can also use fonts from the main tile sheet
1893
+ * - 96 characters (from space to tilde) are stored in an image
1894
+ * - Uses a default 8x8 font if none is supplied
1895
+ * - You can also use fonts from the main tile sheet
1882
1896
  * @example
1883
1897
  * // use built in font
1884
1898
  * const font = new ImageFont;
@@ -1918,7 +1932,7 @@ class FontImage
1918
1932
  {
1919
1933
  const context = this.context;
1920
1934
  context.save();
1921
- context.imageSmoothingEnabled = !cavasPixelated;
1935
+ context.imageSmoothingEnabled = !canvasPixelated;
1922
1936
 
1923
1937
  const size = this.tileSize;
1924
1938
  const drawSize = size.add(this.paddingSize).scale(scale);
@@ -1964,7 +1978,7 @@ class FontImage
1964
1978
  /** Returns true if fullscreen mode is active
1965
1979
  * @return {Boolean}
1966
1980
  * @memberof Draw */
1967
- const isFullscreen = ()=> document.fullscreenElement;
1981
+ function isFullscreen() { return document.fullscreenElement; }
1968
1982
 
1969
1983
  /** Toggle fullsceen mode
1970
1984
  * @memberof Draw */
@@ -1981,10 +1995,10 @@ function toggleFullscreen()
1981
1995
 
1982
1996
  /**
1983
1997
  * LittleJS Input System
1984
- * <br> - Tracks key down, pressed, and released
1985
- * <br> - Also tracks mouse buttons, position, and wheel
1986
- * <br> - Supports multiple gamepads
1987
- * <br> - Virtual gamepad for touch devices with touchGamepadSize
1998
+ * - Tracks key down, pressed, and released
1999
+ * - Also tracks mouse buttons, position, and wheel
2000
+ * - Supports multiple gamepads
2001
+ * - Virtual gamepad for touch devices with touchGamepadSize
1988
2002
  * @namespace Input
1989
2003
  */
1990
2004
 
@@ -1995,25 +2009,28 @@ function toggleFullscreen()
1995
2009
  * @param {Number} [device=0]
1996
2010
  * @return {Boolean}
1997
2011
  * @memberof Input */
1998
- const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1;
2012
+ function keyIsDown(key, device=0)
2013
+ { return inputData[device] && inputData[device][key] & 1; }
1999
2014
 
2000
2015
  /** Returns true if device key was pressed this frame
2001
2016
  * @param {Number} key
2002
2017
  * @param {Number} [device=0]
2003
2018
  * @return {Boolean}
2004
2019
  * @memberof Input */
2005
- const keyWasPressed = (key, device=0)=> inputData[device] && inputData[device][key] & 2 ? 1 : 0;
2020
+ function keyWasPressed(key, device=0)
2021
+ { return inputData[device] && inputData[device][key] & 2 ? 1 : 0; }
2006
2022
 
2007
2023
  /** Returns true if device key was released this frame
2008
2024
  * @param {Number} key
2009
2025
  * @param {Number} [device=0]
2010
2026
  * @return {Boolean}
2011
2027
  * @memberof Input */
2012
- const keyWasReleased = (key, device=0)=> inputData[device] && inputData[device][key] & 4 ? 1 : 0;
2028
+ function keyWasReleased(key, device=0)
2029
+ { return inputData[device] && inputData[device][key] & 4 ? 1 : 0; }
2013
2030
 
2014
2031
  /** Clears all input
2015
2032
  * @memberof Input */
2016
- const clearInput = ()=> inputData = [[]];
2033
+ function clearInput() { inputData = [[]]; }
2017
2034
 
2018
2035
  /** Returns true if mouse button is down
2019
2036
  * @function
@@ -2066,28 +2083,32 @@ let preventDefaultInput = 0;
2066
2083
  * @param {Number} [gamepad=0]
2067
2084
  * @return {Boolean}
2068
2085
  * @memberof Input */
2069
- const gamepadIsDown = (button, gamepad=0)=> keyIsDown(button, gamepad+1);
2086
+ function gamepadIsDown(button, gamepad=0)
2087
+ { return keyIsDown(button, gamepad+1); }
2070
2088
 
2071
2089
  /** Returns true if gamepad button was pressed
2072
2090
  * @param {Number} button
2073
2091
  * @param {Number} [gamepad=0]
2074
2092
  * @return {Boolean}
2075
2093
  * @memberof Input */
2076
- const gamepadWasPressed = (button, gamepad=0)=> keyWasPressed(button, gamepad+1);
2094
+ function gamepadWasPressed(button, gamepad=0)
2095
+ { return keyWasPressed(button, gamepad+1); }
2077
2096
 
2078
2097
  /** Returns true if gamepad button was released
2079
2098
  * @param {Number} button
2080
2099
  * @param {Number} [gamepad=0]
2081
2100
  * @return {Boolean}
2082
2101
  * @memberof Input */
2083
- const gamepadWasReleased = (button, gamepad=0)=> keyWasReleased(button, gamepad+1);
2102
+ function gamepadWasReleased(button, gamepad=0)
2103
+ { return keyWasReleased(button, gamepad+1); }
2084
2104
 
2085
2105
  /** Returns gamepad stick value
2086
2106
  * @param {Number} stick
2087
2107
  * @param {Number} [gamepad=0]
2088
2108
  * @return {Vector2}
2089
2109
  * @memberof Input */
2090
- const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepad][stick] || vec2() : vec2();
2110
+ function gamepadStick(stick, gamepad=0)
2111
+ { return stickData[gamepad] ? stickData[gamepad][stick] || vec2() : vec2(); }
2091
2112
 
2092
2113
  ///////////////////////////////////////////////////////////////////////////////
2093
2114
  // Input update called by engine
@@ -2126,12 +2147,18 @@ onkeydown = (e)=>
2126
2147
  e.repeat || (inputData[isUsingGamepad = 0][remapKey(e.which)] = 3);
2127
2148
  preventDefaultInput && e.preventDefault();
2128
2149
  }
2150
+
2129
2151
  onkeyup = (e)=>
2130
2152
  {
2131
2153
  if (debug && e.target != document.body) return;
2132
2154
  inputData[0][remapKey(e.which)] = 4;
2133
2155
  }
2134
- const remapKey = (c)=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
2156
+
2157
+ function remapKey(c)
2158
+ {
2159
+ return inputWASDEmulateDirection ?
2160
+ c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
2161
+ }
2135
2162
 
2136
2163
  ///////////////////////////////////////////////////////////////////////////////
2137
2164
  // Mouse event handlers
@@ -2140,10 +2167,10 @@ onmousedown = (e)=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e)
2140
2167
  onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
2141
2168
  onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
2142
2169
  onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
2143
- oncontextmenu = (e)=> !1; // prevent right click menu
2170
+ oncontextmenu = (e)=> false; // prevent right click menu
2144
2171
 
2145
2172
  // convert a mouse or touch event position to screen space
2146
- const mouseToScreen = (mousePos)=>
2173
+ function mouseToScreen(mousePos)
2147
2174
  {
2148
2175
  if (!mainCanvas)
2149
2176
  return vec2(); // fix bug that can occur if user clicks before page loads
@@ -2189,8 +2216,7 @@ function gamepadsUpdate()
2189
2216
  if (gamepad)
2190
2217
  {
2191
2218
  // read clamp dead zone of analog sticks
2192
- const deadZone = .3, deadZoneMax = .8;
2193
- const applyDeadZone = (v)=>
2219
+ const deadZone = .3, deadZoneMax = .8, applyDeadZone = (v)=>
2194
2220
  v > deadZone ? percent( v, deadZone, deadZoneMax) :
2195
2221
  v < -deadZone ? -percent(-v, deadZone, deadZoneMax) : 0;
2196
2222
 
@@ -2223,11 +2249,12 @@ function gamepadsUpdate()
2223
2249
  /** Pulse the vibration hardware if it exists
2224
2250
  * @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array
2225
2251
  * @memberof Input */
2226
- const vibrate = (pattern)=> vibrateEnable && navigator && navigator.vibrate && navigator.vibrate(pattern);
2252
+ function vibrate(pattern)
2253
+ { vibrateEnable && navigator && navigator.vibrate && navigator.vibrate(pattern); }
2227
2254
 
2228
2255
  /** Cancel any ongoing vibration
2229
2256
  * @memberof Input */
2230
- const vibrateStop = ()=> vibrate(0);
2257
+ function vibrateStop() { vibrate(0); }
2231
2258
 
2232
2259
  ///////////////////////////////////////////////////////////////////////////////
2233
2260
  // Touch input
@@ -2273,6 +2300,9 @@ if (isTouchDevice)
2273
2300
  return true;
2274
2301
  }
2275
2302
 
2303
+ // try to create touch game pad
2304
+ touchGamepadEnable && touchGamepadCreate();
2305
+
2276
2306
  return ontouchstart(e);
2277
2307
  }
2278
2308
  }
@@ -2286,76 +2316,69 @@ let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
2286
2316
  // create the touch gamepad, called automatically by the engine
2287
2317
  function touchGamepadCreate()
2288
2318
  {
2289
- if (!touchGamepadEnable || !isTouchDevice)
2290
- return;
2291
-
2292
2319
  // touch input internal variables
2293
2320
  touchGamepadButtons = [];
2294
2321
  touchGamepadStick = vec2();
2295
2322
 
2296
- // setup touch input
2297
- ontouchstart = (e)=>
2323
+ let touchHandler = ontouchstart;
2324
+ ontouchstart = ontouchmove = ontouchend = (e)=>
2298
2325
  {
2299
- // fix mobile audio, force it to play a sound on first touch
2300
- zzfx(0);
2301
-
2302
- ontouchstart = ontouchmove = ontouchend = (e)=>
2326
+ // clear touch gamepad input
2327
+ touchGamepadStick = vec2();
2328
+ touchGamepadButtons = [];
2329
+
2330
+ const touching = e.touches.length;
2331
+ if (touching)
2303
2332
  {
2304
- // clear touch gamepad input
2305
- touchGamepadStick = vec2();
2306
- touchGamepadButtons = [];
2307
-
2308
- const touching = e.touches.length;
2309
- if (touching)
2333
+ touchGamepadTimer.set();
2334
+ if (paused)
2310
2335
  {
2311
- // set that gamepad is active
2312
- isUsingGamepad = 1;
2313
- touchGamepadTimer.set();
2314
-
2315
- if (paused)
2316
- {
2317
- // touch anywhere to press start when paused
2318
- touchGamepadButtons[9] = 1;
2319
- return;
2320
- }
2336
+ // touch anywhere to press start when paused
2337
+ touchGamepadButtons[9] = 1;
2338
+ return;
2321
2339
  }
2340
+ }
2322
2341
 
2323
- // get center of left and right sides
2324
- const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2325
- const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
2326
- const startCenter = mainCanvasSize.scale(.5);
2342
+ // get center of left and right sides
2343
+ const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2344
+ const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
2345
+ const startCenter = mainCanvasSize.scale(.5);
2327
2346
 
2328
- // check each touch point
2329
- for (const touch of e.touches)
2347
+ // check each touch point
2348
+ for (const touch of e.touches)
2349
+ {
2350
+ const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
2351
+ if (touchPos.distance(stickCenter) < touchGamepadSize)
2330
2352
  {
2331
- const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
2332
- if (touchPos.distance(stickCenter) < touchGamepadSize)
2333
- {
2334
- // virtual analog stick
2335
- if (touchGamepadAnalog)
2336
- touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
2337
- else
2338
- {
2339
- // 8 way dpad
2340
- const angle = touchPos.subtract(stickCenter).angle();
2341
- touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
2342
- }
2343
- }
2344
- else if (touchPos.distance(buttonCenter) < touchGamepadSize)
2353
+ // virtual analog stick
2354
+ if (touchGamepadAnalog)
2355
+ touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
2356
+ else
2345
2357
  {
2346
- // virtual face buttons
2347
- const button = touchPos.subtract(buttonCenter).direction();
2348
- touchGamepadButtons[button] = 1;
2349
- }
2350
- else if (touchPos.distance(startCenter) < touchGamepadSize)
2351
- {
2352
- // virtual start button in center
2353
- touchGamepadButtons[9] = 1;
2358
+ // 8 way dpad
2359
+ const angle = touchPos.subtract(stickCenter).angle();
2360
+ touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
2354
2361
  }
2355
2362
  }
2363
+ else if (touchPos.distance(buttonCenter) < touchGamepadSize)
2364
+ {
2365
+ // virtual face buttons
2366
+ const button = touchPos.subtract(buttonCenter).direction();
2367
+ touchGamepadButtons[button] = 1;
2368
+ }
2369
+ else if (touchPos.distance(startCenter) < touchGamepadSize)
2370
+ {
2371
+ // virtual start button in center
2372
+ touchGamepadButtons[9] = 1;
2373
+ }
2356
2374
  }
2357
2375
 
2358
- return ontouchstart(e);
2376
+ // call default touch handler and set to using gamepad
2377
+ touchHandler(e);
2378
+ isUsingGamepad = 1;
2379
+
2380
+ // must return true so the document will get focus
2381
+ return true;
2359
2382
  }
2360
2383
  }
2361
2384
 
@@ -2381,7 +2404,7 @@ function touchGamepadRender()
2381
2404
  overlayContext.beginPath();
2382
2405
 
2383
2406
  const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2384
- if (touchGamepadAnalog)
2407
+ if (touchGamepadAnalog) // draw circle shaped gamepad
2385
2408
  {
2386
2409
  overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
2387
2410
  overlayContext.fill();
@@ -2416,20 +2439,21 @@ function touchGamepadRender()
2416
2439
  }
2417
2440
  /**
2418
2441
  * LittleJS Audio System
2419
- * <br> - <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX Sound Effects</a>
2420
- * <br> - <a href=https://keithclark.github.io/ZzFXM/>ZzFXM Music</a>
2421
- * <br> - Caches sounds and music for fast playback
2422
- * <br> - Can attenuate and apply stereo panning to sounds
2423
- * <br> - Ability to play mp3, ogg, and wave files
2424
- * <br> - Speech synthesis wrapper functions
2442
+ * - <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX Sound Effects</a> - Sound Effect Generator
2443
+ * - <a href=https://keithclark.github.io/ZzFXM/>ZzFXM Music</a> - Music System
2444
+ * - Caches sounds and music for fast playback
2445
+ * - Can attenuate and apply stereo panning to sounds
2446
+ * - Ability to play mp3, ogg, and wave files
2447
+ * - Speech synthesis wrapper functions
2448
+ * @namespace Audio
2425
2449
  */
2426
2450
 
2427
2451
  'use strict';
2428
2452
 
2429
2453
  /**
2430
2454
  * Sound Object - Stores a zzfx sound for later use and can be played positionally
2431
- * <br>
2432
- * <br><b><a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a></b>
2455
+ *
2456
+ * <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
2433
2457
  * @example
2434
2458
  * // create a sound
2435
2459
  * const sound_example = new Sound([.5,.5]);
@@ -2513,8 +2537,8 @@ class Sound
2513
2537
 
2514
2538
  /**
2515
2539
  * Music Object - Stores a zzfx music track for later use
2516
- * <br>
2517
- * <br><b><a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a></b>
2540
+ *
2541
+ * <a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a>
2518
2542
  * @example
2519
2543
  * // create some music
2520
2544
  * const music_example = new Music(
@@ -2624,14 +2648,15 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
2624
2648
 
2625
2649
  /** Stop all queued speech
2626
2650
  * @memberof Audio */
2627
- const speakStop = ()=> speechSynthesis && speechSynthesis.cancel();
2651
+ function speakStop() {speechSynthesis && speechSynthesis.cancel();}
2628
2652
 
2629
2653
  /** Get frequency of a note on a musical scale
2630
2654
  * @param {Number} semitoneOffset - How many semitones away from the root note
2631
2655
  * @param {Number} [rootNoteFrequency=220] - Frequency at semitone offset 0
2632
2656
  * @return {Number} - The frequency of the note
2633
2657
  * @memberof Audio */
2634
- const getNoteFrequency = (semitoneOffset, rootFrequency=220)=> rootFrequency * 2**(semitoneOffset/12);
2658
+ function getNoteFrequency(semitoneOffset, rootFrequency=220)
2659
+ { return rootFrequency * 2**(semitoneOffset/12); }
2635
2660
 
2636
2661
  ///////////////////////////////////////////////////////////////////////////////
2637
2662
 
@@ -2686,15 +2711,15 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
2686
2711
  }
2687
2712
 
2688
2713
  ///////////////////////////////////////////////////////////////////////////////
2689
- // ZzFXMicro - Zuper Zmall Zound Zynth - v1.1.8 by Frank Force
2714
+ // ZzFXMicro - Zuper Zmall Zound Zynth - v1.2.0 by Frank Force
2690
2715
 
2691
2716
  /** Generate and play a ZzFX sound
2692
- * <br>
2693
- * <br><b><a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a></b>
2717
+ *
2718
+ * <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
2694
2719
  * @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
2695
- * @return {Array} - Array of audio samples
2720
+ * @return {AudioBufferSourceNode} - The audio node of the sound played
2696
2721
  * @memberof Audio */
2697
- const zzfx = (...zzfxSound) => playSamples([zzfxG(...zzfxSound)]);
2722
+ function zzfx(...zzfxSound) { return playSamples([zzfxG(...zzfxSound)]); }
2698
2723
 
2699
2724
  /** Sample rate used for all ZzFX sounds
2700
2725
  * @default 44100
@@ -2702,7 +2727,29 @@ const zzfx = (...zzfxSound) => playSamples([zzfxG(...zzfxSound)]);
2702
2727
  const zzfxR = 44100;
2703
2728
 
2704
2729
  /** Generate samples for a ZzFX sound
2705
- * @memberof Audio */
2730
+ * @param {Number} [volume=1] - Volume scale (percent)
2731
+ * @param {Number} [randomness=.05] - How much to randomize frequency (percent Hz)
2732
+ * @param {Number} [frequency=220] - Frequency of sound (Hz)
2733
+ * @param {Number} [attack=0] - Attack time, how fast sound starts (seconds)
2734
+ * @param {Number} [sustain=0] - Sustain time, how long sound holds (seconds)
2735
+ * @param {Number} [release=.1] - Release time, how fast sound fades out (seconds)
2736
+ * @param {Number} [shape=0] - Shape of the sound wave
2737
+ * @param {Number} [shapeCurve=1] - Squarenes of wave (0=square, 1=normal, 2=pointy)
2738
+ * @param {Number} [slide=0] - How much to slide frequency (kHz/s)
2739
+ * @param {Number} [deltaSlide=0] - How much to change slide (kHz/s/s)
2740
+ * @param {Number} [pitchJump=0] - Frequency of pitch jump (Hz)
2741
+ * @param {Number} [pitchJumpTime=0] - Time of pitch jump (seconds)
2742
+ * @param {Number} [repeatTime=0] - Resets some parameters periodically (seconds)
2743
+ * @param {Number} [noise=0] - How much random noise to add (percent)
2744
+ * @param {Number} [modulation=0] - Frequency of modulation wave, negative flips phase (Hz)
2745
+ * @param {Number} [bitCrush=0] - Resamples at a lower frequency in (samples*100)
2746
+ * @param {Number} [delay=0] - Overlap sound with itself for reverb and flanger effects (seconds)
2747
+ * @param {Number} [sustainVolume=1] - Volume level for sustain (percent)
2748
+ * @param {Number} [decay=0] - Decay time, how long to reach sustain after attack (seconds)
2749
+ * @param {Number} [tremolo=0] - Trembling effect, rate controlled by repeat time (precent)
2750
+ * @return {Array} - Array of audio samples
2751
+ * @memberof Audio
2752
+ */
2706
2753
  function zzfxG
2707
2754
  (
2708
2755
  // parameters
@@ -2712,11 +2759,11 @@ function zzfxG
2712
2759
  bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0
2713
2760
  )
2714
2761
  {
2715
- // init parameters
2762
+ // locals
2716
2763
  let PI2 = PI*2, startSlide = slide *= 500 * PI2 / zzfxR / zzfxR, b=[],
2717
2764
  startFrequency = frequency *= (1 + randomness*rand(-1,1)) * PI2 / zzfxR,
2718
- t=0, tm=0, i=0, j=1, r=0, c=0, s=0, f, length;
2719
-
2765
+ t=0, tm=0, i=0, j=1, r=0, c=0, s=0, f, length
2766
+
2720
2767
  // scale by sample rate
2721
2768
  attack = attack * zzfxR + 9; // minimum attack to prevent pop
2722
2769
  decay *= zzfxR;
@@ -2766,18 +2813,18 @@ function zzfxG
2766
2813
  Math.cos(modulation*tm++); // modulation
2767
2814
  t += f - f*noise*(1 - (Math.sin(i)+1)*1e9%2); // noise
2768
2815
 
2769
- if (j && ++j > pitchJumpTime) // pitch jump
2816
+ if (j && ++j > pitchJumpTime) // pitch jump
2770
2817
  {
2771
- frequency += pitchJump; // apply pitch jump
2772
- startFrequency += pitchJump; // also apply to start
2773
- j = 0; // reset pitch jump time
2818
+ frequency += pitchJump; // apply pitch jump
2819
+ startFrequency += pitchJump; // also apply to start
2820
+ j = 0; // reset pitch jump time
2774
2821
  }
2775
2822
 
2776
2823
  if (repeatTime && !(++r % repeatTime)) // repeat
2777
2824
  {
2778
- frequency = startFrequency; // reset frequency
2779
- slide = startSlide; // reset slide
2780
- j = j || 1; // reset pitch jump time
2825
+ frequency = startFrequency; // reset frequency
2826
+ slide = startSlide; // reset slide
2827
+ j ||= 1; // reset pitch jump time
2781
2828
  }
2782
2829
  }
2783
2830
 
@@ -2792,7 +2839,7 @@ function zzfxG
2792
2839
  * @param {Array} patterns - Array of pattern data
2793
2840
  * @param {Array} sequence - Array of pattern indexes
2794
2841
  * @param {Number} [BPM=125] - Playback speed of the song in BPM
2795
- * @returns {Array} - Left and right channel sample data
2842
+ * @return {Array} - Left and right channel sample data
2796
2843
  * @memberof Audio */
2797
2844
  function zzfxM(instruments, patterns, sequence, BPM = 125)
2798
2845
  {
@@ -2890,13 +2937,13 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
2890
2937
  }
2891
2938
  /**
2892
2939
  * LittleJS Tile Layer System
2893
- * <br> - Caches arrays of tiles to off screen canvas for fast rendering
2894
- * <br> - Unlimted numbers of layers, allocates canvases as needed
2895
- * <br> - Interfaces with EngineObject for collision
2896
- * <br> - Collision layer is separate from visible layers
2897
- * <br> - It is recommended to have a visible layer that matches the collision
2898
- * <br> - Tile layers can be drawn to using their context with canvas2d
2899
- * <br> - Drawn directly to the main canvas without using WebGL
2940
+ * - Caches arrays of tiles to off screen canvas for fast rendering
2941
+ * - Unlimted numbers of layers, allocates canvases as needed
2942
+ * - Interfaces with EngineObject for collision
2943
+ * - Collision layer is separate from visible layers
2944
+ * - It is recommended to have a visible layer that matches the collision
2945
+ * - Tile layers can be drawn to using their context with canvas2d
2946
+ * - Drawn directly to the main canvas without using WebGL
2900
2947
  * @namespace TileCollision
2901
2948
  */
2902
2949
 
@@ -2927,15 +2974,19 @@ function initTileCollision(size)
2927
2974
  * @param {Vector2} pos
2928
2975
  * @param {Number} [data=0]
2929
2976
  * @memberof TileCollision */
2930
- const setTileCollisionData = (pos, data=0)=>
2977
+ function setTileCollisionData(pos, data=0)
2978
+ {
2931
2979
  pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
2980
+ }
2932
2981
 
2933
2982
  /** Get tile collision data
2934
2983
  * @param {Vector2} pos
2935
2984
  * @return {Number}
2936
2985
  * @memberof TileCollision */
2937
- const getTileCollisionData = (pos)=>
2938
- pos.arrayCheck(tileCollisionSize) ? tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] : 0;
2986
+ function getTileCollisionData(pos)
2987
+ {
2988
+ return pos.arrayCheck(tileCollisionSize) ? tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] : 0;
2989
+ }
2939
2990
 
2940
2991
  /** Check if collision with another object should occur
2941
2992
  * @param {Vector2} pos
@@ -3029,10 +3080,10 @@ class TileLayerData
3029
3080
 
3030
3081
  /**
3031
3082
  * Tile layer object - cached rendering system for tile layers
3032
- * <br> - Each Tile layer is rendered to an off screen canvas
3033
- * <br> - To allow dynamic modifications, layers are rendered using canvas 2d
3034
- * <br> - Some devices like mobile phones are limited to 4k texture resolution
3035
- * <br> - So with 16x16 tiles this limits layers to 256x256 on mobile devices
3083
+ * - Each Tile layer is rendered to an off screen canvas
3084
+ * - To allow dynamic modifications, layers are rendered using canvas 2d
3085
+ * - Some devices like mobile phones are limited to 4k texture resolution
3086
+ * - So with 16x16 tiles this limits layers to 256x256 on mobile devices
3036
3087
  * @extends EngineObject
3037
3088
  * @example
3038
3089
  * // create tile collision and visible tile layer
@@ -3232,12 +3283,9 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3232
3283
  drawRect(pos, size, color, angle)
3233
3284
  { this.drawTile(pos, size, -1, 0, color, angle); }
3234
3285
  }
3235
- /*
3236
- LittleJS Particle System
3237
- - Spawns particles with randomness from parameters
3238
- - Updates particle physics
3239
- - Fast particle rendering
3240
- */
3286
+ /**
3287
+ * LittleJS Particle System
3288
+ */
3241
3289
 
3242
3290
  'use strict';
3243
3291
 
@@ -3543,9 +3591,9 @@ class Particle extends EngineObject
3543
3591
  }
3544
3592
  /**
3545
3593
  * LittleJS Medal System
3546
- * <br> - Tracks and displays medals
3547
- * <br> - Saves medals to local storage
3548
- * <br> - Newgrounds integration
3594
+ * - Tracks and displays medals
3595
+ * - Saves medals to local storage
3596
+ * - Newgrounds integration
3549
3597
  * @namespace Medals
3550
3598
  */
3551
3599
 
@@ -3562,8 +3610,8 @@ let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
3562
3610
  ///////////////////////////////////////////////////////////////////////////////
3563
3611
 
3564
3612
  /** Initialize medals with a save name used for storage
3565
- * <br> - Call this after creating all medals
3566
- * <br> - Checks if medals are unlocked
3613
+ * - Call this after creating all medals
3614
+ * - Checks if medals are unlocked
3567
3615
  * @param {String} saveName
3568
3616
  * @memberof Medals */
3569
3617
  function medalsInit(saveName)
@@ -3833,20 +3881,43 @@ class Newgrounds
3833
3881
  CryptoJS()
3834
3882
  {
3835
3883
  ///////////////////////////////////////////////////////////////////////////////
3836
- // Crypto-JS - https://github.com/brix/crypto-js [The MIT License (MIT)]
3837
- // Copyright (c) 2009-2013 Jeff Mott Copyright (c) 2013-2016 Evan Vosberg
3838
-
3884
+ // Crypto-JS - https://github.com/brix/crypto-js - MIT License
3885
+ //
3886
+ // [The MIT License (MIT)](http://opensource.org/licenses/MIT)
3887
+ //
3888
+ // Copyright (c) 2009-2013 Jeff Mott
3889
+ // Copyright (c) 2013-2016 Evan Vosberg
3890
+ //
3891
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
3892
+ // of this software and associated documentation files (the "Software"), to deal
3893
+ // in the Software without restriction, including without limitation the rights
3894
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
3895
+ // copies of the Software, and to permit persons to whom the Software is
3896
+ // furnished to do so, subject to the following conditions:
3897
+ //
3898
+ // The above copyright notice and this permission notice shall be included in
3899
+ // all copies or substantial portions of the Software.
3900
+ //
3901
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
3902
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
3903
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
3904
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
3905
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3906
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
3907
+ // THE SOFTWARE.
3839
3908
  return 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));
3909
+ // end of Crypto-JS
3910
+ ///////////////////////////////////////////////////////////////////////////////
3840
3911
  }
3841
3912
  }
3842
3913
  /**
3843
3914
  * LittleJS WebGL Interface
3844
- * <br> - All webgl used by the engine is wrapped up here
3845
- * <br> - For normal stuff you won't need to see or call anything in this file
3846
- * <br> - For advanced stuff there are helper functions to create shaders, textures, etc
3847
- * <br> - Can be disabled with glEnable to revert to 2D canvas rendering
3848
- * <br> - Batches sprite rendering on GPU for incredibly fast performance
3849
- * <br> - Sprite transform math is done in the shader where possible
3915
+ * - All webgl used by the engine is wrapped up here
3916
+ * - For normal stuff you won't need to see or call anything in this file
3917
+ * - For advanced stuff there are helper functions to create shaders, textures, etc
3918
+ * - Can be disabled with glEnable to revert to 2D canvas rendering
3919
+ * - Batches sprite rendering on GPU for incredibly fast performance
3920
+ * - Sprite transform math is done in the shader where possible
3850
3921
  * @namespace WebGL
3851
3922
  */
3852
3923
 
@@ -3921,7 +3992,7 @@ function glSetBlendMode(additive)
3921
3992
  }
3922
3993
 
3923
3994
  /** Set the WebGl texture, not normally necessary unless multiple tile sheets are used
3924
- * <br> - This may also flush the gl buffer resulting in more draw calls and worse performance
3995
+ * - This may also flush the gl buffer resulting in more draw calls and worse performance
3925
3996
  * @param {WebGLTexture} [texture=glTileTexture]
3926
3997
  * @memberof WebGL */
3927
3998
  function glSetTexture(texture=glTileTexture)
@@ -3982,7 +4053,7 @@ function glCreateTexture(image)
3982
4053
  image && image.width && glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
3983
4054
 
3984
4055
  // use point filtering for pixelated rendering
3985
- const filter = cavasPixelated ? gl_NEAREST : gl_LINEAR;
4056
+ const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
3986
4057
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
3987
4058
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
3988
4059
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE);
@@ -4233,27 +4304,23 @@ gl_INDICIES_PER_VERT = 6,
4233
4304
  gl_MAX_BATCH = 1<<16,
4234
4305
  gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
4235
4306
  gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE;
4236
- /*
4237
- LittleJS - The Tiny JavaScript Game Engine That Can!
4238
- MIT License - Copyright 2021 Frank Force
4239
-
4240
- Engine Features
4241
- - Object oriented system with base class engine object
4242
- - Base class object handles update, physics, collision, rendering, etc
4243
- - Engine helper classes and functions like Vector2, Color, and Timer
4244
- - Super fast rendering system for tile sheets
4245
- - Sound effects audio with zzfx and music with zzfxm
4246
- - Input processing system with gamepad and touchscreen support
4247
- - Tile layer rendering and collision system
4248
- - Particle effect system
4249
- - Medal system tracks and displays achievements
4250
- - Debug tools and debug rendering system
4251
- - Post processing effects
4252
- - Call engineInit() to start it up!
4253
- */
4254
-
4255
- /**
4256
- * LittleJS Engine Globals
4307
+ /**
4308
+ * LittleJS - The Tiny JavaScript Game Engine That Can!
4309
+ * MIT License - Copyright 2021 Frank Force
4310
+ *
4311
+ * Engine Features
4312
+ * - Object oriented system with base class engine object
4313
+ * - Base class object handles update, physics, collision, rendering, etc
4314
+ * - Engine helper classes and functions like Vector2, Color, and Timer
4315
+ * - Super fast rendering system for tile sheets
4316
+ * - Sound effects audio with zzfx and music with zzfxm
4317
+ * - Input processing system with gamepad and touchscreen support
4318
+ * - Tile layer rendering and collision system
4319
+ * - Particle effect system
4320
+ * - Medal system tracks and displays achievements
4321
+ * - Debug tools and debug rendering system
4322
+ * - Post processing effects
4323
+ * - Call engineInit() to start it up!
4257
4324
  * @namespace Engine
4258
4325
  */
4259
4326
 
@@ -4269,7 +4336,7 @@ const engineName = 'LittleJS';
4269
4336
  * @type {String}
4270
4337
  * @default
4271
4338
  * @memberof Engine */
4272
- const engineVersion = '1.6.0';
4339
+ const engineVersion = '1.6.6';
4273
4340
 
4274
4341
  /** Frames per second to update objects
4275
4342
  * @type {Number}
@@ -4339,10 +4406,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4339
4406
  debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
4340
4407
 
4341
4408
  // setup html
4342
- const styleBody = 'margin:0;overflow:hidden;background:#000' + // fill the window
4343
- ';touch-action:none' + // prevent mobile pinch to resize
4344
- ';user-select:none' + // prevent mobile hold to select
4345
- ';-webkit-user-select:none'; // compatibility for ios
4409
+ const styleBody = 'margin:0;overflow:hidden;' + // fill the window
4410
+ 'background:#000;' + // set background color
4411
+ 'touch-action:none;' + // prevent mobile pinch to resize
4412
+ 'user-select:none;' + // prevent mobile hold to select
4413
+ '-webkit-user-select:none'; // compatibility for ios
4346
4414
  document.body.style = styleBody;
4347
4415
  document.body.appendChild(mainCanvas = document.createElement('canvas'));
4348
4416
  mainContext = mainCanvas.getContext('2d');
@@ -4355,12 +4423,13 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4355
4423
  document.body.appendChild(overlayCanvas = document.createElement('canvas'));
4356
4424
  overlayContext = overlayCanvas.getContext('2d');
4357
4425
 
4358
- // set canvas style to fill the window
4359
- const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
4426
+ // set canvas style
4427
+ const styleCanvas = 'position:absolute;' +
4428
+ 'top:50%;left:50%;transform:translate(-50%,-50%);' + // center the canvas
4429
+ (canvasPixelated?'image-rendering:pixelated':''); // set pixelated rendering
4360
4430
  (glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
4361
4431
 
4362
4432
  gameInit();
4363
- touchGamepadCreate();
4364
4433
  engineUpdate();
4365
4434
  };
4366
4435
 
@@ -4490,7 +4559,7 @@ function enginePreRender()
4490
4559
  mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
4491
4560
 
4492
4561
  // disable smoothing for pixel art
4493
- mainContext.imageSmoothingEnabled = !cavasPixelated;
4562
+ mainContext.imageSmoothingEnabled = !canvasPixelated;
4494
4563
 
4495
4564
  // setup gl rendering if enabled
4496
4565
  glEnable && glPreRender();
@@ -4504,7 +4573,7 @@ function engineObjectsUpdate()
4504
4573
  engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
4505
4574
 
4506
4575
  // recursive object update
4507
- const updateObject = (o)=>
4576
+ function updateObject(o)
4508
4577
  {
4509
4578
  if (!o.destroyed)
4510
4579
  {