littlejsengine 1.8.8 → 1.9.0

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 (47) hide show
  1. package/README.md +17 -17
  2. package/build/littlejs.d.ts +316 -254
  3. package/build/littlejs.esm.js +727 -676
  4. package/build/littlejs.esm.min.js +1 -1
  5. package/build/littlejs.js +726 -676
  6. package/build/littlejs.min.js +1 -1
  7. package/build/littlejs.release.js +661 -612
  8. package/examples/breakout/game.js +4 -4
  9. package/examples/breakout/index.html +3 -3
  10. package/examples/breakoutTutorial/index.html +2 -2
  11. package/examples/electron/game.js +1 -1
  12. package/examples/electron/index.html +2 -2
  13. package/examples/favicon.png +0 -0
  14. package/examples/js13k/index.html +13 -13
  15. package/examples/logo.png +0 -0
  16. package/examples/module/game.js +1 -1
  17. package/examples/module/index.html +1 -1
  18. package/examples/particles/index.html +1 -1
  19. package/examples/platformer/game.js +6 -6
  20. package/examples/platformer/gameCharacter.js +293 -0
  21. package/examples/platformer/gameEffects.js +8 -5
  22. package/examples/platformer/gameObjects.js +13 -13
  23. package/examples/platformer/gamePlayer.js +9 -293
  24. package/examples/platformer/index.html +7 -6
  25. package/examples/puzzle/game.js +3 -2
  26. package/examples/puzzle/index.html +2 -2
  27. package/examples/starter/game.js +3 -3
  28. package/examples/starter/index.html +13 -13
  29. package/examples/starter/tiles.png +0 -0
  30. package/examples/stress/index.html +34 -26
  31. package/examples/typescript/index.html +1 -1
  32. package/package.json +1 -1
  33. package/src/engine.js +50 -47
  34. package/src/engineAudio.js +126 -112
  35. package/src/engineDebug.js +66 -65
  36. package/src/engineDraw.js +47 -56
  37. package/src/engineExport.js +1 -0
  38. package/src/engineInput.js +57 -40
  39. package/src/engineMedals.js +32 -29
  40. package/src/engineObject.js +41 -26
  41. package/src/engineParticles.js +101 -75
  42. package/src/engineRelease.js +1 -1
  43. package/src/engineSettings.js +22 -22
  44. package/src/engineTileLayer.js +34 -33
  45. package/src/engineUtilities.js +44 -44
  46. package/src/engineWebGL.js +105 -126
  47. package/src/jsconfig.json +10 -0
package/build/littlejs.js CHANGED
@@ -18,13 +18,13 @@
18
18
  * @type {Boolean}
19
19
  * @default
20
20
  * @memberof Debug */
21
- const debug = 1;
21
+ const debug = true;
22
22
 
23
23
  /** True if asserts are enaled
24
24
  * @type {Boolean}
25
25
  * @default
26
26
  * @memberof Debug */
27
- const enableAsserts = 1;
27
+ const enableAsserts = true;
28
28
 
29
29
  /** Size to render debug points by default
30
30
  * @type {Number}
@@ -36,82 +36,87 @@ const debugPointSize = .5;
36
36
  * @type {Boolean}
37
37
  * @default
38
38
  * @memberof Debug */
39
- let showWatermark = 1;
39
+ let showWatermark = true;
40
40
 
41
41
  /** Key code used to toggle debug mode, Esc by default
42
+ * @type {String}
43
+ * @default
44
+ * @memberof Debug */
45
+ let debugKey = 'Escape';
46
+
47
+ /** True if the debug overlay is active, always false in release builds
42
48
  * @type {Boolean}
43
49
  * @default
44
50
  * @memberof Debug */
45
- let debugKey = 27;
51
+ let debugOverlay = false;
46
52
 
47
53
  // Engine internal variables not exposed to documentation
48
- let debugPrimitives = [], debugOverlay = 0, debugPhysics = 0, debugRaycast = 0,
49
- debugParticles = 0, debugGamepads = 0, debugMedals = 0, debugTakeScreenshot, downloadLink;
54
+ let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParticles = false, debugGamepads = false, debugMedals = false, debugTakeScreenshot, downloadLink;
50
55
 
51
56
  ///////////////////////////////////////////////////////////////////////////////
52
57
  // Debug helper functions
53
58
 
54
59
  /** Asserts if the experssion is false, does not do anything in release builds
55
- * @param {Boolean} assertion
56
- * @param {Object} output
60
+ * @param {Boolean} assert
61
+ * @param {Object} output
57
62
  * @memberof Debug */
58
- function ASSERT(...assert) { enableAsserts && console.assert(...assert); }
63
+ function ASSERT(assert, output) { enableAsserts && console.assert(assert, output); }
59
64
 
60
65
  /** Draw a debug rectangle in world space
61
66
  * @param {Vector2} pos
62
67
  * @param {Vector2} [size=Vector2()]
63
- * @param {String} [color='#fff']
64
- * @param {Number} [time=0]
65
- * @param {Number} [angle=0]
66
- * @param {Boolean} [fill=false]
68
+ * @param {String} [color]
69
+ * @param {Number} [time]
70
+ * @param {Number} [angle]
71
+ * @param {Boolean} [fill]
67
72
  * @memberof Debug */
68
73
  function debugRect(pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)
69
74
  {
70
- ASSERT(typeof color == 'string'); // pass in regular html strings as colors
75
+ ASSERT(typeof color == 'string', 'pass in css color strings');
71
76
  debugPrimitives.push({pos, size:vec2(size), color, time:new Timer(time), angle, fill});
72
77
  }
73
78
 
74
79
  /** Draw a debug circle in world space
75
80
  * @param {Vector2} pos
76
- * @param {Number} [radius=0]
77
- * @param {String} [color='#fff']
78
- * @param {Number} [time=0]
79
- * @param {Boolean} [fill=false]
81
+ * @param {Number} [radius]
82
+ * @param {String} [color]
83
+ * @param {Number} [time]
84
+ * @param {Boolean} [fill]
80
85
  * @memberof Debug */
81
86
  function debugCircle(pos, radius=0, color='#fff', time=0, fill=false)
82
87
  {
83
- ASSERT(typeof color == 'string'); // pass in regular html strings as colors
88
+ ASSERT(typeof color == 'string', 'pass in css color strings');
84
89
  debugPrimitives.push({pos, size:radius, color, time:new Timer(time), angle:0, fill});
85
90
  }
86
91
 
87
92
  /** Draw a debug point in world space
88
93
  * @param {Vector2} pos
89
- * @param {String} [color='#fff']
90
- * @param {Number} [time=0]
91
- * @param {Number} [angle=0]
94
+ * @param {String} [color]
95
+ * @param {Number} [time]
96
+ * @param {Number} [angle]
92
97
  * @memberof Debug */
93
- function debugPoint(pos, color, time, angle) {debugRect(pos, 0, color, time, angle);}
98
+ function debugPoint(pos, color, time, angle) {debugRect(pos, undefined, color, time, angle);}
94
99
 
95
100
  /** Draw a debug line in world space
96
101
  * @param {Vector2} posA
97
102
  * @param {Vector2} posB
98
- * @param {String} [color='#fff']
99
- * @param {Number} [thickness=.1]
100
- * @param {Number} [time=0]
103
+ * @param {String} [color]
104
+ * @param {Number} [thickness]
105
+ * @param {Number} [time]
101
106
  * @memberof Debug */
102
107
  function debugLine(posA, posB, color, thickness=.1, time)
103
108
  {
104
109
  const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
105
110
  const size = vec2(thickness, halfDelta.length()*2);
106
- debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), 1);
111
+ debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), true);
107
112
  }
108
113
 
109
114
  /** Draw a debug axis aligned bounding box in world space
110
- * @param {Vector2} posA
111
- * @param {Vector2} sizeA
112
- * @param {Vector2} posB
113
- * @param {Vector2} sizeB
114
- * @param {String} [color='#fff']
115
+ * @param {Vector2} pA - position A
116
+ * @param {Vector2} sA - size A
117
+ * @param {Vector2} pB - position B
118
+ * @param {Vector2} sB - size B
119
+ * @param {String} [color]
115
120
  * @memberof Debug */
116
121
  function debugAABB(pA, sA, pB, sB, color)
117
122
  {
@@ -123,15 +128,15 @@ function debugAABB(pA, sA, pB, sB, color)
123
128
  /** Draw a debug axis aligned bounding box in world space
124
129
  * @param {String} text
125
130
  * @param {Vector2} pos
126
- * @param {Number} [size=1]
127
- * @param {String} [color='#fff']
128
- * @param {Number} [time=0]
129
- * @param {Number} [angle=0]
130
- * @param {String} [font='monospace']
131
+ * @param {Number} [size]
132
+ * @param {String} [color]
133
+ * @param {Number} [time]
134
+ * @param {Number} [angle]
135
+ * @param {String} [font]
131
136
  * @memberof Debug */
132
137
  function debugText(text, pos, size=1, color='#fff', time=0, angle=0, font='monospace')
133
138
  {
134
- ASSERT(typeof color == 'string'); // pass in regular html strings as colors
139
+ ASSERT(typeof color == 'string', 'pass in css color strings');
135
140
  debugPrimitives.push({text, pos, size, color, time:new Timer(time), angle, font});
136
141
  }
137
142
 
@@ -142,7 +147,7 @@ function debugClear() { debugPrimitives = []; }
142
147
  /** Save a canvas to disk
143
148
  * @param {HTMLCanvasElement} canvas
144
149
  * @param {String} [filename]
145
- * @param {String} [type='image/png']
150
+ * @param {String} [type]
146
151
  * @memberof Debug */
147
152
  function debugSaveCanvas(canvas, filename=engineName, type='image/png')
148
153
  { debugSaveDataURL(canvas.toDataURL(type), filename); }
@@ -150,7 +155,7 @@ function debugSaveCanvas(canvas, filename=engineName, type='image/png')
150
155
  /** Save a text file to disk
151
156
  * @param {String} text
152
157
  * @param {String} [filename]
153
- * @param {String} [type='text/plain']
158
+ * @param {String} [type]
154
159
  * @memberof Debug */
155
160
  function debugSaveText(text, filename=engineName, type='text/plain')
156
161
  { debugSaveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); }
@@ -185,22 +190,18 @@ function debugUpdate()
185
190
  debugOverlay = !debugOverlay;
186
191
  if (debugOverlay)
187
192
  {
188
- if (keyWasPressed(48)) // 0
193
+ if (keyWasPressed('Digit0'))
189
194
  showWatermark = !showWatermark;
190
- if (keyWasPressed(49)) // 1
191
- debugPhysics = !debugPhysics, debugParticles = 0;
192
- if (keyWasPressed(50)) // 2
193
- debugParticles = !debugParticles, debugPhysics = 0;
194
- if (keyWasPressed(51)) // 3
195
+ if (keyWasPressed('Digit1'))
196
+ debugPhysics = !debugPhysics, debugParticles = false;
197
+ if (keyWasPressed('Digit2'))
198
+ debugParticles = !debugParticles, debugPhysics = false;
199
+ if (keyWasPressed('Digit3'))
195
200
  debugGamepads = !debugGamepads;
196
- if (keyWasPressed(52)) // 4
201
+ if (keyWasPressed('Digit4'))
197
202
  debugRaycast = !debugRaycast;
198
- if (keyWasPressed(53)) // 5
203
+ if (keyWasPressed('Digit5'))
199
204
  debugTakeScreenshot = 1;
200
- //if (keyWasPressed(54)) // 6
201
- //if (keyWasPressed(55)) // 7
202
- //if (keyWasPressed(56)) // 8
203
- //if (keyWasPressed(57)) // 9
204
205
  }
205
206
  }
206
207
 
@@ -211,7 +212,7 @@ function debugRender()
211
212
  if (debugTakeScreenshot)
212
213
  {
213
214
  // composite canvas
214
- glCopyToContext(mainContext, 1);
215
+ glCopyToContext(mainContext, true);
215
216
  mainContext.drawImage(overlayCanvas, 0, 0);
216
217
  overlayCanvas.width |= 0;
217
218
 
@@ -240,7 +241,7 @@ function debugRender()
240
241
  {
241
242
  const drawPos = centerPos.add(vec2(j*stickScale*2, i*stickScale*3));
242
243
  const stickPos = drawPos.add(sticks[j].scale(stickScale));
243
- debugCircle(drawPos, stickScale, '#fff7',0,1);
244
+ debugCircle(drawPos, stickScale, '#fff7',0,true);
244
245
  debugLine(drawPos, stickPos, '#f00');
245
246
  debugPoint(stickPos, '#f00');
246
247
  }
@@ -248,8 +249,8 @@ function debugRender()
248
249
  {
249
250
  const drawPos = centerPos.add(vec2(j*buttonScale*2, i*stickScale*3-stickScale-buttonScale));
250
251
  const pressed = gamepad.buttons[j].pressed;
251
- debugCircle(drawPos, buttonScale, pressed ? '#f00' : '#fff7', 0, 1);
252
- debugText(j, drawPos, .2);
252
+ debugCircle(drawPos, buttonScale, pressed ? '#f00' : '#fff7', 0, true);
253
+ debugText(''+j, drawPos, .2);
253
254
  }
254
255
  }
255
256
  }
@@ -278,25 +279,25 @@ function debugRender()
278
279
 
279
280
  // show object info
280
281
  const size = vec2(max(o.size.x, .2), max(o.size.y, .2));
281
- const color1 = new Color(!!o.collideTiles, !!o.collideSolidObjects, !!o.isSolid, o.parent?.2:.5);
282
+ const color1 = new Color(o.collideTiles?1:0, o.collideSolidObjects?1:0, o.isSolid?1:0, o.parent?.2:.5);
282
283
  const color2 = o.parent ? new Color(1,1,1,.5) : new Color(0,0,0,.8);
283
- drawRect(o.pos, size, color1, o.angle, 0);
284
- drawRect(o.pos, size.scale(.8), color2, o.angle, 0);
285
- o.parent && drawLine(o.pos, o.parent.pos, .1, new Color(0,0,1,.5), 0);
284
+ drawRect(o.pos, size, color1, o.angle, false);
285
+ drawRect(o.pos, size.scale(.8), color2, o.angle, false);
286
+ o.parent && drawLine(o.pos, o.parent.pos, .1, new Color(0,0,1,.5), false);
286
287
  }
287
288
 
288
289
  if (bestObject)
289
290
  {
290
291
  const raycastHitPos = tileCollisionRaycast(bestObject.pos, mousePos);
291
292
  raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), new Color(0,1,1,.3));
292
- drawRect(mousePos.floor().add(vec2(.5)), vec2(1), new Color(0,0,1,.5), 0, 0);
293
- drawLine(mousePos, bestObject.pos, .1, raycastHitPos ? new Color(1,0,0,.5) : new Color(0,1,0,.5), 0);
293
+ drawRect(mousePos.floor().add(vec2(.5)), vec2(1), new Color(0,0,1,.5), 0, false);
294
+ drawLine(mousePos, bestObject.pos, .1, raycastHitPos ? new Color(1,0,0,.5) : new Color(0,1,0,.5), false);
294
295
 
295
296
  const debugText = 'mouse pos = ' + mousePos +
296
297
  '\nmouse collision = ' + getTileCollisionData(mousePos) +
297
298
  '\n\n--- object info ---\n' +
298
299
  bestObject.toString();
299
- drawTextScreen(debugText, mousePosScreen, 24, new Color, .05, 0, 0, 'monospace');
300
+ drawTextScreen(debugText, mousePosScreen, 24, new Color, .05, undefined, 'center', 'monospace');
300
301
  }
301
302
 
302
303
  glCopyToContext(mainContext = saveContext);
@@ -385,7 +386,7 @@ function debugRender()
385
386
  let keysPressed = '';
386
387
  for(const i in inputData[0])
387
388
  {
388
- if (i && keyIsDown(i, 0))
389
+ if (keyIsDown(i, 0))
389
390
  keysPressed += i + ' ' ;
390
391
  }
391
392
  keysPressed && overlayContext.fillText('Keys Down: ' + keysPressed, x, y += h);
@@ -394,7 +395,7 @@ function debugRender()
394
395
  if (inputData[1])
395
396
  for(const i in inputData[1])
396
397
  {
397
- if (i && keyIsDown(i, 1))
398
+ if (keyIsDown(i, 1))
398
399
  buttonsPressed += i + ' ' ;
399
400
  }
400
401
  buttonsPressed && overlayContext.fillText('Gamepad: ' + buttonsPressed, x, y += h);
@@ -456,15 +457,15 @@ function sign(value) { return Math.sign(value); }
456
457
 
457
458
  /** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
458
459
  * @param {Number} dividend
459
- * @param {Number} [divisor=1]
460
+ * @param {Number} [divisor]
460
461
  * @return {Number}
461
462
  * @memberof Utilities */
462
463
  function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
463
464
 
464
465
  /** Clamps the value beween max and min
465
466
  * @param {Number} value
466
- * @param {Number} [min=0]
467
- * @param {Number} [max=1]
467
+ * @param {Number} [min]
468
+ * @param {Number} [max]
468
469
  * @return {Number}
469
470
  * @memberof Utilities */
470
471
  function clamp(value, min=0, max=1) { return value < min ? min : value > max ? max : value; }
@@ -489,7 +490,7 @@ function lerp(percent, valueA, valueB) { return valueA + clamp(percent) * (value
489
490
  /** Returns signed wrapped distance between the two values passed in
490
491
  * @param {Number} valueA
491
492
  * @param {Number} valueB
492
- * @param {Number} [wrapSize=1]
493
+ * @param {Number} [wrapSize]
493
494
  * @returns {Number}
494
495
  * @memberof Utilities */
495
496
  function distanceWrap(valueA, valueB, wrapSize=1)
@@ -499,7 +500,7 @@ function distanceWrap(valueA, valueB, wrapSize=1)
499
500
  * @param {Number} percent
500
501
  * @param {Number} valueA
501
502
  * @param {Number} valueB
502
- * @param {Number} [wrapSize=1]
503
+ * @param {Number} [wrapSize]
503
504
  * @returns {Number}
504
505
  * @memberof Utilities */
505
506
  function lerpWrap(percent, valueA, valueB, wrapSize=1)
@@ -510,7 +511,7 @@ function lerpWrap(percent, valueA, valueB, wrapSize=1)
510
511
  * @param {Number} angleB
511
512
  * @returns {Number}
512
513
  * @memberof Utilities */
513
- function distanceAngle(angleA, angleB) { distanceWrap(angleA, angleB, 2*PI); }
514
+ function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*PI); }
514
515
 
515
516
  /** Linearly interpolates between the angles passed in with wrappping
516
517
  * @param {Number} percent
@@ -546,10 +547,10 @@ function isOverlapping(pointA, sizeA, pointB, sizeB)
546
547
  }
547
548
 
548
549
  /** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
549
- * @param {Number} [frequency=1] - Frequency of the wave in Hz
550
- * @param {Number} [amplitude=1] - Amplitude (max height) of the wave
551
- * @param {Number} [t=time] - Value to use for time of the wave
552
- * @return {Number} - Value waving between 0 and amplitude
550
+ * @param {Number} [frequency] - Frequency of the wave in Hz
551
+ * @param {Number} [amplitude] - Amplitude (max height) of the wave
552
+ * @param {Number} [t=time] - Value to use for time of the wave
553
+ * @return {Number} - Value waving between 0 and amplitude
553
554
  * @memberof Utilities */
554
555
  function wave(frequency=1, amplitude=1, t=time)
555
556
  { return amplitude/2 * (1 - Math.cos(t*frequency*2*PI)); }
@@ -566,15 +567,15 @@ function formatTime(t) { return (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0); }
566
567
  * @namespace Random */
567
568
 
568
569
  /** Returns a random value between the two values passed in
569
- * @param {Number} [valueA=1]
570
- * @param {Number} [valueB=0]
570
+ * @param {Number} [valueA]
571
+ * @param {Number} [valueB]
571
572
  * @return {Number}
572
573
  * @memberof Random */
573
574
  function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valueB); }
574
575
 
575
576
  /** Returns a floored random value the two values passed in
576
577
  * @param {Number} valueA
577
- * @param {Number} [valueB=0]
578
+ * @param {Number} [valueB]
578
579
  * @return {Number}
579
580
  * @memberof Random */
580
581
  function randInt(valueA, valueB=0) { return Math.floor(rand(valueA,valueB)); }
@@ -585,14 +586,14 @@ function randInt(valueA, valueB=0) { return Math.floor(rand(valueA,valueB)); }
585
586
  function randSign() { return randInt(2) * 2 - 1; }
586
587
 
587
588
  /** Returns a random Vector2 with the passed in length
588
- * @param {Number} [length=1]
589
+ * @param {Number} [length]
589
590
  * @return {Vector2}
590
591
  * @memberof Random */
591
592
  function randVector(length=1) { return new Vector2().setAngle(rand(2*PI), length); }
592
593
 
593
594
  /** Returns a random Vector2 within a circular shape
594
- * @param {Number} [radius=1]
595
- * @param {Number} [minRadius=0]
595
+ * @param {Number} [radius]
596
+ * @param {Number} [minRadius]
596
597
  * @return {Vector2}
597
598
  * @memberof Random */
598
599
  function randInCircle(radius=1, minRadius=0)
@@ -604,7 +605,7 @@ function randInCircle(radius=1, minRadius=0)
604
605
  * @param {Boolean} [linear]
605
606
  * @return {Color}
606
607
  * @memberof Random */
607
- function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear)
608
+ function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
608
609
  {
609
610
  return linear ? colorA.lerp(colorB, rand()) :
610
611
  new Color(rand(colorA.r,colorB.r), rand(colorA.g,colorB.g), rand(colorA.b,colorB.b), rand(colorA.a,colorB.a));
@@ -633,8 +634,8 @@ class RandomGenerator
633
634
  }
634
635
 
635
636
  /** Returns a seeded random value between the two values passed in
636
- * @param {Number} [valueA=1]
637
- * @param {Number} [valueB=0]
637
+ * @param {Number} [valueA]
638
+ * @param {Number} [valueB]
638
639
  * @return {Number} */
639
640
  float(valueA=1, valueB=0)
640
641
  {
@@ -647,7 +648,7 @@ class RandomGenerator
647
648
 
648
649
  /** Returns a floored seeded random value the two values passed in
649
650
  * @param {Number} valueA
650
- * @param {Number} [valueB=0]
651
+ * @param {Number} [valueB]
651
652
  * @return {Number} */
652
653
  int(valueA, valueB=0) { return Math.floor(this.float(valueA, valueB)); }
653
654
 
@@ -660,8 +661,8 @@ class RandomGenerator
660
661
 
661
662
  /**
662
663
  * Create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
663
- * @param {(Number|Vector2)} [x=0]
664
- * @param {Number} [y=0]
664
+ * @param {(Number|Vector2)} [x]
665
+ * @param {Number} [y]
665
666
  * @return {Vector2}
666
667
  * @example
667
668
  * let a = vec2(0, 1); // vector with coordinates (0, 1)
@@ -671,15 +672,15 @@ class RandomGenerator
671
672
  * @memberof Utilities
672
673
  */
673
674
  function vec2(x=0, y)
674
- { return x.x == undefined ? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y); }
675
+ { return typeof x === 'number'? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y); }
675
676
 
676
677
  /**
677
678
  * Check if object is a valid Vector2
678
- * @param {Vector2} v
679
+ * @param {any} v
679
680
  * @return {Boolean}
680
681
  * @memberof Utilities
681
682
  */
682
- function isVector2(v) { return !isNaN(v.x) && !isNaN(v.y); }
683
+ function isVector2(v) { return typeof v === 'object' && typeof v.x === 'number' && typeof v.y === 'number'; }
683
684
 
684
685
  /**
685
686
  * 2D Vector object with vector math library
@@ -693,8 +694,8 @@ function isVector2(v) { return !isNaN(v.x) && !isNaN(v.y); }
693
694
  class Vector2
694
695
  {
695
696
  /** Create a 2D vector with the x and y passed in, can also be created with vec2()
696
- * @param {Number} [x=0] - X axis location
697
- * @param {Number} [y=0] - Y axis location */
697
+ * @param {Number} [x] - X axis location
698
+ * @param {Number} [y] - Y axis location */
698
699
  constructor(x=0, y=0)
699
700
  {
700
701
  /** @property {Number} - X axis location */
@@ -751,12 +752,12 @@ class Vector2
751
752
  distanceSquared(v) { return (this.x - v.x)**2 + (this.y - v.y)**2; }
752
753
 
753
754
  /** Returns a new vector in same direction as this one with the length passed in
754
- * @param {Number} [length=1]
755
+ * @param {Number} [length]
755
756
  * @return {Vector2} */
756
757
  normalize(length=1) { const l = this.length(); return l ? this.scale(length/l) : new Vector2(0, length); }
757
758
 
758
759
  /** Returns a new vector clamped to length passed in
759
- * @param {Number} [length=1]
760
+ * @param {Number} [length]
760
761
  * @return {Vector2} */
761
762
  clampLength(length=1) { const l = this.length(); return l > length ? this.scale(length/l) : this; }
762
763
 
@@ -775,8 +776,8 @@ class Vector2
775
776
  angle() { return Math.atan2(this.x, this.y); }
776
777
 
777
778
  /** Sets this vector with angle and length passed in
778
- * @param {Number} [angle=0]
779
- * @param {Number} [length=1]
779
+ * @param {Number} [angle]
780
+ * @param {Number} [length]
780
781
  * @return {Vector2} */
781
782
  setAngle(angle=0, length=1)
782
783
  { this.x = length*Math.sin(angle); this.y = length*Math.cos(angle); return this; }
@@ -861,10 +862,10 @@ function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
861
862
  class Color
862
863
  {
863
864
  /** Create a color with the rgba components passed in, white by default
864
- * @param {Number} [r=1] - red
865
- * @param {Number} [g=1] - green
866
- * @param {Number} [b=1] - blue
867
- * @param {Number} [a=1] - alpha*/
865
+ * @param {Number} [r] - red
866
+ * @param {Number} [g] - green
867
+ * @param {Number} [b] - blue
868
+ * @param {Number} [a] - alpha*/
868
869
  constructor(r=1, g=1, b=1, a=1)
869
870
  {
870
871
  /** @property {Number} - Red */
@@ -919,10 +920,10 @@ class Color
919
920
  lerp(c, percent) { return this.add(c.subtract(this).scale(clamp(percent))); }
920
921
 
921
922
  /** Sets this color given a hue, saturation, lightness, and alpha
922
- * @param {Number} [h=0] - hue
923
- * @param {Number} [s=0] - saturation
924
- * @param {Number} [l=1] - lightness
925
- * @param {Number} [a=1] - alpha
923
+ * @param {Number} [h] - hue
924
+ * @param {Number} [s] - saturation
925
+ * @param {Number} [l] - lightness
926
+ * @param {Number} [a] - alpha
926
927
  * @return {Color} */
927
928
  setHSLA(h=0, s=0, l=1, a=1)
928
929
  {
@@ -968,8 +969,8 @@ class Color
968
969
  }
969
970
 
970
971
  /** Returns a new color that has each component randomly adjusted
971
- * @param {Number} [amount=.05]
972
- * @param {Number} [alphaAmount=0]
972
+ * @param {Number} [amount]
973
+ * @param {Number} [alphaAmount]
973
974
  * @return {Color} */
974
975
  mutate(amount=.05, alphaAmount=0)
975
976
  {
@@ -983,9 +984,9 @@ class Color
983
984
  }
984
985
 
985
986
  /** Returns this color expressed as a hex color code
986
- * @param {Boolean} [useAlpha=1] - if alpha should be included in result
987
+ * @param {Boolean} [useAlpha] - if alpha should be included in result
987
988
  * @return {String} */
988
- toString(useAlpha = 1)
989
+ toString(useAlpha = true)
989
990
  {
990
991
  const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
991
992
  return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
@@ -1034,7 +1035,7 @@ class Timer
1034
1035
  constructor(timeLeft) { this.time = timeLeft == undefined ? undefined : time + timeLeft; this.setTime = timeLeft; }
1035
1036
 
1036
1037
  /** Set the timer with seconds passed in
1037
- * @param {Number} [timeLeft=0] - How much time left before the timer is elapsed in seconds */
1038
+ * @param {Number} [timeLeft] - How much time left before the timer is elapsed in seconds */
1038
1039
  set(timeLeft=0) { this.time = time + timeLeft; this.setTime = timeLeft; }
1039
1040
 
1040
1041
  /** Unset the timer */
@@ -1111,7 +1112,7 @@ let canvasFixedSize = vec2();
1111
1112
  * @type {Boolean}
1112
1113
  * @default
1113
1114
  * @memberof Settings */
1114
- let canvasPixelated = 1;
1115
+ let canvasPixelated = true;
1115
1116
 
1116
1117
  /** Default font used for text rendering
1117
1118
  * @type {String}
@@ -1123,7 +1124,7 @@ let fontDefault = 'arial';
1123
1124
  * @type {Boolean}
1124
1125
  * @default
1125
1126
  * @memberof Settings */
1126
- let showSplashScreen = 0;
1127
+ let showSplashScreen = false;
1127
1128
 
1128
1129
  ///////////////////////////////////////////////////////////////////////////////
1129
1130
  // WebGL settings
@@ -1132,13 +1133,13 @@ let showSplashScreen = 0;
1132
1133
  * @type {Boolean}
1133
1134
  * @default
1134
1135
  * @memberof Settings */
1135
- let glEnable = 1;
1136
+ let glEnable = true;
1136
1137
 
1137
1138
  /** Fixes slow rendering in some browsers by not compositing the WebGL canvas
1138
1139
  * @type {Boolean}
1139
1140
  * @default
1140
1141
  * @memberof Settings */
1141
- let glOverlay = 1;
1142
+ let glOverlay = true;
1142
1143
 
1143
1144
  ///////////////////////////////////////////////////////////////////////////////
1144
1145
  // Tile sheet settings
@@ -1162,7 +1163,7 @@ let tileFixBleedScale = .3;
1162
1163
  * @type {Boolean}
1163
1164
  * @default
1164
1165
  * @memberof Settings */
1165
- let enablePhysicsSolver = 1;
1166
+ let enablePhysicsSolver = true;
1166
1167
 
1167
1168
  /** Default object mass for collison calcuations (how heavy objects are)
1168
1169
  * @type {Number}
@@ -1184,7 +1185,7 @@ let objectDefaultAngleDamping = 1;
1184
1185
 
1185
1186
  /** How much to bounce when a collision occurs (0-1)
1186
1187
  * @type {Number}
1187
- * @default 0
1188
+ * @default
1188
1189
  * @memberof Settings */
1189
1190
  let objectDefaultElasticity = 0;
1190
1191
 
@@ -1202,7 +1203,7 @@ let objectMaxSpeed = 1;
1202
1203
 
1203
1204
  /** How much gravity to apply to objects along the Y axis, negative is down
1204
1205
  * @type {Number}
1205
- * @default 0
1206
+ * @default
1206
1207
  * @memberof Settings */
1207
1208
  let gravity = 0;
1208
1209
 
@@ -1219,33 +1220,33 @@ let particleEmitRateScale = 1;
1219
1220
  * @type {Boolean}
1220
1221
  * @default
1221
1222
  * @memberof Settings */
1222
- let gamepadsEnable = 1;
1223
+ let gamepadsEnable = true;
1223
1224
 
1224
1225
  /** If true, the dpad input is also routed to the left analog stick (for better accessability)
1225
1226
  * @type {Boolean}
1226
1227
  * @default
1227
1228
  * @memberof Settings */
1228
- let gamepadDirectionEmulateStick = 1;
1229
+ let gamepadDirectionEmulateStick = true;
1229
1230
 
1230
1231
  /** If true the WASD keys are also routed to the direction keys (for better accessability)
1231
1232
  * @type {Boolean}
1232
1233
  * @default
1233
1234
  * @memberof Settings */
1234
- let inputWASDEmulateDirection = 1;
1235
+ let inputWASDEmulateDirection = true;
1235
1236
 
1236
1237
  /** True if touch gamepad should appear on mobile devices
1237
1238
  * - Supports left analog stick, 4 face buttons and start button (button 9)
1238
1239
  * - Must be set by end of gameInit to be activated
1239
1240
  * @type {Boolean}
1240
- * @default 0
1241
+ * @default
1241
1242
  * @memberof Settings */
1242
- let touchGamepadEnable = 0;
1243
+ let touchGamepadEnable = false;
1243
1244
 
1244
1245
  /** True if touch gamepad should be analog stick or false to use if 8 way dpad
1245
1246
  * @type {Boolean}
1246
1247
  * @default
1247
1248
  * @memberof Settings */
1248
- let touchGamepadAnalog = 1;
1249
+ let touchGamepadAnalog = true;
1249
1250
 
1250
1251
  /** Size of virutal gamepad for touch devices in pixels
1251
1252
  * @type {Number}
@@ -1263,7 +1264,7 @@ let touchGamepadAlpha = .3;
1263
1264
  * @type {Boolean}
1264
1265
  * @default
1265
1266
  * @memberof Settings */
1266
- let vibrateEnable = 1;
1267
+ let vibrateEnable = true;
1267
1268
 
1268
1269
  ///////////////////////////////////////////////////////////////////////////////
1269
1270
  // Audio settings
@@ -1272,7 +1273,7 @@ let vibrateEnable = 1;
1272
1273
  * @type {Boolean}
1273
1274
  * @default
1274
1275
  * @memberof Settings */
1275
- let soundEnable = 1;
1276
+ let soundEnable = true;
1276
1277
 
1277
1278
  /** Volume scale to apply to all sound, music and speech
1278
1279
  * @type {Number}
@@ -1321,9 +1322,9 @@ let medalDisplayIconSize = 50;
1321
1322
 
1322
1323
  /** Set to stop medals from being unlockable (like if cheats are enabled)
1323
1324
  * @type {Boolean}
1324
- * @default 0
1325
+ * @default
1325
1326
  * @memberof Settings */
1326
- let medalsPreventUnlock;
1327
+ let medalsPreventUnlock = false;
1327
1328
 
1328
1329
  ///////////////////////////////////////////////////////////////////////////////
1329
1330
  // Setters for global variables
@@ -1394,12 +1395,12 @@ function setEnablePhysicsSolver(enable) { enablePhysicsSolver = enable; }
1394
1395
  function setObjectDefaultMass(mass) { objectDefaultMass = mass; }
1395
1396
 
1396
1397
  /** Set how much to slow velocity by each frame
1397
- * @param {Number} damping
1398
+ * @param {Number} damp
1398
1399
  * @memberof Settings */
1399
1400
  function setObjectDefaultDamping(damp) { objectDefaultDamping = damp; }
1400
1401
 
1401
1402
  /** Set how much to slow angular velocity each frame
1402
- * @param {Number} damping
1403
+ * @param {Number} damp
1403
1404
  * @memberof Settings */
1404
1405
  function setObjectDefaultAngleDamping(damp) { objectDefaultAngleDamping = damp; }
1405
1406
 
@@ -1419,9 +1420,9 @@ function setObjectDefaultFriction(friction) { objectDefaultFriction = friction;
1419
1420
  function setObjectMaxSpeed(speed) { objectMaxSpeed = speed; }
1420
1421
 
1421
1422
  /** Set how much gravity to apply to objects along the Y axis
1422
- * @param {Number} gravity
1423
+ * @param {Number} newGravity
1423
1424
  * @memberof Settings */
1424
- function setGravity(g) { gravity = g; }
1425
+ function setGravity(newGravity) { gravity = newGravity; }
1425
1426
 
1426
1427
  /** Set to scales emit rate of particles
1427
1428
  * @param {Number} scale
@@ -1519,7 +1520,7 @@ function setMedalsPreventUnlock(preventUnlock) { medalsPreventUnlock = preventUn
1519
1520
  function setShowWatermark(show) { showWatermark = show; }
1520
1521
 
1521
1522
  /** Set key code used to toggle debug mode, Esc by default
1522
- * @param {Number} key
1523
+ * @param {String} key
1523
1524
  * @memberof Debug */
1524
1525
  function setDebugKey(key) { debugKey = key; }
1525
1526
  /**
@@ -1556,27 +1557,25 @@ function setDebugKey(key) { debugKey = key; }
1556
1557
  class EngineObject
1557
1558
  {
1558
1559
  /** Create an engine object and adds it to the list of objects
1559
- * @param {Vector2} [pos=Vector2()] - World space position of the object
1560
- * @param {Vector2} [size=Vector2(1,1)] - World space size of the object
1561
- * @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
1562
- * @param {Number} [angle=0] - Angle the object is rotated by
1563
- * @param {Color} [color=Color()] - Color to apply to tile when rendered
1564
- * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
1560
+ * @param {Vector2} [pos=Vector2()] - World space position of the object
1561
+ * @param {Vector2} [size=Vector2(1,1)] - World space size of the object
1562
+ * @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
1563
+ * @param {Number} [angle] - Angle the object is rotated by
1564
+ * @param {Color} [color=Color()] - Color to apply to tile when rendered
1565
+ * @param {Number} [renderOrder] - Objects sorted by renderOrder before being rendered
1565
1566
  */
1566
1567
  constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color, renderOrder=0)
1567
1568
  {
1568
1569
  // set passed in params
1569
- ASSERT(isVector2(pos) && isVector2(size)); // ensure pos and size are vec2s
1570
- ASSERT(typeof tileInfo !== 'number' || !tileInfo); // prevent old style calls
1571
- ASSERT(!(renderOrder instanceof Color)); // prevent old style calls
1572
- // to fix old calls, replace with tile(tileIndex, tileSize)
1570
+ ASSERT(isVector2(pos) && isVector2(size), 'ensure pos and size are vec2s');
1571
+ ASSERT(typeof tileInfo !== 'number' || !tileInfo, 'old style tile setup');
1573
1572
 
1574
1573
  /** @property {Vector2} - World space position of the object */
1575
1574
  this.pos = pos.copy();
1576
1575
  /** @property {Vector2} - World space width and height of the object */
1577
1576
  this.size = size;
1578
1577
  /** @property {Vector2} - Size of object used for drawing, uses size if not set */
1579
- this.drawSize;
1578
+ this.drawSize = undefined;
1580
1579
  /** @property {TileInfo} - Tile info to render object (undefined is untextured) */
1581
1580
  this.tileInfo = tileInfo;
1582
1581
  /** @property {Number} - Angle to rotate the object */
@@ -1584,9 +1583,11 @@ class EngineObject
1584
1583
  /** @property {Color} - Color to apply when rendered */
1585
1584
  this.color = color;
1586
1585
  /** @property {Color} - Additive color to apply when rendered */
1587
- this.additiveColor;
1586
+ this.additiveColor = undefined;
1587
+ /** @property {Boolean} - Should it flip along y axis when rendered */
1588
+ this.mirror = false;
1588
1589
 
1589
- // set object defaults
1590
+ // physical properties
1590
1591
  /** @property {Number} [mass=objectDefaultMass] - How heavy the object is, static if 0 */
1591
1592
  this.mass = objectDefaultMass;
1592
1593
  /** @property {Number} [damping=objectDefaultDamping] - How much to slow down velocity each frame (0-1) */
@@ -1597,19 +1598,34 @@ class EngineObject
1597
1598
  this.elasticity = objectDefaultElasticity;
1598
1599
  /** @property {Number} [friction=objectDefaultFriction] - How much friction to apply when sliding (0-1) */
1599
1600
  this.friction = objectDefaultFriction;
1600
- /** @property {Number} [gravityScale=1] - How much to scale gravity by for this object */
1601
+ /** @property {Number} - How much to scale gravity by for this object */
1601
1602
  this.gravityScale = 1;
1602
- /** @property {Number} [renderOrder=0] - Objects are sorted by render order */
1603
+ /** @property {Number} - Objects are sorted by render order */
1603
1604
  this.renderOrder = renderOrder;
1604
- /** @property {Vector2} [velocity=Vector2()] - Velocity of the object */
1605
+ /** @property {Vector2} - Velocity of the object */
1605
1606
  this.velocity = vec2();
1606
- /** @property {Number} [angleVelocity=0] - Angular velocity of the object */
1607
+ /** @property {Number} - Angular velocity of the object */
1607
1608
  this.angleVelocity = 0;
1608
-
1609
- // init other internal object stuff
1609
+ /** @property {Number} - Track when object was created */
1610
1610
  this.spawnTime = time;
1611
+ /** @property {Array} - List of children of this object */
1611
1612
  this.children = [];
1613
+
1614
+ // parent child system
1615
+ /** @property {EngineObject} - Parent of object if in local space */
1616
+ this.parent = undefined;
1617
+ /** @property {Vector2} - Local position if child */
1618
+ this.localPos = vec2();
1619
+ /** @property {Number} - Local angle if child */
1620
+ this.localAngle = 0;
1621
+
1622
+ // collision flags
1623
+ /** @property {Boolean} - Object collides with the tile collision */
1612
1624
  this.collideTiles = false;
1625
+ /** @property {Boolean} - Object collides with solid objects */
1626
+ this.collideSolidObjects = false;
1627
+ /** @property {Boolean} - Object collides with and blocks other objects */
1628
+ this.isSolid = false;
1613
1629
 
1614
1630
  // add to list of objects
1615
1631
  engineObjects.push(this);
@@ -1826,7 +1842,7 @@ class EngineObject
1826
1842
  * @param {EngineObject} object - the object to test against
1827
1843
  * @return {Boolean} - true if the collision should be resolved
1828
1844
  */
1829
- collideWithObject(object) { return 1; }
1845
+ collideWithObject(object) { return true; }
1830
1846
 
1831
1847
  /** How long since the object was created
1832
1848
  * @return {Number} */
@@ -1847,7 +1863,7 @@ class EngineObject
1847
1863
  /** Attaches a child to this with a given local transform
1848
1864
  * @param {EngineObject} child
1849
1865
  * @param {Vector2} [localPos=Vector2()]
1850
- * @param {Number} [localAngle=0] */
1866
+ * @param {Number} [localAngle] */
1851
1867
  addChild(child, localPos=vec2(), localAngle=0)
1852
1868
  {
1853
1869
  ASSERT(!child.parent && !this.children.includes(child));
@@ -1867,12 +1883,12 @@ class EngineObject
1867
1883
  }
1868
1884
 
1869
1885
  /** Set how this object collides
1870
- * @param {Boolean} [collideSolidObjects=1] - Does it collide with solid objects
1871
- * @param {Boolean} [isSolid=1] - Does it collide with and block other objects (expensive in large numbers)
1872
- * @param {Boolean} [collideTiles=1] - Does it collide with the tile collision */
1873
- setCollision(collideSolidObjects=1, isSolid=1, collideTiles=1)
1886
+ * @param {Boolean} [collideSolidObjects] - Does it collide with solid objects
1887
+ * @param {Boolean} [isSolid] - Does it collide with and block other objects (expensive in large numbers)
1888
+ * @param {Boolean} [collideTiles] - Does it collide with the tile collision */
1889
+ setCollision(collideSolidObjects=true, isSolid=true, collideTiles=true)
1874
1890
  {
1875
- ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
1891
+ ASSERT(collideSolidObjects || !isSolid, 'solid objects must be set to collide');
1876
1892
 
1877
1893
  this.collideSolidObjects = collideSolidObjects;
1878
1894
  this.isSolid = isSolid;
@@ -1963,9 +1979,9 @@ let drawCount;
1963
1979
  * Create a tile info object
1964
1980
  * - This can take vecs or floats for easier use and conversion
1965
1981
  * - If an index is passed in, the tile size and index will determine the position
1966
- * @param {(Number|Vector2)} [pos=Vector2()] - Top left corner of tile in pixels or index
1967
- * @param {(Number|Vector2)} [size=tileSizeDefault] - Size of tile in pixels
1968
- * @param {Number} [textureIndex=0] - Texture index to use
1982
+ * @param {(Number|Vector2)} [pos=Vector2()] - Top left corner of tile in pixels or index
1983
+ * @param {(Number|Vector2)} [size=tileSizeDefault] - Size of tile in pixels
1984
+ * @param {Number} [textureIndex] - Texture index to use
1969
1985
  * @return {TileInfo}
1970
1986
  * @example
1971
1987
  * tile(2) // a tile at index 2 using the default tile size of 16
@@ -1977,14 +1993,14 @@ let drawCount;
1977
1993
  function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
1978
1994
  {
1979
1995
  // if size is a number, make it a vector
1980
- if (size.x == undefined)
1996
+ if (typeof size === 'number')
1981
1997
  {
1982
1998
  ASSERT(size > 0);
1983
1999
  size = vec2(size);
1984
2000
  }
1985
2001
 
1986
2002
  // if pos is a number, use it as a tile index
1987
- if (pos.x == undefined)
2003
+ if (typeof pos === 'number')
1988
2004
  {
1989
2005
  const textureInfo = textureInfos[textureIndex];
1990
2006
  if (textureInfo)
@@ -2008,7 +2024,7 @@ class TileInfo
2008
2024
  /** Create a tile info object
2009
2025
  * @param {Vector2} [pos=Vector2()] - Top left corner of tile in pixels
2010
2026
  * @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
2011
- * @param {Number} [textureIndex=0] - Texture index to use
2027
+ * @param {Number} [textureIndex] - Texture index to use
2012
2028
  */
2013
2029
  constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
2014
2030
  {
@@ -2037,10 +2053,13 @@ class TileInfo
2037
2053
  /** Texture Info - Stores info about each texture */
2038
2054
  class TextureInfo
2039
2055
  {
2040
- // create a TextureInfo, called automatically by the engine
2056
+ /**
2057
+ * Create a TextureInfo, called automatically by the engine
2058
+ * @param {HTMLImageElement} image
2059
+ */
2041
2060
  constructor(image)
2042
2061
  {
2043
- /** @property {CanvasImageSource} - image source */
2062
+ /** @property {HTMLImageElement} - image source */
2044
2063
  this.image = image;
2045
2064
  /** @property {Vector2} - size of the image */
2046
2065
  this.size = vec2(image.width, image.height);
@@ -2083,23 +2102,21 @@ function worldToScreen(worldPos)
2083
2102
  * @param {Vector2} pos - Center of the tile in world space
2084
2103
  * @param {Vector2} [size=Vector2(1,1)] - Size of the tile in world space
2085
2104
  * @param {TileInfo}[tileInfo] - Tile info to use, untextured if undefined
2086
- * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
2087
2105
  * @param {Color} [color=Color()] - Color to modulate with
2088
- * @param {Number} [angle=0] - Angle to rotate by
2089
- * @param {Boolean} [mirror=0] - If true image is flipped along the Y axis
2106
+ * @param {Number} [angle] - Angle to rotate by
2107
+ * @param {Boolean} [mirror] - If true image is flipped along the Y axis
2090
2108
  * @param {Color} [additiveColor=Color(0,0,0,0)] - Additive color to be applied
2091
2109
  * @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
2092
- * @param {Boolean} [screenSpace=0] - If true the pos and size are in screen space
2110
+ * @param {Boolean} [screenSpace] - If true the pos and size are in screen space
2093
2111
  * @param {CanvasRenderingContext2D} [context] - Canvas 2D context to draw to
2094
2112
  * @memberof Draw */
2095
2113
  function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
2096
2114
  angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace, context)
2097
2115
  {
2098
- ASSERT(!context || !useWebGL); // context only supported in canvas 2D mode
2099
- ASSERT(typeof tileInfo !== 'number' || !tileInfo); // prevent old style calls
2100
- // to fix old calls, replace with tile(tileIndex, tileSize)
2116
+ ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
2117
+ ASSERT(typeof tileInfo !== 'number' || !tileInfo,
2118
+ 'this is an old style calls, to fix replace it with tile(tileIndex, tileSize)');
2101
2119
 
2102
- showWatermark && ++drawCount;
2103
2120
  const textureInfo = tileInfo && tileInfo.getTextureInfo();
2104
2121
  if (useWebGL)
2105
2122
  {
@@ -2133,6 +2150,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
2133
2150
  else
2134
2151
  {
2135
2152
  // normal canvas 2D rendering method (slower)
2153
+ showWatermark && ++drawCount;
2136
2154
  drawCanvas2D(pos, size, angle, mirror, (context)=>
2137
2155
  {
2138
2156
  if (textureInfo)
@@ -2160,49 +2178,38 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
2160
2178
  * @param {Vector2} pos
2161
2179
  * @param {Vector2} [size=Vector2(1,1)]
2162
2180
  * @param {Color} [color=Color()]
2163
- * @param {Number} [angle=0]
2181
+ * @param {Number} [angle]
2164
2182
  * @param {Boolean} [useWebGL=glEnable]
2165
- * @param {Boolean} [screenSpace=0]
2183
+ * @param {Boolean} [screenSpace]
2166
2184
  * @param {CanvasRenderingContext2D} [context]
2167
2185
  * @memberof Draw */
2168
2186
  function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
2169
2187
  {
2170
- drawTile(pos, size, undefined, color, angle, 0, undefined, useWebGL, screenSpace, context);
2188
+ drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
2171
2189
  }
2172
2190
 
2173
2191
  /** Draw colored polygon using passed in points
2174
2192
  * @param {Array} points - Array of Vector2 points
2175
2193
  * @param {Color} [color=Color()]
2176
- * @param {Boolean} [useWebGL=glEnable]
2177
- * @param {Boolean} [screenSpace=0]
2178
- * @param {CanvasRenderingContext2D} [context]
2194
+ * @param {Boolean} [screenSpace]
2195
+ * @param {CanvasRenderingContext2D} [context=mainContext]
2179
2196
  * @memberof Draw */
2180
- function drawPoly(points, color=new Color, useWebGL=glEnable, screenSpace, context)
2197
+ function drawPoly(points, color=new Color, screenSpace, context=mainContext)
2181
2198
  {
2182
- ASSERT(!context || !useWebGL); // context only supported in canvas 2D mode
2183
-
2184
- if (useWebGL)
2185
- glDrawPoints(screenSpace ? points.map(screenToWorld) : points, color.rgbaInt());
2186
- else
2187
- {
2188
- // draw using canvas
2189
- if (!context)
2190
- context = mainContext;
2191
- context.fillStyle = color;
2192
- context.beginPath();
2193
- for (const point of screenSpace ? points : points.map(worldToScreen))
2194
- context.lineTo(point.x, point.y);
2195
- context.fill();
2196
- }
2199
+ context.fillStyle = color.toString();
2200
+ context.beginPath();
2201
+ for (const point of screenSpace ? points : points.map(worldToScreen))
2202
+ context.lineTo(point.x, point.y);
2203
+ context.fill();
2197
2204
  }
2198
2205
 
2199
2206
  /** Draw colored line between two points
2200
2207
  * @param {Vector2} posA
2201
2208
  * @param {Vector2} posB
2202
- * @param {Number} [thickness=.1]
2209
+ * @param {Number} [thickness]
2203
2210
  * @param {Color} [color=Color()]
2204
2211
  * @param {Boolean} [useWebGL=glEnable]
2205
- * @param {Boolean} [screenSpace=0]
2212
+ * @param {Boolean} [screenSpace]
2206
2213
  * @param {CanvasRenderingContext2D} [context]
2207
2214
  * @memberof Draw */
2208
2215
  function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
@@ -2218,7 +2225,7 @@ function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, contex
2218
2225
  * @param {Number} angle
2219
2226
  * @param {Boolean} mirror
2220
2227
  * @param {Function} drawFunction
2221
- * @param {Boolean} [screenSpace=0]
2228
+ * @param {Boolean} [screenSpace]
2222
2229
  * @param {CanvasRenderingContext2D} [context=mainContext]
2223
2230
  * @memberof Draw */
2224
2231
  function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, context=mainContext)
@@ -2238,13 +2245,13 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
2238
2245
  }
2239
2246
 
2240
2247
  /** Enable normal or additive blend mode
2241
- * @param {Boolean} [additive=0]
2248
+ * @param {Boolean} [additive]
2242
2249
  * @param {Boolean} [useWebGL=glEnable]
2243
2250
  * @param {CanvasRenderingContext2D} [context=mainContext]
2244
2251
  * @memberof Draw */
2245
2252
  function setBlendMode(additive, useWebGL=glEnable, context)
2246
2253
  {
2247
- ASSERT(!context || !useWebGL); // context only supported in canvas 2D mode
2254
+ ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
2248
2255
  if (useWebGL)
2249
2256
  glAdditive = additive;
2250
2257
  else
@@ -2259,11 +2266,11 @@ function setBlendMode(additive, useWebGL=glEnable, context)
2259
2266
  * Automatically splits new lines into rows
2260
2267
  * @param {String} text
2261
2268
  * @param {Vector2} pos
2262
- * @param {Number} [size=1]
2269
+ * @param {Number} [size]
2263
2270
  * @param {Color} [color=Color()]
2264
- * @param {Number} [lineWidth=0]
2271
+ * @param {Number} [lineWidth]
2265
2272
  * @param {Color} [lineColor=Color(0,0,0)]
2266
- * @param {String} [textAlign='center']
2273
+ * @param {CanvasTextAlign} [textAlign='center']
2267
2274
  * @param {String} [font=fontDefault]
2268
2275
  * @param {CanvasRenderingContext2D} [context=overlayContext]
2269
2276
  * @memberof Draw */
@@ -2276,19 +2283,19 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
2276
2283
  * Automatically splits new lines into rows
2277
2284
  * @param {String} text
2278
2285
  * @param {Vector2} pos
2279
- * @param {Number} [size=1]
2286
+ * @param {Number} [size]
2280
2287
  * @param {Color} [color=Color()]
2281
- * @param {Number} [lineWidth=0]
2288
+ * @param {Number} [lineWidth]
2282
2289
  * @param {Color} [lineColor=Color(0,0,0)]
2283
- * @param {String} [textAlign='center']
2290
+ * @param {CanvasTextAlign} [textAlign]
2284
2291
  * @param {String} [font=fontDefault]
2285
2292
  * @param {CanvasRenderingContext2D} [context=overlayContext]
2286
2293
  * @memberof Draw */
2287
2294
  function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
2288
2295
  {
2289
- context.fillStyle = color;
2296
+ context.fillStyle = color.toString();
2290
2297
  context.lineWidth = lineWidth;
2291
- context.strokeStyle = lineColor;
2298
+ context.strokeStyle = lineColor.toString();
2292
2299
  context.textAlign = textAlign;
2293
2300
  context.font = size + 'px '+ font;
2294
2301
  context.textBaseline = 'middle';
@@ -2323,8 +2330,8 @@ class FontImage
2323
2330
  {
2324
2331
  /** Create an image font
2325
2332
  * @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
2326
- * @param {Vector2} [tileSize=vec2(8)] - Size of the font source tiles
2327
- * @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
2333
+ * @param {Vector2} [tileSize=Vector2(8)] - Size of the font source tiles
2334
+ * @param {Vector2} [paddingSize=Vector2(0,1)] - How much extra space to add between characters
2328
2335
  * @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
2329
2336
  */
2330
2337
  constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), context=overlayContext)
@@ -2353,7 +2360,7 @@ class FontImage
2353
2360
  /** Draw text in screen space using the image font
2354
2361
  * @param {String} text
2355
2362
  * @param {Vector2} pos
2356
- * @param {Number} [scale=4]
2363
+ * @param {Number} [scale]
2357
2364
  * @param {Boolean} [center]
2358
2365
  */
2359
2366
  drawTextScreen(text, pos, scale=4, center)
@@ -2371,7 +2378,7 @@ class FontImage
2371
2378
  for(let j=line.length; j--;)
2372
2379
  {
2373
2380
  // draw each character
2374
- let charCode = line[j].charCodeAt();
2381
+ let charCode = line[j].charCodeAt(0);
2375
2382
  if (charCode < 32 || charCode > 127)
2376
2383
  charCode = 127; // unknown character
2377
2384
 
@@ -2395,7 +2402,7 @@ class FontImage
2395
2402
  /** Returns true if fullscreen mode is active
2396
2403
  * @return {Boolean}
2397
2404
  * @memberof Draw */
2398
- function isFullscreen() { return document.fullscreenElement; }
2405
+ function isFullscreen() { return !!document.fullscreenElement; }
2399
2406
 
2400
2407
  /** Toggle fullsceen mode
2401
2408
  * @memberof Draw */
@@ -2421,28 +2428,37 @@ function toggleFullscreen()
2421
2428
 
2422
2429
 
2423
2430
  /** Returns true if device key is down
2424
- * @param {Number} key
2425
- * @param {Number} [device=0]
2431
+ * @param {String|Number} key
2432
+ * @param {Number} [device]
2426
2433
  * @return {Boolean}
2427
2434
  * @memberof Input */
2428
2435
  function keyIsDown(key, device=0)
2429
- { return inputData[device] && inputData[device][key] & 1; }
2436
+ {
2437
+ ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
2438
+ return inputData[device] && !!(inputData[device][key] & 1);
2439
+ }
2430
2440
 
2431
2441
  /** Returns true if device key was pressed this frame
2432
- * @param {Number} key
2433
- * @param {Number} [device=0]
2442
+ * @param {String|Number} key
2443
+ * @param {Number} [device]
2434
2444
  * @return {Boolean}
2435
2445
  * @memberof Input */
2436
2446
  function keyWasPressed(key, device=0)
2437
- { return inputData[device] && inputData[device][key] & 2 ? 1 : 0; }
2447
+ {
2448
+ ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
2449
+ return inputData[device] && !!(inputData[device][key] & 2);
2450
+ }
2438
2451
 
2439
2452
  /** Returns true if device key was released this frame
2440
- * @param {Number} key
2441
- * @param {Number} [device=0]
2453
+ * @param {String|Number} key
2454
+ * @param {Number} [device]
2442
2455
  * @return {Boolean}
2443
2456
  * @memberof Input */
2444
2457
  function keyWasReleased(key, device=0)
2445
- { return inputData[device] && inputData[device][key] & 4 ? 1 : 0; }
2458
+ {
2459
+ ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
2460
+ return inputData[device] && !!(inputData[device][key] & 4);
2461
+ }
2446
2462
 
2447
2463
  /** Clears all input
2448
2464
  * @memberof Input */
@@ -2487,16 +2503,16 @@ let mouseWheel = 0;
2487
2503
  /** Returns true if user is using gamepad (has more recently pressed a gamepad button)
2488
2504
  * @type {Boolean}
2489
2505
  * @memberof Input */
2490
- let isUsingGamepad = 0;
2506
+ let isUsingGamepad = false;
2491
2507
 
2492
2508
  /** Prevents input continuing to the default browser handling (false by default)
2493
2509
  * @type {Boolean}
2494
2510
  * @memberof Input */
2495
- let preventDefaultInput = 0;
2511
+ let preventDefaultInput = false;
2496
2512
 
2497
2513
  /** Returns true if gamepad button is down
2498
2514
  * @param {Number} button
2499
- * @param {Number} [gamepad=0]
2515
+ * @param {Number} [gamepad]
2500
2516
  * @return {Boolean}
2501
2517
  * @memberof Input */
2502
2518
  function gamepadIsDown(button, gamepad=0)
@@ -2504,7 +2520,7 @@ function gamepadIsDown(button, gamepad=0)
2504
2520
 
2505
2521
  /** Returns true if gamepad button was pressed
2506
2522
  * @param {Number} button
2507
- * @param {Number} [gamepad=0]
2523
+ * @param {Number} [gamepad]
2508
2524
  * @return {Boolean}
2509
2525
  * @memberof Input */
2510
2526
  function gamepadWasPressed(button, gamepad=0)
@@ -2512,7 +2528,7 @@ function gamepadWasPressed(button, gamepad=0)
2512
2528
 
2513
2529
  /** Returns true if gamepad button was released
2514
2530
  * @param {Number} button
2515
- * @param {Number} [gamepad=0]
2531
+ * @param {Number} [gamepad]
2516
2532
  * @return {Boolean}
2517
2533
  * @memberof Input */
2518
2534
  function gamepadWasReleased(button, gamepad=0)
@@ -2520,7 +2536,7 @@ function gamepadWasReleased(button, gamepad=0)
2520
2536
 
2521
2537
  /** Returns gamepad stick value
2522
2538
  * @param {Number} stick
2523
- * @param {Number} [gamepad=0]
2539
+ * @param {Number} [gamepad]
2524
2540
  * @return {Vector2}
2525
2541
  * @memberof Input */
2526
2542
  function gamepadStick(stick, gamepad=0)
@@ -2563,9 +2579,10 @@ function inputUpdatePost()
2563
2579
  if (debug && e.target != document.body) return;
2564
2580
  if (!e.repeat)
2565
2581
  {
2566
- inputData[isUsingGamepad = 0][e.which] = 3;
2582
+ isUsingGamepad = false;
2583
+ inputData[0][e.code] = 3;
2567
2584
  if (inputWASDEmulateDirection)
2568
- inputData[0][remapKey(e.which)] = 3;
2585
+ inputData[0][remapKey(e.code)] = 3;
2569
2586
  }
2570
2587
  preventDefaultInput && e.preventDefault();
2571
2588
  }
@@ -2573,26 +2590,29 @@ function inputUpdatePost()
2573
2590
  onkeyup = (e)=>
2574
2591
  {
2575
2592
  if (debug && e.target != document.body) return;
2576
- inputData[0][e.which] = 4;
2593
+ inputData[0][e.code] = 4;
2577
2594
  if (inputWASDEmulateDirection)
2578
- inputData[0][remapKey(e.which)] = 4;
2595
+ inputData[0][remapKey(e.code)] = 4;
2579
2596
  }
2580
2597
 
2581
2598
  // handle remapping wasd keys to directions
2582
2599
  function remapKey(c)
2583
- {
2600
+ {
2584
2601
  return inputWASDEmulateDirection ?
2585
- c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
2602
+ c == 'KeyW' ? 'ArrowUp' :
2603
+ c == 'KeyS' ? 'ArrowDown' :
2604
+ c == 'KeyA' ? 'ArrowLeft' :
2605
+ c == 'KeyD' ? 'ArrowRight' : c : c;
2586
2606
  }
2587
2607
  }
2588
2608
 
2589
2609
  ///////////////////////////////////////////////////////////////////////////////
2590
2610
  // Mouse event handlers
2591
2611
 
2592
- onmousedown = (e)=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
2612
+ onmousedown = (e)=> {isUsingGamepad = false; inputData[0][e.button] = 3; window.onmousemove(e); e.button && e.preventDefault();}
2593
2613
  onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
2594
2614
  onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
2595
- onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
2615
+ onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
2596
2616
  oncontextmenu = (e)=> false; // prevent right click menu
2597
2617
 
2598
2618
  // convert a mouse or touch event position to screen space
@@ -2630,7 +2650,7 @@ function gamepadsUpdate()
2630
2650
  for (let i=10; i--;)
2631
2651
  {
2632
2652
  const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
2633
- data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
2653
+ data[j] = touchGamepadButtons[i] ? gamepadIsDown(j,0) ? 1 : 3 : gamepadIsDown(j,0) ? 4 : 0;
2634
2654
  }
2635
2655
  }
2636
2656
  }
@@ -2662,18 +2682,23 @@ function gamepadsUpdate()
2662
2682
  for (let j = gamepad.buttons.length; j--;)
2663
2683
  {
2664
2684
  const button = gamepad.buttons[j];
2665
- data[j] = button.pressed ? 1 + 2*!gamepadIsDown(j,i) : 4*gamepadIsDown(j,i);
2666
- isUsingGamepad |= !i && button.pressed;
2667
- touchGamepadEnable && touchGamepadTimer.unset(); // disable touch gamepad if using real gamepad
2685
+ const wasDown = gamepadIsDown(j,i);
2686
+ data[j] = button.pressed ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
2687
+ isUsingGamepad ||= !i && button.pressed;
2668
2688
  }
2669
2689
 
2670
2690
  if (gamepadDirectionEmulateStick)
2671
2691
  {
2672
2692
  // copy dpad to left analog stick when pressed
2673
- const dpad = vec2(gamepadIsDown(15,i) - gamepadIsDown(14,i), gamepadIsDown(12,i) - gamepadIsDown(13,i));
2693
+ const dpad = vec2(
2694
+ (gamepadIsDown(15,i)&&1) - (gamepadIsDown(14,i)&&1),
2695
+ (gamepadIsDown(12,i)&&1) - (gamepadIsDown(13,i)&&1));
2674
2696
  if (dpad.lengthSquared())
2675
2697
  sticks[0] = dpad.clampLength();
2676
2698
  }
2699
+
2700
+ // disable touch gamepad if using real gamepad
2701
+ touchGamepadEnable && isUsingGamepad && touchGamepadTimer.unset();
2677
2702
  }
2678
2703
  }
2679
2704
  }
@@ -2681,9 +2706,9 @@ function gamepadsUpdate()
2681
2706
  ///////////////////////////////////////////////////////////////////////////////
2682
2707
 
2683
2708
  /** Pulse the vibration hardware if it exists
2684
- * @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array
2709
+ * @param {Number} [pattern] - a single value in miliseconds or vibration interval array
2685
2710
  * @memberof Input */
2686
- function vibrate(pattern)
2711
+ function vibrate(pattern=100)
2687
2712
  { vibrateEnable && navigator && navigator.vibrate && navigator.vibrate(pattern); }
2688
2713
 
2689
2714
  /** Cancel any ongoing vibration
@@ -2701,29 +2726,28 @@ const isTouchDevice = window.ontouchstart !== undefined;
2701
2726
  if (isTouchDevice)
2702
2727
  {
2703
2728
  // override mouse events
2704
- let wasTouching, mouseDown = onmousedown, mouseUp = onmouseup;
2729
+ let wasTouching;
2705
2730
  onmousedown = onmouseup = ()=> 0;
2706
2731
 
2707
2732
  // handle all touch events the same way
2708
2733
  ontouchstart = ontouchmove = ontouchend = (e)=>
2709
2734
  {
2710
- e.button = 0; // all touches are left click
2711
-
2712
2735
  // fix stalled audio on mobile
2713
2736
  if (soundEnable)
2714
2737
  audioContext ? audioContext.resume() : zzfx(0);
2715
2738
 
2716
2739
  // check if touching and pass to mouse events
2717
2740
  const touching = e.touches.length;
2741
+ const button = 0; // all touches are left mouse button
2718
2742
  if (touching)
2719
2743
  {
2720
2744
  // set event pos and pass it along
2721
- e.x = e.touches[0].clientX;
2722
- e.y = e.touches[0].clientY;
2723
- wasTouching ? onmousemove(e) : mouseDown(e);
2745
+ const p = vec2(e.touches[0].clientX, e.touches[0].clientY);
2746
+ mousePosScreen = mouseToScreen(p);
2747
+ wasTouching ? isUsingGamepad = false : inputData[0][button] = 3;
2724
2748
  }
2725
2749
  else if (wasTouching)
2726
- mouseUp(e);
2750
+ inputData[0][button] = inputData[0][button] & 2 | 4;
2727
2751
 
2728
2752
  // set was touching
2729
2753
  wasTouching = touching;
@@ -2804,8 +2828,8 @@ function createTouchGamepad()
2804
2828
  }
2805
2829
 
2806
2830
  // call default touch handler and set to using gamepad
2807
- touchHandler(e);
2808
- isUsingGamepad = 1;
2831
+ touchHandler.bind(window)(e);
2832
+ isUsingGamepad = true;
2809
2833
 
2810
2834
  // must return true so the document will get focus
2811
2835
  return true;
@@ -2819,7 +2843,7 @@ function touchGamepadRender()
2819
2843
  return;
2820
2844
 
2821
2845
  // fade off when not touching or paused
2822
- const alpha = percent(touchGamepadTimer, 4, 3);
2846
+ const alpha = percent(touchGamepadTimer.get(), 4, 3);
2823
2847
  if (!alpha || paused)
2824
2848
  return;
2825
2849
 
@@ -2923,13 +2947,13 @@ class Sound
2923
2947
 
2924
2948
  /** Play the sound
2925
2949
  * @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null
2926
- * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
2927
- * @param {Number} [pitch=1] - How much to scale pitch by (also adjusted by this.randomness)
2928
- * @param {Number} [randomnessScale=1] - How much to scale randomness
2929
- * @param {Boolean} [loop=0] - Should the sound loop
2950
+ * @param {Number} [volume] - How much to scale volume by (in addition to range fade)
2951
+ * @param {Number} [pitch] - How much to scale pitch by (also adjusted by this.randomness)
2952
+ * @param {Number} [randomnessScale] - How much to scale randomness
2953
+ * @param {Boolean} [loop] - Should the sound loop
2930
2954
  * @return {AudioBufferSourceNode} - The audio source node
2931
2955
  */
2932
- play(pos, volume=1, pitch=1, randomnessScale=1, loop=0)
2956
+ play(pos, volume=1, pitch=1, randomnessScale=1, loop=false)
2933
2957
  {
2934
2958
  if (!soundEnable || !this.sampleChannels) return;
2935
2959
 
@@ -2962,8 +2986,13 @@ class Sound
2962
2986
  {
2963
2987
  if (this.source)
2964
2988
  this.source.stop();
2965
- this.source = 0;
2989
+ this.source = undefined;
2966
2990
  }
2991
+
2992
+ /** Get source of most recent instance of this sound that was played
2993
+ * @return {AudioBufferSourceNode}
2994
+ */
2995
+ getSource() { return this.source; }
2967
2996
 
2968
2997
  /** Play the sound as a note with a semitone offset
2969
2998
  * @param {Number} semitoneOffset - How many semitones to offset pitch
@@ -2979,12 +3008,7 @@ class Sound
2979
3008
  */
2980
3009
  getDuration()
2981
3010
  { return this.sampleChannels && this.sampleChannels[0].length / this.sampleRate; }
2982
-
2983
- /** Check if the last instance of this sound is playing
2984
- * @return {Boolean} - True if the sound is playing
2985
- */
2986
- isPlaying() { return this.source && !this.source.ended; }
2987
-
3011
+
2988
3012
  /** Check if sound is loading, for sounds fetched from a url
2989
3013
  * @return {Boolean} - True if sound is loading and not ready to play
2990
3014
  */
@@ -3005,13 +3029,13 @@ class SoundWave extends Sound
3005
3029
  {
3006
3030
  /** Create a sound object and cache the wave file for later use
3007
3031
  * @param {String} filename - Filename of audio file to load
3008
- * @param {Number} [randomness=0] - How much to randomize frequency each time sound plays
3032
+ * @param {Number} [randomness] - How much to randomize frequency each time sound plays
3009
3033
  * @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
3010
3034
  * @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
3011
3035
  */
3012
3036
  constructor(filename, randomness=0, range, taper)
3013
3037
  {
3014
- super(0, range, taper);
3038
+ super(undefined, range, taper);
3015
3039
  this.randomness = randomness;
3016
3040
 
3017
3041
  if (!soundEnable) return;
@@ -3065,11 +3089,11 @@ let soundDecoderContext; // audio context used only to decode audio files
3065
3089
  class Music extends Sound
3066
3090
  {
3067
3091
  /** Create a music object and cache the zzfx music samples for later use
3068
- * @param {Array} zzfxMusic - Array of zzfx music parameters
3092
+ * @param {[Array, Array, Array, Number]} zzfxMusic - Array of zzfx music parameters
3069
3093
  */
3070
3094
  constructor(zzfxMusic)
3071
3095
  {
3072
- super();
3096
+ super(undefined);
3073
3097
 
3074
3098
  if (!soundEnable) return;
3075
3099
  this.randomness = 0;
@@ -3082,17 +3106,17 @@ class Music extends Sound
3082
3106
  * @param {Boolean} [loop=1] - True if the music should loop
3083
3107
  * @return {AudioBufferSourceNode} - The audio source node
3084
3108
  */
3085
- playMusic(volume, loop = 1)
3086
- { return super.play(0, volume, 1, 1, loop); }
3109
+ playMusic(volume, loop = false)
3110
+ { return super.play(undefined, volume, 1, 1, loop); }
3087
3111
  }
3088
3112
 
3089
3113
  /** Play an mp3, ogg, or wav audio from a local file or url
3090
3114
  * @param {String} url - Location of sound file to play
3091
- * @param {Number} [volume=1] - How much to scale volume by
3092
- * @param {Boolean} [loop=1] - True if the music should loop
3115
+ * @param {Number} [volume] - How much to scale volume by
3116
+ * @param {Boolean} [loop] - True if the music should loop
3093
3117
  * @return {HTMLAudioElement} - The audio element for this sound
3094
3118
  * @memberof Audio */
3095
- function playAudioFile(url, volume=1, loop=1)
3119
+ function playAudioFile(url, volume=1, loop=false)
3096
3120
  {
3097
3121
  if (!soundEnable) return;
3098
3122
 
@@ -3106,9 +3130,9 @@ function playAudioFile(url, volume=1, loop=1)
3106
3130
  /** Speak text with passed in settings
3107
3131
  * @param {String} text - The text to speak
3108
3132
  * @param {String} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
3109
- * @param {Number} [volume=1] - How much to scale volume by
3110
- * @param {Number} [rate=1] - How quickly to speak
3111
- * @param {Number} [pitch=1] - How much to change the pitch by
3133
+ * @param {Number} [volume] - How much to scale volume by
3134
+ * @param {Number} [rate] - How quickly to speak
3135
+ * @param {Number} [pitch] - How much to change the pitch by
3112
3136
  * @return {SpeechSynthesisUtterance} - The utterance that was spoken
3113
3137
  * @memberof Audio */
3114
3138
  function speak(text, language='', volume=1, rate=1, pitch=1)
@@ -3135,7 +3159,7 @@ function speakStop() {speechSynthesis && speechSynthesis.cancel();}
3135
3159
 
3136
3160
  /** Get frequency of a note on a musical scale
3137
3161
  * @param {Number} semitoneOffset - How many semitones away from the root note
3138
- * @param {Number} [rootNoteFrequency=220] - Frequency at semitone offset 0
3162
+ * @param {Number} [rootFrequency=220] - Frequency at semitone offset 0
3139
3163
  * @return {Number} - The frequency of the note
3140
3164
  * @memberof Audio */
3141
3165
  function getNoteFrequency(semitoneOffset, rootFrequency=220)
@@ -3149,14 +3173,14 @@ let audioContext;
3149
3173
 
3150
3174
  /** Play cached audio samples with given settings
3151
3175
  * @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
3152
- * @param {Number} [volume=1] - How much to scale volume by
3153
- * @param {Number} [rate=1] - The playback rate to use
3154
- * @param {Number} [pan=0] - How much to apply stereo panning
3155
- * @param {Boolean} [loop=0] - True if the sound should loop when it reaches the end
3176
+ * @param {Number} [volume] - How much to scale volume by
3177
+ * @param {Number} [rate] - The playback rate to use
3178
+ * @param {Number} [pan] - How much to apply stereo panning
3179
+ * @param {Boolean} [loop] - True if the sound should loop when it reaches the end
3156
3180
  * @param {Number} [sampleRate=44100] - Sample rate for the sound
3157
3181
  * @return {AudioBufferSourceNode} - The audio node of the sound played
3158
3182
  * @memberof Audio */
3159
- function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0, sampleRate=zzfxR)
3183
+ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR)
3160
3184
  {
3161
3185
  if (!soundEnable) return;
3162
3186
 
@@ -3196,7 +3220,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0, sampleRate
3196
3220
  }
3197
3221
 
3198
3222
  ///////////////////////////////////////////////////////////////////////////////
3199
- // ZzFXMicro - Zuper Zmall Zound Zynth - v1.2.0 by Frank Force
3223
+ // ZzFXMicro - Zuper Zmall Zound Zynth - v1.3.1 by Frank Force
3200
3224
 
3201
3225
  /** Generate and play a ZzFX sound
3202
3226
  *
@@ -3212,26 +3236,27 @@ function zzfx(...zzfxSound) { return playSamples([zzfxG(...zzfxSound)]); }
3212
3236
  const zzfxR = 44100;
3213
3237
 
3214
3238
  /** Generate samples for a ZzFX sound
3215
- * @param {Number} [volume=1] - Volume scale (percent)
3216
- * @param {Number} [randomness=.05] - How much to randomize frequency (percent Hz)
3217
- * @param {Number} [frequency=220] - Frequency of sound (Hz)
3218
- * @param {Number} [attack=0] - Attack time, how fast sound starts (seconds)
3219
- * @param {Number} [sustain=0] - Sustain time, how long sound holds (seconds)
3220
- * @param {Number} [release=.1] - Release time, how fast sound fades out (seconds)
3221
- * @param {Number} [shape=0] - Shape of the sound wave
3222
- * @param {Number} [shapeCurve=1] - Squarenes of wave (0=square, 1=normal, 2=pointy)
3223
- * @param {Number} [slide=0] - How much to slide frequency (kHz/s)
3224
- * @param {Number} [deltaSlide=0] - How much to change slide (kHz/s/s)
3225
- * @param {Number} [pitchJump=0] - Frequency of pitch jump (Hz)
3226
- * @param {Number} [pitchJumpTime=0] - Time of pitch jump (seconds)
3227
- * @param {Number} [repeatTime=0] - Resets some parameters periodically (seconds)
3228
- * @param {Number} [noise=0] - How much random noise to add (percent)
3229
- * @param {Number} [modulation=0] - Frequency of modulation wave, negative flips phase (Hz)
3230
- * @param {Number} [bitCrush=0] - Resamples at a lower frequency in (samples*100)
3231
- * @param {Number} [delay=0] - Overlap sound with itself for reverb and flanger effects (seconds)
3232
- * @param {Number} [sustainVolume=1] - Volume level for sustain (percent)
3233
- * @param {Number} [decay=0] - Decay time, how long to reach sustain after attack (seconds)
3234
- * @param {Number} [tremolo=0] - Trembling effect, rate controlled by repeat time (precent)
3239
+ * @param {Number} [volume] - Volume scale (percent)
3240
+ * @param {Number} [randomness] - How much to randomize frequency (percent Hz)
3241
+ * @param {Number} [frequency] - Frequency of sound (Hz)
3242
+ * @param {Number} [attack] - Attack time, how fast sound starts (seconds)
3243
+ * @param {Number} [sustain] - Sustain time, how long sound holds (seconds)
3244
+ * @param {Number} [release] - Release time, how fast sound fades out (seconds)
3245
+ * @param {Number} [shape] - Shape of the sound wave
3246
+ * @param {Number} [shapeCurve] - Squarenes of wave (0=square, 1=normal, 2=pointy)
3247
+ * @param {Number} [slide] - How much to slide frequency (kHz/s)
3248
+ * @param {Number} [deltaSlide] - How much to change slide (kHz/s/s)
3249
+ * @param {Number} [pitchJump] - Frequency of pitch jump (Hz)
3250
+ * @param {Number} [pitchJumpTime] - Time of pitch jump (seconds)
3251
+ * @param {Number} [repeatTime] - Resets some parameters periodically (seconds)
3252
+ * @param {Number} [noise] - How much random noise to add (percent)
3253
+ * @param {Number} [modulation] - Frequency of modulation wave, negative flips phase (Hz)
3254
+ * @param {Number} [bitCrush] - Resamples at a lower frequency in (samples*100)
3255
+ * @param {Number} [delay] - Overlap sound with itself for reverb and flanger effects (seconds)
3256
+ * @param {Number} [sustainVolume] - Volume level for sustain (percent)
3257
+ * @param {Number} [decay] - Decay time, how long to reach sustain after attack (seconds)
3258
+ * @param {Number} [tremolo] - Trembling effect, rate controlled by repeat time (precent)
3259
+ * @param {Number} [filter] - Filter cutoff frequency, positive for HPF, negative for LPF (Hz)
3235
3260
  * @return {Array} - Array of audio samples
3236
3261
  * @memberof Audio
3237
3262
  */
@@ -3241,78 +3266,91 @@ function zzfxG
3241
3266
  volume = 1, randomness = .05, frequency = 220, attack = 0, sustain = 0,
3242
3267
  release = .1, shape = 0, shapeCurve = 1, slide = 0, deltaSlide = 0,
3243
3268
  pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, modulation = 0,
3244
- bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0
3269
+ bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0, filter = 0
3245
3270
  )
3246
3271
  {
3247
- // locals
3248
- let PI2 = PI*2, startSlide = slide *= 500 * PI2 / zzfxR / zzfxR, b=[],
3249
- startFrequency = frequency *= (1 + randomness*rand(-1,1)) * PI2 / zzfxR,
3250
- t=0, tm=0, i=0, j=1, r=0, c=0, s=0, f, length
3272
+ // init parameters
3273
+ let PI2 = PI*2, sampleRate = zzfxR,
3274
+ startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
3275
+ startFrequency = frequency *=
3276
+ rand(1 + randomness, 1-randomness) * PI2 / sampleRate,
3277
+ b = [], t = 0, tm = 0, i = 0, j = 1, r = 0, c = 0, s = 0, f, length,
3278
+
3279
+ // biquad LP/HP filter
3280
+ quality = 2, w = PI2 * abs(filter) * 2 / sampleRate,
3281
+ cos = Math.cos(w), alpha = Math.sin(w) / 2 / quality,
3282
+ a0 = 1 + alpha, a1 = -2*cos / a0, a2 = (1 - alpha) / a0,
3283
+ b0 = (1 + sign(filter) * cos) / 2 / a0,
3284
+ b1 = -(sign(filter) + cos) / a0, b2 = b0,
3285
+ x2 = 0, x1 = 0, y2 = 0, y1 = 0;
3251
3286
 
3252
3287
  // scale by sample rate
3253
- attack = attack * zzfxR + 9; // minimum attack to prevent pop
3254
- decay *= zzfxR;
3255
- sustain *= zzfxR;
3256
- release *= zzfxR;
3257
- delay *= zzfxR;
3258
- deltaSlide *= 500 * PI2 / zzfxR**3;
3259
- modulation *= PI2 / zzfxR;
3260
- pitchJump *= PI2 / zzfxR;
3261
- pitchJumpTime *= zzfxR;
3262
- repeatTime = repeatTime * zzfxR | 0;
3288
+ attack = attack * sampleRate + 9; // minimum attack to prevent pop
3289
+ decay *= sampleRate;
3290
+ sustain *= sampleRate;
3291
+ release *= sampleRate;
3292
+ delay *= sampleRate;
3293
+ deltaSlide *= 500 * PI2 / sampleRate**3;
3294
+ modulation *= PI2 / sampleRate;
3295
+ pitchJump *= PI2 / sampleRate;
3296
+ pitchJumpTime *= sampleRate;
3297
+ repeatTime = repeatTime * sampleRate | 0;
3298
+ volume *= soundVolume;
3263
3299
 
3264
3300
  // generate waveform
3265
- for (length = attack + decay + sustain + release + delay | 0;
3266
- i < length; b[i++] = s)
3301
+ for(length = attack + decay + sustain + release + delay | 0;
3302
+ i < length; b[i++] = s * volume) // sample
3267
3303
  {
3268
- if (!(++c%(bitCrush*100|0))) // bit crush
3304
+ if (!(++c%(bitCrush*100|0))) // bit crush
3269
3305
  {
3270
- s = shape? shape>1? shape>2? shape>3? // wave shape
3271
- Math.sin((t%PI2)**3) : // 4 noise
3272
- max(min(Math.tan(t),1),-1): // 3 tan
3273
- 1-(2*t/PI2%2+2)%2: // 2 saw
3274
- 1-4*abs(Math.round(t/PI2)-t/PI2): // 1 triangle
3275
- Math.sin(t); // 0 sin
3276
-
3306
+ s = shape? shape>1? shape>2? shape>3? // wave shape
3307
+ Math.sin(t**3) : // 4 noise
3308
+ clamp(Math.tan(t),1,-1): // 3 tan
3309
+ 1-(2*t/PI2%2+2)%2: // 2 saw
3310
+ 1-4*abs(Math.round(t/PI2)-t/PI2): // 1 triangle
3311
+ Math.sin(t); // 0 sin
3312
+
3277
3313
  s = (repeatTime ?
3278
3314
  1 - tremolo + tremolo*Math.sin(PI2*i/repeatTime) // tremolo
3279
3315
  : 1) *
3280
- sign(s)*(abs(s)**shapeCurve) * // curve 0=square, 2=pointy
3281
- volume * soundVolume * ( // envelope
3282
- i < attack ? i/attack : // attack
3283
- i < attack + decay ? // decay
3284
- 1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
3285
- i < attack + decay + sustain ? // sustain
3286
- sustainVolume : // sustain volume
3287
- i < length - delay ? // release
3288
- (length - i - delay)/release * // release falloff
3289
- sustainVolume : // release volume
3290
- 0); // post release
3291
-
3292
- s = delay ? s/2 + (delay > i ? 0 : // delay
3293
- (i<length-delay? 1 : (length-i)/delay) * // release delay
3294
- b[i-delay|0]/2) : s; // sample delay
3316
+ sign(s)*(abs(s)**shapeCurve) * // curve
3317
+ (i < attack ? i/attack : // attack
3318
+ i < attack + decay ? // decay
3319
+ 1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
3320
+ i < attack + decay + sustain ? // sustain
3321
+ sustainVolume : // sustain volume
3322
+ i < length - delay ? // release
3323
+ (length - i - delay)/release * // release falloff
3324
+ sustainVolume : // release volume
3325
+ 0); // post release
3326
+
3327
+ s = delay ? s/2 + (delay > i ? 0 : // delay
3328
+ (i<length-delay? 1 : (length-i)/delay) * // release delay
3329
+ b[i-delay|0]/2/volume) : s; // sample delay
3330
+
3331
+ if (filter) // apply filter
3332
+ s = y1 = b2*x2 + b1*(x2=x1) + b0*(x1=s) - a2*y2 - a1*(y2=y1);
3295
3333
  }
3296
3334
 
3297
- f = (frequency += slide += deltaSlide) * // frequency
3298
- Math.cos(modulation*tm++); // modulation
3299
- t += f - f*noise*(1 - (Math.sin(i)+1)*1e9%2); // noise
3300
-
3301
- if (j && ++j > pitchJumpTime) // pitch jump
3302
- {
3303
- frequency += pitchJump; // apply pitch jump
3304
- startFrequency += pitchJump; // also apply to start
3305
- j = 0; // reset pitch jump time
3306
- }
3307
-
3308
- if (repeatTime && !(++r % repeatTime)) // repeat
3309
- {
3310
- frequency = startFrequency; // reset frequency
3311
- slide = startSlide; // reset slide
3312
- j ||= 1; // reset pitch jump time
3335
+ f = (frequency += slide += deltaSlide) *// frequency
3336
+ Math.cos(modulation*tm++); // modulation
3337
+ t += f + f*noise*Math.sin(i**5); // noise
3338
+
3339
+ if (j && ++j > pitchJumpTime) // pitch jump
3340
+ {
3341
+ frequency += pitchJump; // apply pitch jump
3342
+ startFrequency += pitchJump; // also apply to start
3343
+ j = 0; // stop pitch jump time
3344
+ }
3345
+
3346
+ if (repeatTime && !(++r % repeatTime)) // repeat
3347
+ {
3348
+ frequency = startFrequency; // reset frequency
3349
+ slide = startSlide; // reset slide
3350
+ j = j || 1; // reset pitch jump time
3313
3351
  }
3314
3352
  }
3315
-
3353
+
3316
3354
  return b;
3317
3355
  }
3318
3356
 
@@ -3323,7 +3361,7 @@ function zzfxG
3323
3361
  * @param {Array} instruments - Array of ZzFX sound paramaters
3324
3362
  * @param {Array} patterns - Array of pattern data
3325
3363
  * @param {Array} sequence - Array of pattern indexes
3326
- * @param {Number} [BPM=125] - Playback speed of the song in BPM
3364
+ * @param {Number} [BPM] - Playback speed of the song in BPM
3327
3365
  * @return {Array} - Left and right channel sample data
3328
3366
  * @memberof Audio */
3329
3367
  function zzfxM(instruments, patterns, sequence, BPM = 125)
@@ -3362,10 +3400,10 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
3362
3400
  patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
3363
3401
 
3364
3402
  // check if there are more channels
3365
- hasMore ||= !!patterns[patternIndex][channelIndex];
3403
+ hasMore |= patterns[patternIndex][channelIndex]&&1;
3366
3404
 
3367
3405
  // get next offset, use the length of first channel
3368
- nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - !notFirstBeat) * beatLength;
3406
+ nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - (notFirstBeat?0:1)) * beatLength;
3369
3407
  // for each beat in pattern, plus one extra if end of sequence
3370
3408
  isSequenceEnd = sequenceIndex == sequence.length - 1;
3371
3409
  for (i = 2, k = outSampleOffset; i < patternChannel.length + isSequenceEnd; notFirstBeat = ++i) {
@@ -3381,7 +3419,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
3381
3419
  for (j = 0; j < beatLength && notFirstBeat;
3382
3420
 
3383
3421
  // fade off attenuation at end of beat if stopping note, prevents clicking
3384
- j++ > beatLength - 99 && stop ? attenuation += (attenuation < 1) / 99 : 0
3422
+ j++ > beatLength - 99 && stop && attenuation < 1? attenuation += 1 / 99 : 0
3385
3423
  ) {
3386
3424
  // copy sample to stereo buffers with panning
3387
3425
  sample = (1 - attenuation) * sampleBuffer[sampleOffset++] / 2 || 0;
@@ -3457,7 +3495,7 @@ function initTileCollision(size)
3457
3495
 
3458
3496
  /** Set tile collision data
3459
3497
  * @param {Vector2} pos
3460
- * @param {Number} [data=0]
3498
+ * @param {Number} [data]
3461
3499
  * @memberof TileCollision */
3462
3500
  function setTileCollisionData(pos, data=0)
3463
3501
  {
@@ -3490,7 +3528,7 @@ function tileCollisionTest(pos, size=vec2(), object)
3490
3528
  {
3491
3529
  const tileData = tileCollision[y*tileCollisionSize.x+x];
3492
3530
  if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
3493
- return 1;
3531
+ return true;
3494
3532
  }
3495
3533
  }
3496
3534
 
@@ -3556,11 +3594,11 @@ function tileCollisionRaycast(posStart, posEnd, object)
3556
3594
  class TileLayerData
3557
3595
  {
3558
3596
  /** Create a tile layer data object, one for each tile in a TileLayer
3559
- * @param {Number} [tile] - The tile to use, untextured if undefined
3560
- * @param {Number} [direction=0] - Integer direction of tile, in 90 degree increments
3561
- * @param {Boolean} [mirror=0] - If the tile should be mirrored along the x axis
3562
- * @param {Color} [color=Color()] - Color of the tile */
3563
- constructor(tile, direction=0, mirror=0, color=new Color())
3597
+ * @param {Number} [tile] - The tile to use, untextured if undefined
3598
+ * @param {Number} [direction] - Integer direction of tile, in 90 degree increments
3599
+ * @param {Boolean} [mirror] - If the tile should be mirrored along the x axis
3600
+ * @param {Color} [color] - Color of the tile */
3601
+ constructor(tile, direction=0, mirror=false, color=new Color())
3564
3602
  {
3565
3603
  /** @property {Number} - The tile to use, untextured if undefined */
3566
3604
  this.tile = tile;
@@ -3573,7 +3611,7 @@ class TileLayerData
3573
3611
  }
3574
3612
 
3575
3613
  /** Set this tile to clear, it will not be rendered */
3576
- clear() { this.tile = this.direction = this.mirror = 0; color = new Color; }
3614
+ clear() { this.tile = this.direction = 0; this.mirror = false; this.color = new Color; }
3577
3615
  }
3578
3616
 
3579
3617
  /**
@@ -3590,25 +3628,25 @@ class TileLayerData
3590
3628
  */
3591
3629
  class TileLayer extends EngineObject
3592
3630
  {
3593
- /** Create a tile layer object
3594
- * @param {Vector2} [position=Vector2()] - World space position
3595
- * @param {Vector2} [size=tileCollisionSize] - World space size
3596
- * @param {TileInfo} [tileInfo] - Tile info for layer
3597
- * @param {Vector2} [scale=Vector2(1,1)] - How much to scale this layer when rendered
3598
- * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
3631
+ /** Create a tile layer object
3632
+ * @param {Vector2} [position=Vector2()] - World space position
3633
+ * @param {Vector2} [size=tileCollisionSize] - World space size
3634
+ * @param {TileInfo} [tileInfo] - Tile info for layer
3635
+ * @param {Vector2} [scale=Vector2(1,1)] - How much to scale this layer when rendered
3636
+ * @param {Number} [renderOrder] - Objects sorted by renderOrder before being rendered
3599
3637
  */
3600
- constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderOrder=0)
3638
+ constructor(position, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderOrder=0)
3601
3639
  {
3602
- super(pos, size, tileInfo, 0, undefined, renderOrder);
3640
+ super(position, size, tileInfo, 0, undefined, renderOrder);
3603
3641
 
3604
- /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
3642
+ /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
3605
3643
  this.canvas = document.createElement('canvas');
3606
3644
  /** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
3607
3645
  this.context = this.canvas.getContext('2d');
3608
- /** @property {Vector2} - How much to scale this layer when rendered */
3646
+ /** @property {Vector2} - How much to scale this layer when rendered */
3609
3647
  this.scale = scale;
3610
- /** @property {Boolean} [isOverlay=0] - If true this layer will render to overlay canvas and appear above all objects */
3611
- this.isOverlay;
3648
+ /** @property {Boolean} - If true this layer will render to overlay canvas and appear above all objects */
3649
+ this.isOverlay = false;
3612
3650
 
3613
3651
  // init tile data
3614
3652
  this.data = [];
@@ -3617,10 +3655,10 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3617
3655
  }
3618
3656
 
3619
3657
  /** Set data at a given position in the array
3620
- * @param {Vector2} position - Local position in array
3621
- * @param {TileLayerData} data - Data to set
3622
- * @param {Boolean} [redraw=0] - Force the tile to redraw if true */
3623
- setData(layerPos, data, redraw)
3658
+ * @param {Vector2} layerPos - Local position in array
3659
+ * @param {TileLayerData} data - Data to set
3660
+ * @param {Boolean} [redraw] - Force the tile to redraw if true */
3661
+ setData(layerPos, data, redraw=false)
3624
3662
  {
3625
3663
  if (layerPos.arrayCheck(this.size))
3626
3664
  {
@@ -3641,7 +3679,7 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3641
3679
  // Render the tile layer, called automatically by the engine
3642
3680
  render()
3643
3681
  {
3644
- ASSERT(mainContext != this.context); // must call redrawEnd() after drawing tiles
3682
+ ASSERT(mainContext != this.context, 'must call redrawEnd() after drawing tiles');
3645
3683
 
3646
3684
  // flush and copy gl canvas because tile canvas does not use webgl
3647
3685
  glEnable && !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
@@ -3660,16 +3698,17 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3660
3698
  */
3661
3699
  redraw()
3662
3700
  {
3663
- this.redrawStart(1);
3701
+ this.redrawStart(true);
3664
3702
  this.drawAllTileData();
3665
3703
  this.redrawEnd();
3666
3704
  }
3667
3705
 
3668
3706
  /** Call to start the redraw process
3669
- * @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
3670
- redrawStart(clear = 0)
3707
+ * @param {Boolean} [clear] - Should it clear the canvas before drawing */
3708
+ redrawStart(clear=false)
3671
3709
  {
3672
3710
  // save current render settings
3711
+ /** @type {[HTMLCanvasElement, CanvasRenderingContext2D, Vector2, Vector2, number]} */
3673
3712
  this.savedRenderSettings = [mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale];
3674
3713
 
3675
3714
  // hack: use normal rendering system to render the tiles
@@ -3692,8 +3731,8 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3692
3731
  /** Call to end the redraw process */
3693
3732
  redrawEnd()
3694
3733
  {
3695
- ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
3696
- glEnable && glCopyToContext(mainContext, 1);
3734
+ ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
3735
+ glEnable && glCopyToContext(mainContext, true);
3697
3736
  //debugSaveCanvas(this.canvas);
3698
3737
 
3699
3738
  // set stuff back to normal
@@ -3706,13 +3745,13 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3706
3745
  {
3707
3746
  // first clear out where the tile was
3708
3747
  const pos = layerPos.floor().add(this.pos).add(vec2(.5));
3709
- this.drawCanvas2D(pos, vec2(1), 0, 0, (context)=>context.clearRect(-.5, -.5, 1, 1));
3748
+ this.drawCanvas2D(pos, vec2(1), 0, false, (context)=>context.clearRect(-.5, -.5, 1, 1));
3710
3749
 
3711
3750
  // draw the tile if not undefined
3712
3751
  const d = this.getData(layerPos);
3713
3752
  if (d.tile != undefined)
3714
3753
  {
3715
- ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
3754
+ ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
3716
3755
  const tileInfo = tile(d.tile, this.tileInfo.size, this.tileInfo.textureIndex);
3717
3756
  drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
3718
3757
  }
@@ -3780,7 +3819,7 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3780
3819
  * @param {Color} [color=Color()]
3781
3820
  * @param {Number} [angle=0] */
3782
3821
  drawRect(pos, size, color, angle)
3783
- { this.drawTile(pos, size, -1, 0, color, angle); }
3822
+ { this.drawTile(pos, size, undefined, color, angle); }
3784
3823
  }
3785
3824
  /**
3786
3825
  * LittleJS Particle System
@@ -3808,37 +3847,37 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3808
3847
  class ParticleEmitter extends EngineObject
3809
3848
  {
3810
3849
  /** Create a particle system with the given settings
3811
- * @param {Vector2} position - World space position of the emitter
3812
- * @param {Number} [angle=0] - Angle to emit the particles
3813
- * @param {Number} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect)
3814
- * @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
3815
- * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
3816
- * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
3817
- * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
3818
- * @param {Color} [colorStartA=Color()] - Color at start of life 1, randomized between start colors
3819
- * @param {Color} [colorStartB=Color()] - Color at start of life 2, randomized between start colors
3820
- * @param {Color} [colorEndA=Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
3821
- * @param {Color} [colorEndB=Color(1,1,1,0)] - Color at end of life 2, randomized between end colors
3822
- * @param {Number} [particleTime=.5] - How long particles live
3823
- * @param {Number} [sizeStart=.1] - How big are particles at start
3824
- * @param {Number} [sizeEnd=1] - How big are particles at end
3825
- * @param {Number} [speed=.1] - How fast are particles when spawned
3826
- * @param {Number} [angleSpeed=.05] - How fast are particles rotating
3827
- * @param {Number} [damping=1] - How much to dampen particle speed
3828
- * @param {Number} [angleDamping=1] - How much to dampen particle angular speed
3829
- * @param {Number} [gravityScale=0] - How much does gravity effect particles
3830
- * @param {Number} [particleConeAngle=PI] - Cone for start particle angle
3831
- * @param {Number} [fadeRate=.1] - How quick to fade in particles at start/end in percent of life
3832
- * @param {Number} [randomness=.2] - Apply extra randomness percent
3833
- * @param {Boolean} [collideTiles=0] - Do particles collide against tiles
3834
- * @param {Boolean} [additive=0] - Should particles use addtive blend
3835
- * @param {Boolean} [randomColorLinear=1] - Should color be randomized linearly or across each component
3836
- * @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
3837
- * @param {Boolean} [localSpace=0] - Should it be in local space of emitter (world space is default)
3850
+ * @param {Vector2} position - World space position of the emitter
3851
+ * @param {Number} [angle] - Angle to emit the particles
3852
+ * @param {Number|Vector2} [emitSize] - World space size of the emitter (float for circle diameter, vec2 for rect)
3853
+ * @param {Number} [emitTime] - How long to stay alive (0 is forever)
3854
+ * @param {Number} [emitRate] - How many particles per second to spawn, does not emit if 0
3855
+ * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
3856
+ * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
3857
+ * @param {Color} [colorStartA=Color()] - Color at start of life 1, randomized between start colors
3858
+ * @param {Color} [colorStartB=Color()] - Color at start of life 2, randomized between start colors
3859
+ * @param {Color} [colorEndA=Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
3860
+ * @param {Color} [colorEndB=Color(1,1,1,0)] - Color at end of life 2, randomized between end colors
3861
+ * @param {Number} [particleTime] - How long particles live
3862
+ * @param {Number} [sizeStart] - How big are particles at start
3863
+ * @param {Number} [sizeEnd] - How big are particles at end
3864
+ * @param {Number} [speed] - How fast are particles when spawned
3865
+ * @param {Number} [angleSpeed] - How fast are particles rotating
3866
+ * @param {Number} [damping] - How much to dampen particle speed
3867
+ * @param {Number} [angleDamping] - How much to dampen particle angular speed
3868
+ * @param {Number} [gravityScale] - How much gravity effect particles
3869
+ * @param {Number} [particleConeAngle] - Cone for start particle angle
3870
+ * @param {Number} [fadeRate] - How quick to fade particles at start/end in percent of life
3871
+ * @param {Number} [randomness] - Apply extra randomness percent
3872
+ * @param {Boolean} [collideTiles] - Do particles collide against tiles
3873
+ * @param {Boolean} [additive] - Should particles use addtive blend
3874
+ * @param {Boolean} [randomColorLinear] - Should color be randomized linearly or across each component
3875
+ * @param {Number} [renderOrder] - Render order for particles (additive is above other stuff by default)
3876
+ * @param {Boolean} [localSpace] - Should it be in local space of emitter (world space is default)
3838
3877
  */
3839
3878
  constructor
3840
3879
  (
3841
- pos,
3880
+ position,
3842
3881
  angle,
3843
3882
  emitSize = 0,
3844
3883
  emitTime = 0,
@@ -3860,17 +3899,17 @@ class ParticleEmitter extends EngineObject
3860
3899
  particleConeAngle = PI,
3861
3900
  fadeRate = .1,
3862
3901
  randomness = .2,
3863
- collideTiles,
3864
- additive,
3865
- randomColorLinear = 1,
3902
+ collideTiles = false,
3903
+ additive = false,
3904
+ randomColorLinear = true,
3866
3905
  renderOrder = additive ? 1e9 : 0,
3867
- localSpace
3906
+ localSpace = false
3868
3907
  )
3869
3908
  {
3870
- super(pos, vec2(), tileInfo, angle, undefined, renderOrder);
3909
+ super(position, vec2(), tileInfo, angle, undefined, renderOrder);
3871
3910
 
3872
3911
  // emitter settings
3873
- /** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
3912
+ /** @property {Number|Vector2} - World space size of the emitter (float for circle diameter, vec2 for rect) */
3874
3913
  this.emitSize = emitSize
3875
3914
  /** @property {Number} - How long to stay alive (0 is forever) */
3876
3915
  this.emitTime = emitTime;
@@ -3916,14 +3955,17 @@ class ParticleEmitter extends EngineObject
3916
3955
  this.randomness = randomness;
3917
3956
  /** @property {Boolean} - Do particles collide against tiles */
3918
3957
  this.collideTiles = collideTiles;
3919
- /** @property {Number} - Should particles use addtive blend */
3958
+ /** @property {Boolean} - Should particles use addtive blend */
3920
3959
  this.additive = additive;
3921
3960
  /** @property {Boolean} - Should it be in local space of emitter */
3922
- this.localSpace = localSpace;
3923
- /** @property {Number} - If set the partile is drawn as a trail, stretched in the drection of velocity */
3961
+ this.localSpace = localSpace;
3962
+ /** @property {Number} - If non zero the partile is drawn as a trail, stretched in the drection of velocity */
3924
3963
  this.trailScale = 0;
3925
-
3926
- // internal variables
3964
+ /** @property {Function} - Callback when particle is destroyed */
3965
+ this.particleDestroyCallback = undefined;
3966
+ /** @property {Function} - Callback when particle is created */
3967
+ this.particleCreateCallback = undefined;
3968
+ /** @property {Number} - Track particle emit time */
3927
3969
  this.emitTimeBuffer = 0;
3928
3970
  }
3929
3971
 
@@ -3955,18 +3997,16 @@ class ParticleEmitter extends EngineObject
3955
3997
  emitParticle()
3956
3998
  {
3957
3999
  // spawn a particle
3958
- let pos = this.emitSize.x != undefined ? // check if vec2 was used for size
3959
- vec2(rand(-.5,.5), rand(-.5,.5))
3960
- .multiply(this.emitSize).rotate(this.angle) // box emitter
3961
- : randInCircle(this.emitSize/2); // circle emitter
4000
+ let pos = typeof this.emitSize === 'number' ? // check if number was used
4001
+ randInCircle(this.emitSize/2) // circle emitter
4002
+ : vec2(rand(-.5,.5), rand(-.5,.5)) // box emitter
4003
+ .multiply(this.emitSize).rotate(this.angle)
3962
4004
  let angle = rand(this.particleConeAngle, -this.particleConeAngle);
3963
4005
  if (!this.localSpace)
3964
4006
  {
3965
4007
  pos = this.pos.add(pos);
3966
4008
  angle += this.angle;
3967
4009
  }
3968
-
3969
- const particle = new Particle(pos, this.tileInfo, this.tileSize, angle);
3970
4010
 
3971
4011
  // randomness scales each paremeter by a percentage
3972
4012
  const randomness = this.randomness;
@@ -3982,30 +4022,21 @@ class ParticleEmitter extends EngineObject
3982
4022
  const colorStart = randColor(this.colorStartA, this.colorStartB, this.randomColorLinear);
3983
4023
  const colorEnd = randColor(this.colorEndA, this.colorEndB, this.randomColorLinear);
3984
4024
  const velocityAngle = this.localSpace ? coneAngle : this.angle + coneAngle;
3985
-
3986
- // build particle settings
3987
- particle.colorStart = colorStart;
3988
- particle.colorEndDelta = colorEnd.subtract(colorStart);
3989
- particle.velocity = vec2().setAngle(velocityAngle, speed);
3990
- particle.angleVelocity = angleSpeed;
3991
- particle.lifeTime = particleTime;
3992
- particle.sizeStart = sizeStart;
3993
- particle.sizeEndDelta = sizeEnd - sizeStart;
3994
- particle.fadeRate = this.fadeRate;
3995
- particle.damping = this.damping;
3996
- particle.angleDamping = this.angleDamping;
3997
- particle.elasticity = this.elasticity;
3998
- particle.friction = this.friction;
3999
- particle.gravityScale = this.gravityScale;
4000
- particle.collideTiles = this.collideTiles;
4001
- particle.additive = this.additive;
4002
- particle.renderOrder = this.renderOrder;
4003
- particle.trailScale = this.trailScale;
4004
- particle.mirror = randInt(2);
4005
- particle.localSpaceEmitter = this.localSpace && this;
4006
-
4007
- // setup callbacks for particles
4008
- particle.destroyCallback = this.particleDestroyCallback;
4025
+
4026
+ // build particle
4027
+ const particle = new Particle(pos, this.tileInfo, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, this.fadeRate, this.additive, this.trailScale, this.localSpace && this, this.particleDestroyCallback);
4028
+ particle.velocity = vec2().setAngle(velocityAngle, speed);
4029
+ particle.fadeRate = this.fadeRate;
4030
+ particle.damping = this.damping;
4031
+ particle.angleDamping = this.angleDamping;
4032
+ particle.elasticity = this.elasticity;
4033
+ particle.friction = this.friction;
4034
+ particle.gravityScale = this.gravityScale;
4035
+ particle.collideTiles = this.collideTiles;
4036
+ particle.renderOrder = this.renderOrder;
4037
+ particle.mirror = !!randInt(2);
4038
+
4039
+ // call particle create callaback
4009
4040
  this.particleCreateCallback && this.particleCreateCallback(particle);
4010
4041
 
4011
4042
  // return the newly created particle
@@ -4024,13 +4055,47 @@ class ParticleEmitter extends EngineObject
4024
4055
  class Particle extends EngineObject
4025
4056
  {
4026
4057
  /**
4027
- * Create a particle with the given settings
4028
- * @param {Vector2} position - World space position of the particle
4029
- * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
4030
- * @param {Number} [angle=0] - Angle to rotate the particle
4058
+ * Create a particle with the given shis.colorStart = undefined;ettings
4059
+ * @param {Vector2} position - World space position of the particle
4060
+ * @param {TileInfo} [tileInfo] - Tile info to render particles
4061
+ * @param {Number} [angle] - Angle to rotate the particle
4062
+ * @param {Color} [colorStart] - Color at start of life
4063
+ * @param {Color} [colorEnd] - Color at end of life
4064
+ * @param {Number} [lifeTime] - How long to live for
4065
+ * @param {Number} [sizeStart] - Angle to rotate the particle
4066
+ * @param {Number} [sizeEnd] - Angle to rotate the particle
4067
+ * @param {Number} [fadeRate] - Angle to rotate the particle
4068
+ * @param {Boolean} [additive] - Angle to rotate the particle
4069
+ * @param {Number} [trailScale] - If a trail, how long to make it
4070
+ * @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
4071
+ * @param {Function} [destroyCallback] - Called when particle dies
4031
4072
  */
4032
- constructor(pos, tileInfo, angle)
4033
- { super(pos, vec2(), tileInfo, angle); }
4073
+ constructor(position, tileInfo, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, fadeRate, additive, trailScale, localSpaceEmitter, destroyCallback
4074
+ )
4075
+ {
4076
+ super(position, vec2(), tileInfo, angle);
4077
+
4078
+ /** @property {Color} - Color at start of life */
4079
+ this.colorStart = colorStart;
4080
+ /** @property {Color} - Calculated change in color */
4081
+ this.colorEndDelta = colorEnd.subtract(colorStart);
4082
+ /** @property {Number} - How long to live for */
4083
+ this.lifeTime = lifeTime;
4084
+ /** @property {Number} - Size at start of life */
4085
+ this.sizeStart = sizeStart;
4086
+ /** @property {Number} - Calculated change in size */
4087
+ this.sizeEndDelta = sizeEnd - sizeStart;
4088
+ /** @property {Number} - How quick to fade in/out */
4089
+ this.fadeRate = fadeRate;
4090
+ /** @property {Boolean} - Is it additive */
4091
+ this.additive = additive;
4092
+ /** @property {Number} - If a trail, how long to make it */
4093
+ this.trailScale = trailScale;
4094
+ /** @property {ParticleEmitter} - Parent emitter if local space */
4095
+ this.localSpaceEmitter = localSpaceEmitter;
4096
+ /** @property {Function} - Called when particle dies */
4097
+ this.destroyCallback = destroyCallback;
4098
+ }
4034
4099
 
4035
4100
  /** Render the particle, automatically called each frame, sorted by renderOrder */
4036
4101
  render()
@@ -4048,7 +4113,7 @@ class Particle extends EngineObject
4048
4113
  (p < fadeRate ? p/fadeRate : p > 1-fadeRate ? (1-p)/fadeRate : 1)); // fade alpha
4049
4114
 
4050
4115
  // draw the particle
4051
- this.additive && setBlendMode(1);
4116
+ this.additive && setBlendMode(true);
4052
4117
 
4053
4118
  let pos = this.pos, angle = this.angle;
4054
4119
  if (this.localSpaceEmitter)
@@ -4138,7 +4203,7 @@ class Medal
4138
4203
  * @param {Number} id - The unique identifier of the medal
4139
4204
  * @param {String} name - Name of the medal
4140
4205
  * @param {String} [description] - Description of the medal
4141
- * @param {String} [icon='🏆'] - Icon for the medal
4206
+ * @param {String} [icon] - Icon for the medal
4142
4207
  * @param {String} [src] - Image location for the medal
4143
4208
  */
4144
4209
  constructor(id, name, description='', icon='🏆', src)
@@ -4161,14 +4226,14 @@ class Medal
4161
4226
  return;
4162
4227
 
4163
4228
  // save the medal
4164
- ASSERT(medalsSaveName); // save name must be set
4229
+ ASSERT(medalsSaveName, 'save name must be set');
4165
4230
  localStorage[this.storageKey()] = this.unlocked = 1;
4166
4231
  medalsDisplayQueue.push(this);
4167
4232
  newgrounds && newgrounds.unlockMedal(this.id);
4168
4233
  }
4169
4234
 
4170
4235
  /** Render a medal
4171
- * @param {Number} [hidePercent=0] - How much to slide the medal off screen
4236
+ * @param {Number} [hidePercent] - How much to slide the medal off screen
4172
4237
  */
4173
4238
  render(hidePercent=0)
4174
4239
  {
@@ -4180,25 +4245,25 @@ class Medal
4180
4245
  // draw containing rect and clip to that region
4181
4246
  context.save();
4182
4247
  context.beginPath();
4183
- context.fillStyle = new Color(.9,.9,.9);
4184
- context.strokeStyle = new Color(0,0,0);
4248
+ context.fillStyle = rgb(.9,.9,.9).toString();
4249
+ context.strokeStyle = rgb(0,0,0).toString();
4185
4250
  context.lineWidth = 3;
4186
- context.fill(context.rect(x, y, width, medalDisplaySize.y));
4251
+ context.rect(x, y, width, medalDisplaySize.y);
4252
+ context.fill();
4187
4253
  context.stroke();
4188
4254
  context.clip();
4189
4255
 
4190
4256
  // draw the icon and text
4191
4257
  this.renderIcon(vec2(x+15+medalDisplayIconSize/2, y+medalDisplaySize.y/2));
4192
4258
  const pos = vec2(x+medalDisplayIconSize+30, y+28);
4193
- drawTextScreen(this.name, pos, 38, new Color(0,0,0), 0, 0, 'left');
4259
+ drawTextScreen(this.name, pos, 38, new Color(0,0,0), 0, undefined, 'left');
4194
4260
  pos.y += 32;
4195
- drawTextScreen(this.description, pos, 24, new Color(0,0,0), 0, 0, 'left');
4261
+ drawTextScreen(this.description, pos, 24, new Color(0,0,0), 0, undefined, 'left');
4196
4262
  context.restore();
4197
4263
  }
4198
4264
 
4199
4265
  /** Render the icon for a medal
4200
- * @param {Number} x - Screen space X position
4201
- * @param {Number} y - Screen space Y position
4266
+ * @param {Vector2} pos - Screen space position
4202
4267
  * @param {Number} [size=medalDisplayIconSize] - Screen space size
4203
4268
  */
4204
4269
  renderIcon(pos, size=medalDisplayIconSize)
@@ -4226,7 +4291,10 @@ function medalsRender()
4226
4291
  if (!medalsDisplayTimeLast)
4227
4292
  medalsDisplayTimeLast = timeReal;
4228
4293
  else if (time > medalDisplayTime)
4229
- medalsDisplayQueue.shift(medalsDisplayTimeLast = 0);
4294
+ {
4295
+ medalsDisplayTimeLast = 0;
4296
+ medalsDisplayQueue.shift();
4297
+ }
4230
4298
  else
4231
4299
  {
4232
4300
  // slide on/off medals
@@ -4266,8 +4334,8 @@ class Newgrounds
4266
4334
  * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher */
4267
4335
  constructor(app_id, cipher, cryptoJS)
4268
4336
  {
4269
- ASSERT(!newgrounds && app_id); // can only be one newgrounds object
4270
- ASSERT(!cipher || cryptoJS); // must provide cryptojs if there is a cipher
4337
+ ASSERT(!newgrounds && app_id>0, 'there can only be one newgrounds object');
4338
+ ASSERT(!cipher || cryptoJS, 'must provide cryptojs if there is a cipher');
4271
4339
 
4272
4340
  this.app_id = app_id;
4273
4341
  this.cipher = cipher;
@@ -4310,39 +4378,39 @@ class Newgrounds
4310
4378
  debugMedals && console.log(this.scoreboards);
4311
4379
 
4312
4380
  const keepAliveMS = 5 * 60 * 1e3;
4313
- setInterval(()=>this.call('Gateway.ping', 0, 1), keepAliveMS);
4381
+ setInterval(()=>this.call('Gateway.ping', 0, true), keepAliveMS);
4314
4382
  }
4315
4383
 
4316
4384
  /** Send message to unlock a medal by id
4317
4385
  * @param {Number} id - The medal id */
4318
- unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, 1); }
4386
+ unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, true); }
4319
4387
 
4320
4388
  /** Send message to post score
4321
4389
  * @param {Number} id - The scoreboard id
4322
4390
  * @param {Number} value - The score value */
4323
- postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); }
4391
+ postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, true); }
4324
4392
 
4325
4393
  /** Get scores from a scoreboard
4326
- * @param {Number} id - The scoreboard id
4327
- * @param {String} [user=0] - A user's id or name
4328
- * @param {Number} [social=0] - If true, only social scores will be loaded
4329
- * @param {Number} [skip=0] - Number of scores to skip before start
4330
- * @param {Number} [limit=10] - Number of scores to include in the list
4331
- * @return {Object} - The response JSON object
4394
+ * @param {Number} id - The scoreboard id
4395
+ * @param {String} [user] - A user's id or name
4396
+ * @param {Number} [social] - If true, only social scores will be loaded
4397
+ * @param {Number} [skip] - Number of scores to skip before start
4398
+ * @param {Number} [limit] - Number of scores to include in the list
4399
+ * @return {Object} - The response JSON object
4332
4400
  */
4333
- getScores(id, user=0, social=0, skip=0, limit=10)
4401
+ getScores(id, user, social=0, skip=0, limit=10)
4334
4402
  { return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
4335
4403
 
4336
4404
  /** Send message to log a view */
4337
- logView() { return this.call('App.logView', {'host':this.host}, 1); }
4405
+ logView() { return this.call('App.logView', {'host':this.host}, true); }
4338
4406
 
4339
4407
  /** Send a message to call a component of the Newgrounds API
4340
- * @param {String} component - Name of the component
4341
- * @param {Object} [parameters=0] - Parameters to use for call
4342
- * @param {Boolean} [async=0] - If true, don't wait for response before continuing (avoid stall)
4343
- * @return {Object} - The response JSON object
4408
+ * @param {String} component - Name of the component
4409
+ * @param {Object} [parameters] - Parameters to use for call
4410
+ * @param {Boolean} [async] - If true, don't wait for response before continuing
4411
+ * @return {Object} - The response JSON object
4344
4412
  */
4345
- call(component, parameters=0, async=0)
4413
+ call(component, parameters, async=false)
4346
4414
  {
4347
4415
  const call = {'component':component, 'parameters':parameters};
4348
4416
  if (this.cipher)
@@ -4377,7 +4445,7 @@ class Newgrounds
4377
4445
  return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
4378
4446
  }
4379
4447
  }
4380
- /**
4448
+ /**
4381
4449
  * LittleJS WebGL Interface
4382
4450
  * - All webgl used by the engine is wrapped up here
4383
4451
  * - For normal stuff you won't need to see or call anything in this file
@@ -4396,13 +4464,13 @@ class Newgrounds
4396
4464
  * @memberof WebGL */
4397
4465
  let glCanvas;
4398
4466
 
4399
- /** 2d context for glCanvas
4400
- * @type {WebGLRenderingContext}
4467
+ /** 2d context for glCanvas
4468
+ * @type {WebGL2RenderingContext}
4401
4469
  * @memberof WebGL */
4402
4470
  let glContext;
4403
4471
 
4404
4472
  // WebGL internal variables not exposed to documentation
4405
- let glActiveTexture, glShader, glArrayBuffer, glVertexData, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
4473
+ let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glInstanceCount, glAdditive, glBatchAdditive;
4406
4474
 
4407
4475
  ///////////////////////////////////////////////////////////////////////////////
4408
4476
 
@@ -4418,73 +4486,89 @@ function glInit()
4418
4486
 
4419
4487
  // setup vertex and fragment shaders
4420
4488
  glShader = glCreateProgram(
4421
- '#version 300 es\n' + // specify GLSL ES version
4422
- 'precision highp float;'+ // use highp for better accuracy
4423
- 'uniform mat4 m;'+ // transform matrix
4424
- 'in vec4 p,c,a;'+ // position, uv, color, additiveColor
4425
- 'out vec4 v,d,e;'+ // return uv, color, additiveColor
4426
- 'void main(){'+ // shader entry point
4427
- 'gl_Position=m*vec4(p.xy,1,1);'+ // transform position
4428
- 'v=p;d=c;e=a;'+ // pass stuff to fragment shader
4429
- '}' // end of shader
4489
+ '#version 300 es\n' + // specify GLSL ES version
4490
+ 'precision highp float;'+ // use highp for better accuracy
4491
+ 'uniform mat4 m;'+ // transform matrix
4492
+ 'in vec2 g;'+ // geometry
4493
+ 'in vec4 p,u,c,a;'+ // position/size, uvs, color, additiveColor
4494
+ 'in float r;'+ // rotation
4495
+ 'out vec2 v;'+ // return uv, color, additiveColor
4496
+ 'out vec4 d,e;'+ // return uv, color, additiveColor
4497
+ 'void main(){'+ // shader entry point
4498
+ 'vec2 s=(g-.5)*p.zw;'+ // get size offset
4499
+ 'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
4500
+ 'v=mix(u.xw,u.zy,g);'+ // pass uv to fragment shader
4501
+ 'd=c;e=a;'+ // pass colors to fragment shader
4502
+ '}' // end of shader
4430
4503
  ,
4431
- '#version 300 es\n' + // specify GLSL ES version
4432
- 'precision highp float;'+ // use highp for better accuracy
4433
- 'in vec4 v,d,e;'+ // position, uv, color, additiveColor
4434
- 'uniform sampler2D s;'+ // texture
4435
- 'out vec4 c;'+ // out color
4436
- 'void main(){'+ // shader entry point
4437
- 'c=texture(s,v.zw)*d+e;'+ // modulate texture by color plus additive
4438
- '}' // end of shader
4504
+ '#version 300 es\n' + // specify GLSL ES version
4505
+ 'precision highp float;'+ // use highp for better accuracy
4506
+ 'in vec2 v;'+ // uv
4507
+ 'in vec4 d,e;'+ // color, additiveColor
4508
+ 'uniform sampler2D s;'+ // texture
4509
+ 'out vec4 c;'+ // out color
4510
+ 'void main(){'+ // shader entry point
4511
+ 'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
4512
+ '}' // end of shader
4439
4513
  );
4440
4514
 
4441
4515
  // init buffers
4442
- glVertexData = new ArrayBuffer(gl_VERTEX_BUFFER_SIZE);
4443
- glPositionData = new Float32Array(glVertexData);
4444
- glColorData = new Uint32Array(glVertexData);
4516
+ const glInstanceData = new ArrayBuffer(gl_INSTANCE_BUFFER_SIZE);
4517
+ glPositionData = new Float32Array(glInstanceData);
4518
+ glColorData = new Uint32Array(glInstanceData);
4445
4519
  glArrayBuffer = glContext.createBuffer();
4446
- glBatchCount = 0;
4520
+ glGeometryBuffer = glContext.createBuffer();
4521
+
4522
+ // create the geometry buffer, triangle strip square
4523
+ const geometry = new Float32Array([glInstanceCount=0,0,1,0,0,1,1,1]);
4524
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
4525
+ glContext.bufferData(gl_ARRAY_BUFFER, geometry, gl_STATIC_DRAW);
4447
4526
  }
4448
4527
 
4449
4528
  // Setup render each frame, called automatically by engine
4450
4529
  function glPreRender()
4451
4530
  {
4452
4531
  // clear and set to same size as main canvas
4453
- glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
4532
+ glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
4454
4533
  glContext.clear(gl_COLOR_BUFFER_BIT);
4455
4534
 
4456
4535
  // set up the shader
4457
4536
  glContext.useProgram(glShader);
4458
4537
  glContext.activeTexture(gl_TEXTURE0);
4459
4538
  glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
4460
- glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
4461
- glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
4462
- glAdditive = 0;
4463
-
4539
+
4464
4540
  // set vertex attributes
4465
- let offset = 0;
4466
- const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
4541
+ let offset = glAdditive = glBatchAdditive = 0;
4542
+ let initVertexAttribArray = (name, type, typeSize, size)=>
4467
4543
  {
4468
4544
  const location = glContext.getAttribLocation(glShader, name);
4545
+ const stride = typeSize && gl_INSTANCE_BYTE_STRIDE; // only if not geometry
4546
+ const divisor = typeSize && 1; // only if not geometry
4547
+ const normalize = typeSize==1; // only if color
4469
4548
  glContext.enableVertexAttribArray(location);
4470
- glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
4549
+ glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
4550
+ glContext.vertexAttribDivisor(location, divisor);
4471
4551
  offset += size*typeSize;
4472
4552
  }
4473
- initVertexAttribArray('p', gl_FLOAT, 4, 4); // position & texture
4474
- initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
4475
- initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
4553
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
4554
+ initVertexAttribArray('g', gl_FLOAT, 0, 2); // geometry
4555
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
4556
+ glContext.bufferData(gl_ARRAY_BUFFER, gl_INSTANCE_BUFFER_SIZE, gl_DYNAMIC_DRAW);
4557
+ initVertexAttribArray('p', gl_FLOAT, 4, 4); // position & size
4558
+ initVertexAttribArray('u', gl_FLOAT, 4, 4); // texture coords
4559
+ initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4); // color
4560
+ initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4); // additiveColor
4561
+ initVertexAttribArray('r', gl_FLOAT, 4, 1); // rotation
4476
4562
 
4477
4563
  // build the transform matrix
4478
- const sx = 2 * cameraScale / mainCanvas.width;
4479
- const sy = 2 * cameraScale / mainCanvas.height;
4480
- const cx = -1 - sx*cameraPos.x;
4481
- const cy = -1 - sy*cameraPos.y;
4482
- glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
4564
+ const s = vec2(2*cameraScale).divide(mainCanvasSize);
4565
+ const p = vec2(-1).subtract(cameraPos.multiply(s));
4566
+ glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), false,
4483
4567
  new Float32Array([
4484
- sx, 0, 0, 0,
4485
- 0, sy, 0, 0,
4486
- 1, 1, -1, 1,
4487
- cx, cy, 0, 0
4568
+ s.x, 0, 0, 0,
4569
+ 0, s.y, 0, 0,
4570
+ 1, 1, 1, 1,
4571
+ p.x, p.y, 0, 0
4488
4572
  ])
4489
4573
  );
4490
4574
  }
@@ -4505,7 +4589,7 @@ function glSetTexture(texture)
4505
4589
 
4506
4590
  /** Compile WebGL shader of the given type, will throw errors if in debug mode
4507
4591
  * @param {String} source
4508
- * @param type
4592
+ * @param {Number} type
4509
4593
  * @return {WebGLShader}
4510
4594
  * @memberof WebGL */
4511
4595
  function glCompileShader(source, type)
@@ -4522,8 +4606,8 @@ function glCompileShader(source, type)
4522
4606
  }
4523
4607
 
4524
4608
  /** Create WebGL program with given shaders
4525
- * @param {WebGLShader} vsSource
4526
- * @param {WebGLShader} fsSource
4609
+ * @param {String} vsSource
4610
+ * @param {String} fsSource
4527
4611
  * @return {WebGLProgram}
4528
4612
  * @memberof WebGL */
4529
4613
  function glCreateProgram(vsSource, fsSource)
@@ -4541,7 +4625,7 @@ function glCreateProgram(vsSource, fsSource)
4541
4625
  }
4542
4626
 
4543
4627
  /** Create WebGL texture from an image and init the texture settings
4544
- * @param {Image} image
4628
+ * @param {HTMLImageElement} image
4545
4629
  * @return {WebGLTexture}
4546
4630
  * @memberof WebGL */
4547
4631
  function glCreateTexture(image)
@@ -4551,7 +4635,7 @@ function glCreateTexture(image)
4551
4635
  glContext.bindTexture(gl_TEXTURE_2D, texture);
4552
4636
  if (image)
4553
4637
  glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
4554
-
4638
+
4555
4639
  // use point filtering for pixelated rendering
4556
4640
  const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
4557
4641
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
@@ -4566,29 +4650,31 @@ function glCreateTexture(image)
4566
4650
  * @memberof WebGL */
4567
4651
  function glFlush()
4568
4652
  {
4569
- if (!glBatchCount) return;
4653
+ if (!glInstanceCount) return;
4570
4654
 
4571
4655
  const destBlend = glBatchAdditive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
4572
4656
  glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
4573
4657
  glContext.enable(gl_BLEND);
4574
4658
 
4575
4659
  // draw all the sprites in the batch and reset the buffer
4576
- glContext.bufferSubData(gl_ARRAY_BUFFER, 0, glVertexData);
4577
- glContext.drawArrays(gl_TRIANGLE_STRIP, 0, glBatchCount);
4578
- glBatchCount = 0;
4660
+ glContext.bufferSubData(gl_ARRAY_BUFFER, 0, glPositionData);
4661
+ glContext.drawArraysInstanced(gl_TRIANGLE_STRIP, 0, 4, glInstanceCount);
4662
+ if (showWatermark)
4663
+ drawCount += glInstanceCount;
4664
+ glInstanceCount = 0;
4579
4665
  glBatchAdditive = glAdditive;
4580
4666
  }
4581
4667
 
4582
4668
  /** Draw any sprites still in the buffer, copy to main canvas and clear
4583
4669
  * @param {CanvasRenderingContext2D} context
4584
- * @param {Boolean} [forceDraw=0]
4670
+ * @param {Boolean} [forceDraw]
4585
4671
  * @memberof WebGL */
4586
- function glCopyToContext(context, forceDraw)
4672
+ function glCopyToContext(context, forceDraw=false)
4587
4673
  {
4588
- if (!glBatchCount && !forceDraw) return;
4589
-
4674
+ if (!glInstanceCount && !forceDraw) return;
4675
+
4590
4676
  glFlush();
4591
-
4677
+
4592
4678
  // do not draw in overlay mode because the canvas is visible
4593
4679
  if (!glOverlay || forceDraw)
4594
4680
  context.drawImage(glCanvas, 0, 0);
@@ -4610,60 +4696,22 @@ function glCopyToContext(context, forceDraw)
4610
4696
  function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdditive=0)
4611
4697
  {
4612
4698
  // flush if there is not enough room or if different blend mode
4613
- const vertCount = 6;
4614
- if (glBatchCount >= gl_MAX_BATCH-vertCount || glBatchAdditive != glAdditive)
4699
+ if (glInstanceCount >= gl_MAX_INSTANCES-1 || glBatchAdditive != glAdditive)
4615
4700
  glFlush();
4616
4701
 
4617
- // prepare to create the verts from size and angle
4618
- const c = Math.cos(angle)/2, s = Math.sin(angle)/2;
4619
- const cx = c*sizeX, cy = c*sizeY, sx = s*sizeX, sy = s*sizeY;
4620
- const positionData =
4621
- [
4622
- x-cx+sy, y+cy+sx, uv0X, uv0Y,
4623
- x-cx-sy, y-cy+sx, uv0X, uv1Y,
4624
- x+cx+sy, y+cy-sx, uv1X, uv0Y,
4625
- x+cx-sy, y-cy-sx, uv1X, uv1Y,
4626
- ];
4627
-
4628
- // setup 2 triangle strip quad
4629
- for(let i = vertCount, offset = glBatchCount * gl_INDICIES_PER_VERT; i--;)
4630
- {
4631
- const j = clamp(i-1, 0, 3)*4; // degenerate tri at ends
4632
- glPositionData[offset++] = positionData[j+0];
4633
- glPositionData[offset++] = positionData[j+1];
4634
- glPositionData[offset++] = positionData[j+2];
4635
- glPositionData[offset++] = positionData[j+3];
4636
- glColorData[offset++] = rgba;
4637
- glColorData[offset++] = rgbaAdditive;
4638
- }
4639
- glBatchCount += vertCount;
4640
- }
4641
-
4642
- /** Add a convex polygon to the gl draw list
4643
- * @param {Array} points - Array of Vector2 points
4644
- * @param {Number} rgba - Color of the polygon
4645
- * @memberof WebGL */
4646
- function glDrawPoints(points, rgba)
4647
- {
4648
- // flush if there is not enough room or if different blend mode
4649
- const vertCount = points.length + 2;
4650
- if (glBatchCount >= gl_MAX_BATCH-vertCount || glBatchAdditive != glAdditive)
4651
- glFlush();
4652
-
4653
- // setup triangle strip from list of points
4654
- for(let i = vertCount, offset = glBatchCount * gl_INDICIES_PER_VERT; i--;)
4655
- {
4656
- const j = clamp(i-1, 0, vertCount-3); // degenerate tri at ends
4657
- const h = j>>1;
4658
- const point = points[j%2? h : vertCount-3-h];
4659
- glPositionData[offset++] = point.x;
4660
- glPositionData[offset++] = point.y;
4661
- glPositionData[offset++] = 0; // uvx
4662
- glPositionData[offset++] = 0; // uvy
4663
- glColorData[offset++] = 0; // nothing to tint
4664
- glColorData[offset++] = rgba; // apply rgba via additive
4665
- }
4666
- glBatchCount += vertCount;
4702
+ let offset = glInstanceCount * gl_INDICIES_PER_INSTANCE;
4703
+ glPositionData[offset++] = x;
4704
+ glPositionData[offset++] = y;
4705
+ glPositionData[offset++] = sizeX;
4706
+ glPositionData[offset++] = sizeY;
4707
+ glPositionData[offset++] = uv0X;
4708
+ glPositionData[offset++] = uv0Y;
4709
+ glPositionData[offset++] = uv1X;
4710
+ glPositionData[offset++] = uv1Y;
4711
+ glColorData[offset++] = rgba;
4712
+ glColorData[offset++] = rgbaAdditive;
4713
+ glPositionData[offset++] = angle;
4714
+ glInstanceCount++;
4667
4715
  }
4668
4716
 
4669
4717
  ///////////////////////////////////////////////////////////////////////////////
@@ -4677,7 +4725,7 @@ let glPostShader, glPostArrayBuffer, glPostTexture, glPostIncludeOverlay;
4677
4725
  * @memberof WebGL */
4678
4726
  function glInitPostProcess(shaderCode, includeOverlay)
4679
4727
  {
4680
- ASSERT(!glPostShader); // can only have 1 post effects shader
4728
+ ASSERT(!glPostShader, 'can only have 1 post effects shader');
4681
4729
 
4682
4730
  if (!shaderCode) // default shader pass through
4683
4731
  shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
@@ -4688,14 +4736,14 @@ function glInitPostProcess(shaderCode, includeOverlay)
4688
4736
  'precision highp float;'+ // use highp for better accuracy
4689
4737
  'in vec2 p;'+ // position
4690
4738
  'void main(){'+ // shader entry point
4691
- 'gl_Position=vec4(p,1,1);'+ // set position
4739
+ 'gl_Position=vec4(p+p-1.,1,1);'+ // set position
4692
4740
  '}' // end of shader
4693
4741
  ,
4694
4742
  '#version 300 es\n' + // specify GLSL ES version
4695
4743
  'precision highp float;'+ // use highp for better accuracy
4696
4744
  'uniform sampler2D iChannel0;'+ // input texture
4697
4745
  'uniform vec3 iResolution;'+ // size of output texture
4698
- 'uniform float iTime;'+ // time passed
4746
+ 'uniform float iTime;'+ // time
4699
4747
  'out vec4 c;'+ // out color
4700
4748
  '\n' + shaderCode + '\n'+ // insert custom shader code
4701
4749
  'void main(){'+ // shader entry point
@@ -4706,7 +4754,7 @@ function glInitPostProcess(shaderCode, includeOverlay)
4706
4754
 
4707
4755
  // create buffer and texture
4708
4756
  glPostArrayBuffer = glContext.createBuffer();
4709
- glPostTexture = glCreateTexture();
4757
+ glPostTexture = glCreateTexture(undefined);
4710
4758
  glPostIncludeOverlay = includeOverlay;
4711
4759
 
4712
4760
  // hide the original 2d canvas
@@ -4742,10 +4790,9 @@ function glRenderPostProcess()
4742
4790
 
4743
4791
  // setup shader program to draw one triangle
4744
4792
  glContext.useProgram(glPostShader);
4793
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
4794
+ glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, 1);
4745
4795
  glContext.disable(gl_BLEND);
4746
- glContext.bindBuffer(gl_ARRAY_BUFFER, glPostArrayBuffer);
4747
- glContext.bufferData(gl_ARRAY_BUFFER, new Float32Array([-3,1,1,-3,1,1]), gl_STATIC_DRAW);
4748
- glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, true);
4749
4796
 
4750
4797
  // set textures, pass in the 2d canvas and gl canvas in separate texture channels
4751
4798
  glContext.activeTexture(gl_TEXTURE0);
@@ -4756,19 +4803,19 @@ function glRenderPostProcess()
4756
4803
  const vertexByteStride = 8;
4757
4804
  const pLocation = glContext.getAttribLocation(glPostShader, 'p');
4758
4805
  glContext.enableVertexAttribArray(pLocation);
4759
- glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, 0, vertexByteStride, 0);
4806
+ glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, false, vertexByteStride, 0);
4760
4807
 
4761
4808
  // set uniforms and draw
4762
4809
  const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
4763
4810
  glContext.uniform1i(uniformLocation('iChannel0'), 0);
4764
4811
  glContext.uniform1f(uniformLocation('iTime'), time);
4765
4812
  glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
4766
- glContext.drawArrays(gl_TRIANGLE_STRIP, 0, 3);
4813
+ glContext.drawArrays(gl_TRIANGLE_STRIP, 0, 4);
4767
4814
  }
4768
4815
 
4769
4816
  ///////////////////////////////////////////////////////////////////////////////
4770
4817
  // store gl constants as integers so their name doesn't use space in minifed
4771
- const
4818
+ const
4772
4819
  gl_ONE = 1,
4773
4820
  gl_TRIANGLE_STRIP = 5,
4774
4821
  gl_SRC_ALPHA = 770,
@@ -4790,17 +4837,17 @@ gl_TEXTURE0 = 33984,
4790
4837
  gl_ARRAY_BUFFER = 34962,
4791
4838
  gl_STATIC_DRAW = 35044,
4792
4839
  gl_DYNAMIC_DRAW = 35048,
4793
- gl_FRAGMENT_SHADER = 35632,
4840
+ gl_FRAGMENT_SHADER = 35632,
4794
4841
  gl_VERTEX_SHADER = 35633,
4795
4842
  gl_COMPILE_STATUS = 35713,
4796
4843
  gl_LINK_STATUS = 35714,
4797
4844
  gl_UNPACK_FLIP_Y_WEBGL = 37440,
4798
4845
 
4799
4846
  // constants for batch rendering
4800
- gl_INDICIES_PER_VERT = 6,
4801
- gl_MAX_BATCH = 1e5,
4802
- gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
4803
- gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTEX_BYTE_STRIDE;
4847
+ gl_INDICIES_PER_INSTANCE = 11,
4848
+ gl_MAX_INSTANCES = 1e4,
4849
+ gl_INSTANCE_BYTE_STRIDE = gl_INDICIES_PER_INSTANCE * 4, // 11 * 4
4850
+ gl_INSTANCE_BUFFER_SIZE = gl_MAX_INSTANCES * gl_INSTANCE_BYTE_STRIDE;
4804
4851
  /**
4805
4852
  * LittleJS - The Tiny JavaScript Game Engine That Can!
4806
4853
  * MIT License - Copyright 2021 Frank Force
@@ -4833,7 +4880,7 @@ const engineName = 'LittleJS';
4833
4880
  * @type {String}
4834
4881
  * @default
4835
4882
  * @memberof Engine */
4836
- const engineVersion = '1.8.8';
4883
+ const engineVersion = '1.9.0';
4837
4884
 
4838
4885
  /** Frames per second to update objects
4839
4886
  * @type {Number}
@@ -4874,14 +4921,14 @@ let timeReal = 0;
4874
4921
 
4875
4922
  /** Is the game paused? Causes time and objects to not be updated
4876
4923
  * @type {Boolean}
4877
- * @default 0
4924
+ * @default false
4878
4925
  * @memberof Engine */
4879
- let paused = 0;
4926
+ let paused = false;
4880
4927
 
4881
4928
  /** Set if game is paused
4882
- * @param {Boolean} paused
4929
+ * @param {Boolean} isPaused
4883
4930
  * @memberof Engine */
4884
- function setPaused(_paused) { paused = _paused; }
4931
+ function setPaused(isPaused) { paused = isPaused; }
4885
4932
 
4886
4933
  // Frame time tracking
4887
4934
  let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
@@ -4898,7 +4945,7 @@ let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
4898
4945
  * @memberof Engine */
4899
4946
  function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=['tiles.png'])
4900
4947
  {
4901
- ASSERT(Array.isArray(imageSources)); // pass in images as array
4948
+ ASSERT(Array.isArray(imageSources), 'pass in images as array');
4902
4949
 
4903
4950
  // internal update loop for engine
4904
4951
  function engineUpdate(frameTimeMS=0)
@@ -4908,12 +4955,12 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4908
4955
  frameTimeLastMS = frameTimeMS;
4909
4956
  if (debug || showWatermark)
4910
4957
  averageFPS = lerp(.05, averageFPS, 1e3/(frameTimeDeltaMS||1));
4911
- const debugSpeedUp = debug && keyIsDown(107); // +
4912
- const debugSpeedDown = debug && keyIsDown(109); // -
4958
+ const debugSpeedUp = debug && keyIsDown('Equal'); // +
4959
+ const debugSpeedDown = debug && keyIsDown('Minus'); // -
4913
4960
  if (debug) // +/- to speed/slow time
4914
4961
  frameTimeDeltaMS *= debugSpeedUp ? 5 : debugSpeedDown ? .2 : 1;
4915
4962
  timeReal += frameTimeDeltaMS / 1e3;
4916
- frameTimeBufferMS += !paused * frameTimeDeltaMS;
4963
+ frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
4917
4964
  if (!debugSpeedUp)
4918
4965
  frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp incase of slow framerate
4919
4966
 
@@ -5023,7 +5070,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5023
5070
  'user-select:none;' + // prevent mobile hold to select
5024
5071
  '-webkit-user-select:none;' + // compatibility for ios
5025
5072
  '-webkit-touch-callout:none'; // compatibility for ios
5026
- document.body.style = styleBody;
5073
+ document.body.style.cssText = styleBody;
5027
5074
  document.body.appendChild(mainCanvas = document.createElement('canvas'));
5028
5075
  mainContext = mainCanvas.getContext('2d');
5029
5076
 
@@ -5036,10 +5083,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5036
5083
  overlayContext = overlayCanvas.getContext('2d');
5037
5084
 
5038
5085
  // set canvas style
5039
- const styleCanvas =
5040
- 'position:absolute;' + // position
5086
+ const styleCanvas = 'position:absolute;' + // position
5041
5087
  'top:50%;left:50%;transform:translate(-50%,-50%)'; // center
5042
- (glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
5088
+ (glCanvas||mainCanvas).style.cssText = mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
5043
5089
 
5044
5090
  // create promises for loading images
5045
5091
  const promises = imageSources.map((src, textureIndex)=>
@@ -5059,13 +5105,13 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5059
5105
  showSplashScreen && promises.push(new Promise(resolve =>
5060
5106
  {
5061
5107
  let t = 0;
5062
- console.log(`LittleJS Engine v${engineVersion}`);
5108
+ console.log(`${engineName} Engine v${engineVersion}`);
5063
5109
  updateSplash();
5064
5110
  function updateSplash()
5065
5111
  {
5066
5112
  clearInput();
5067
5113
  drawEngineSplashScreen(t+=.01);
5068
- t>1 ? resolve() : setTimeout(updateSplash,16);
5114
+ t>1 ? resolve() : setTimeout(updateSplash, 16);
5069
5115
  }
5070
5116
  }));
5071
5117
 
@@ -5126,7 +5172,7 @@ function engineObjectsDestroy()
5126
5172
 
5127
5173
  /** Triggers a callback for each object within a given area
5128
5174
  * @param {Vector2} [pos] - Center of test area
5129
- * @param {Number} [size] - Radius of circle if float, rectangle size if Vector2
5175
+ * @param {Number|Vector2} [size] - Radius of circle if float, rectangle size if Vector2
5130
5176
  * @param {Function} [callbackFunction] - Calls this function on every object that passes the test
5131
5177
  * @param {Array} [objects=engineObjects] - List of objects to check
5132
5178
  * @memberof Engine */
@@ -5137,7 +5183,7 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
5137
5183
  for (const o of objects)
5138
5184
  callbackFunction(o);
5139
5185
  }
5140
- else if (size.x != undefined) // bounding box test
5186
+ else if (typeof size === 'object') // bounding box test
5141
5187
  {
5142
5188
  for (const o of objects)
5143
5189
  isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
@@ -5155,21 +5201,23 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
5155
5201
 
5156
5202
  function drawEngineSplashScreen(t)
5157
5203
  {
5158
- // background
5159
- const grayscale = 0;
5160
- const x = mainContext;
5161
- const w = mainCanvas.width = innerWidth;
5162
- const h = mainCanvas.height = innerHeight;
5163
- const p3 = percent(t, 1, .8);
5164
- const p4 = percent(t, 0, .5);
5165
- const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,Math.hypot(w,h)*.7);
5166
- g.addColorStop(0,hsl(0,0,lerp(p4,0,p3/2),p3));
5167
- g.addColorStop(1,hsl(0,0,0,p3));
5168
- x.save();
5169
- x.fillStyle = g;
5170
- x.fillRect(0,0,w,h);
5171
-
5172
- // logo - fade in and out
5204
+ const x = overlayContext;
5205
+ const w = overlayCanvas.width = innerWidth;
5206
+ const h = overlayCanvas.height = innerHeight;
5207
+
5208
+ {
5209
+ // background
5210
+ const p3 = percent(t, 1, .8);
5211
+ const p4 = percent(t, 0, .5);
5212
+ const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,Math.hypot(w,h)*.7);
5213
+ g.addColorStop(0,hsl(0,0,lerp(p4,0,p3/2),p3).toString());
5214
+ g.addColorStop(1,hsl(0,0,0,p3).toString());
5215
+ x.save();
5216
+ x.fillStyle = g;
5217
+ x.fillRect(0,0,w,h);
5218
+ }
5219
+
5220
+ // draw LittleJS logo...
5173
5221
  const rect = (X, Y, W, H, C)=>
5174
5222
  {
5175
5223
  x.beginPath();
@@ -5194,7 +5242,7 @@ function drawEngineSplashScreen(t)
5194
5242
  C ? x.fill() : x.stroke();
5195
5243
  };
5196
5244
  const color = (c=0, l=0) =>
5197
- hsl([.98,.3,.57,.14][c%4]-10,grayscale?0:.8,[0,.3,.5,.8,.9][l]);
5245
+ hsl([.98,.3,.57,.14][c%4]-10,.8,[0,.3,.5,.8,.9][l]).toString();
5198
5246
  const alpha = wave(1,1,t);
5199
5247
  const p = percent(alpha, .1, .5);
5200
5248
 
@@ -5204,7 +5252,7 @@ function drawEngineSplashScreen(t)
5204
5252
  x.scale(size,size);
5205
5253
  x.translate(-40,-35);
5206
5254
  x.lineJoin = x.lineCap = 'round';
5207
- x.lineWidth = 1+p;
5255
+ x.lineWidth = .1 + p*1.9;
5208
5256
 
5209
5257
  // drawing effect
5210
5258
  const p2 = percent(alpha,.1,1);
@@ -5229,7 +5277,7 @@ function drawEngineSplashScreen(t)
5229
5277
 
5230
5278
  // little stack
5231
5279
  rect(37,14,9,6,color(3,2));
5232
- rect(37,14,4,6,color(3,3));
5280
+ rect(37,14,4.5,6,color(3,3));
5233
5281
  rect(37,14,9,6);
5234
5282
 
5235
5283
  // big stack
@@ -5237,9 +5285,9 @@ function drawEngineSplashScreen(t)
5237
5285
  rect(50,20,6,-10,color(0,2));
5238
5286
  rect(50,20,3,-10,color(0,3));
5239
5287
  rect(50,10,10,10);
5240
- circle(55,2,11,.5,PI-.5,color(3,3));
5241
- circle(55,2,11,.5,PI/2,color(3,2),1);
5242
- circle(55,2,11,.5,PI-.5);
5288
+ circle(55,2,11.4,.5,PI-.5,color(3,3));
5289
+ circle(55,2,11.4,.5,PI/2,color(3,2),1);
5290
+ circle(55,2,11.4,.5,PI-.5);
5243
5291
  rect(45,7,20,-7,color(0,2));
5244
5292
  rect(45,0,20,3,color(0,3));
5245
5293
  rect(45,0,20,7);
@@ -5273,12 +5321,14 @@ function drawEngineSplashScreen(t)
5273
5321
  x.lineTo(53+(1+i*2.9)*p,40);
5274
5322
  x.lineTo(53+(4+i*3.5)*p,54);
5275
5323
  x.fillStyle = color(0,i%2+2);
5276
- x.fill() || i%2 && x.stroke();
5324
+ x.fill();
5325
+ i%2 && x.stroke();
5277
5326
  }
5278
5327
 
5279
5328
  // wheels
5280
- rect(5,40,9,6,color());
5281
- rect(15,54,38,-14,color())
5329
+ rect(6,40,5,5);
5330
+ rect(6,40,5,5,color());
5331
+ rect(15,54,38,-14,color());
5282
5332
  for (let i=3; i--;)
5283
5333
  for (let j=2; j--;)
5284
5334
  {
@@ -5287,26 +5337,26 @@ function drawEngineSplashScreen(t)
5287
5337
  circle(15*i+15,47,j?7:1,0,PI,color(i,2));
5288
5338
  x.stroke();
5289
5339
  }
5290
- line(6,40,68,40) // center
5291
- line(77,54,4,54) // bottom
5340
+ line(6,40,68,40); // center
5341
+ line(77,54,4,54); // bottom
5292
5342
 
5293
- // text
5343
+ // draw engine name
5294
5344
  const s = engineName;
5295
5345
  x.font = '900 16px arial';
5296
5346
  x.textAlign = 'center';
5297
5347
  x.textBaseline = 'top';
5298
- x.lineWidth = 1+p*3
5348
+ x.lineWidth = .1+p*3.9;
5299
5349
  let w2 = 0;
5300
5350
  for (let i=0; i<s.length; ++i)
5301
5351
  w2 += x.measureText(s[i]).width;
5302
5352
  for (let j=2; j--;)
5303
5353
  for (let i=0, X=41-w2/2; i<s.length; ++i)
5304
5354
  {
5305
- x.fillStyle = color(i,grayscale?3:2);
5355
+ x.fillStyle = color(i,2);
5306
5356
  const w = x.measureText(s[i]).width;
5307
5357
  x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
5308
5358
  X += w;
5309
5359
  }
5310
-
5360
+
5311
5361
  x.restore();
5312
5362
  }