littlejsengine 1.8.9 → 1.9.1

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 (48) hide show
  1. package/README.md +17 -17
  2. package/build/littlejs.d.ts +352 -288
  3. package/build/littlejs.esm.js +695 -658
  4. package/build/littlejs.esm.min.js +1 -1
  5. package/build/littlejs.js +694 -658
  6. package/build/littlejs.min.js +1 -1
  7. package/build/littlejs.release.js +629 -594
  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/build.js +1 -1
  15. package/examples/js13k/index.html +13 -13
  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/build.js +1 -1
  28. package/examples/starter/game.js +2 -2
  29. package/examples/starter/index.html +13 -13
  30. package/examples/stress/index.html +35 -27
  31. package/examples/typescript/index.html +1 -1
  32. package/index.d.ts +2094 -0
  33. package/package.json +1 -1
  34. package/src/engine.js +28 -28
  35. package/src/engineAudio.js +57 -57
  36. package/src/engineDebug.js +66 -65
  37. package/src/engineDraw.js +64 -73
  38. package/src/engineExport.js +1 -0
  39. package/src/engineInput.js +57 -40
  40. package/src/engineMedals.js +32 -29
  41. package/src/engineObject.js +42 -27
  42. package/src/engineParticles.js +98 -72
  43. package/src/engineRelease.js +1 -1
  44. package/src/engineSettings.js +22 -22
  45. package/src/engineTileLayer.js +66 -60
  46. package/src/engineUtilities.js +49 -49
  47. package/src/engineWebGL.js +112 -135
  48. package/src/jsconfig.json +10 -0
@@ -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
@@ -533,11 +534,11 @@ function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
533
534
  function nearestPowerOfTwo(value) { return 2**Math.ceil(Math.log2(value)); }
534
535
 
535
536
  /** Returns true if two axis aligned bounding boxes are overlapping
536
- * @param {Vector2} pointA - Center of box A
537
- * @param {Vector2} sizeA - Size of box A
538
- * @param {Vector2} pointB - Center of box B
539
- * @param {Vector2} sizeB - Size of box B
540
- * @return {Boolean} - True if overlapping
537
+ * @param {Vector2} pointA - Center of box A
538
+ * @param {Vector2} sizeA - Size of box A
539
+ * @param {Vector2} pointB - Center of box B
540
+ * @param {Vector2} sizeB - Size of box B
541
+ * @return {Boolean} - True if overlapping
541
542
  * @memberof Utilities */
542
543
  function isOverlapping(pointA, sizeA, pointB, sizeB)
543
544
  {
@@ -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=(0,0)] - World space position of the object
1561
+ * @param {Vector2} [size=(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=(1,1,1,1)] - 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} */
@@ -1846,8 +1862,8 @@ class EngineObject
1846
1862
 
1847
1863
  /** Attaches a child to this with a given local transform
1848
1864
  * @param {EngineObject} child
1849
- * @param {Vector2} [localPos=Vector2()]
1850
- * @param {Number} [localAngle=0] */
1865
+ * @param {Vector2} [localPos=(0,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=(0,0)] - 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)
@@ -2006,9 +2022,9 @@ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
2006
2022
  class TileInfo
2007
2023
  {
2008
2024
  /** Create a tile info object
2009
- * @param {Vector2} [pos=Vector2()] - Top left corner of tile in pixels
2025
+ * @param {Vector2} [pos=(0,0)] - 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);
@@ -2080,26 +2099,24 @@ function worldToScreen(worldPos)
2080
2099
  }
2081
2100
 
2082
2101
  /** Draw textured tile centered in world space, with color applied if using WebGL
2083
- * @param {Vector2} pos - Center of the tile in world space
2084
- * @param {Vector2} [size=Vector2(1,1)] - Size of the tile in world space
2085
- * @param {TileInfo}[tileInfo] - Tile info to use, untextured if undefined
2086
- * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
2087
- * @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
2090
- * @param {Color} [additiveColor=Color(0,0,0,0)] - Additive color to be applied
2091
- * @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
2092
- * @param {Boolean} [screenSpace=0] - If true the pos and size are in screen space
2093
- * @param {CanvasRenderingContext2D} [context] - Canvas 2D context to draw to
2102
+ * @param {Vector2} pos - Center of the tile in world space
2103
+ * @param {Vector2} [size=(1,1)] - Size of the tile in world space
2104
+ * @param {TileInfo}[tileInfo] - Tile info to use, untextured if undefined
2105
+ * @param {Color} [color=(1,1,1,1)] - Color to modulate with
2106
+ * @param {Number} [angle] - Angle to rotate by
2107
+ * @param {Boolean} [mirror] - If true image is flipped along the Y axis
2108
+ * @param {Color} [additiveColor=(0,0,0,0)] - Additive color to be applied
2109
+ * @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
2110
+ * @param {Boolean} [screenSpace=false] - If true the pos and size are in screen space
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)
@@ -2158,51 +2176,40 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
2158
2176
 
2159
2177
  /** Draw colored rect centered on pos
2160
2178
  * @param {Vector2} pos
2161
- * @param {Vector2} [size=Vector2(1,1)]
2162
- * @param {Color} [color=Color()]
2163
- * @param {Number} [angle=0]
2179
+ * @param {Vector2} [size=(1,1)]
2180
+ * @param {Color} [color=(1,1,1,1)]
2181
+ * @param {Number} [angle]
2164
2182
  * @param {Boolean} [useWebGL=glEnable]
2165
- * @param {Boolean} [screenSpace=0]
2183
+ * @param {Boolean} [screenSpace=false]
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
- * @param {Color} [color=Color()]
2176
- * @param {Boolean} [useWebGL=glEnable]
2177
- * @param {Boolean} [screenSpace=0]
2178
- * @param {CanvasRenderingContext2D} [context]
2193
+ * @param {Color} [color=(1,1,1,1)]
2194
+ * @param {Boolean} [screenSpace=false]
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]
2203
- * @param {Color} [color=Color()]
2209
+ * @param {Number} [thickness]
2210
+ * @param {Color} [color=(1,1,1,1)]
2204
2211
  * @param {Boolean} [useWebGL=glEnable]
2205
- * @param {Boolean} [screenSpace=0]
2212
+ * @param {Boolean} [screenSpace=false]
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=false]
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]
2263
- * @param {Color} [color=Color()]
2264
- * @param {Number} [lineWidth=0]
2265
- * @param {Color} [lineColor=Color(0,0,0)]
2266
- * @param {String} [textAlign='center']
2269
+ * @param {Number} [size]
2270
+ * @param {Color} [color=(1,1,1,1)]
2271
+ * @param {Number} [lineWidth]
2272
+ * @param {Color} [lineColor=(0,0,0,1)]
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]
2280
- * @param {Color} [color=Color()]
2281
- * @param {Number} [lineWidth=0]
2282
- * @param {Color} [lineColor=Color(0,0,0)]
2283
- * @param {String} [textAlign='center']
2286
+ * @param {Number} [size]
2287
+ * @param {Color} [color=(1,1,1,1)]
2288
+ * @param {Number} [lineWidth]
2289
+ * @param {Color} [lineColor=(0,0,0,1)]
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';
@@ -2322,9 +2329,9 @@ let engineFontImage;
2322
2329
  class FontImage
2323
2330
  {
2324
2331
  /** Create an image font
2325
- * @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
2332
+ * @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
2333
+ * @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
2334
+ * @param {Vector2} [paddingSize=(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; mousePosScreen = mouseToScreen(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)=> mouseWheel = e.ctrlKey ? 0 : 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|Array} [pattern] - single value in ms 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.3.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,27 +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)
3235
- * @param {Number} [filter=0] - Filter cutoff frequency, positive for HPF, negative for LPF (Hz)
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)
3236
3260
  * @return {Array} - Array of audio samples
3237
3261
  * @memberof Audio
3238
3262
  */
@@ -3280,7 +3304,7 @@ function zzfxG
3280
3304
  if (!(++c%(bitCrush*100|0))) // bit crush
3281
3305
  {
3282
3306
  s = shape? shape>1? shape>2? shape>3? // wave shape
3283
- Math.sin(t*t) : // 4 noise
3307
+ Math.sin(t**3) : // 4 noise
3284
3308
  clamp(Math.tan(t),1,-1): // 3 tan
3285
3309
  1-(2*t/PI2%2+2)%2: // 2 saw
3286
3310
  1-4*abs(Math.round(t/PI2)-t/PI2): // 1 triangle
@@ -3337,7 +3361,7 @@ function zzfxG
3337
3361
  * @param {Array} instruments - Array of ZzFX sound paramaters
3338
3362
  * @param {Array} patterns - Array of pattern data
3339
3363
  * @param {Array} sequence - Array of pattern indexes
3340
- * @param {Number} [BPM=125] - Playback speed of the song in BPM
3364
+ * @param {Number} [BPM] - Playback speed of the song in BPM
3341
3365
  * @return {Array} - Left and right channel sample data
3342
3366
  * @memberof Audio */
3343
3367
  function zzfxM(instruments, patterns, sequence, BPM = 125)
@@ -3376,10 +3400,10 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
3376
3400
  patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
3377
3401
 
3378
3402
  // check if there are more channels
3379
- hasMore ||= !!patterns[patternIndex][channelIndex];
3403
+ hasMore |= patterns[patternIndex][channelIndex]&&1;
3380
3404
 
3381
3405
  // get next offset, use the length of first channel
3382
- nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - !notFirstBeat) * beatLength;
3406
+ nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - (notFirstBeat?0:1)) * beatLength;
3383
3407
  // for each beat in pattern, plus one extra if end of sequence
3384
3408
  isSequenceEnd = sequenceIndex == sequence.length - 1;
3385
3409
  for (i = 2, k = outSampleOffset; i < patternChannel.length + isSequenceEnd; notFirstBeat = ++i) {
@@ -3395,7 +3419,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
3395
3419
  for (j = 0; j < beatLength && notFirstBeat;
3396
3420
 
3397
3421
  // fade off attenuation at end of beat if stopping note, prevents clicking
3398
- j++ > beatLength - 99 && stop ? attenuation += (attenuation < 1) / 99 : 0
3422
+ j++ > beatLength - 99 && stop && attenuation < 1? attenuation += 1 / 99 : 0
3399
3423
  ) {
3400
3424
  // copy sample to stereo buffers with panning
3401
3425
  sample = (1 - attenuation) * sampleBuffer[sampleOffset++] / 2 || 0;
@@ -3471,7 +3495,7 @@ function initTileCollision(size)
3471
3495
 
3472
3496
  /** Set tile collision data
3473
3497
  * @param {Vector2} pos
3474
- * @param {Number} [data=0]
3498
+ * @param {Number} [data]
3475
3499
  * @memberof TileCollision */
3476
3500
  function setTileCollisionData(pos, data=0)
3477
3501
  {
@@ -3489,7 +3513,7 @@ function getTileCollisionData(pos)
3489
3513
 
3490
3514
  /** Check if collision with another object should occur
3491
3515
  * @param {Vector2} pos
3492
- * @param {Vector2} [size=Vector2(1,1)]
3516
+ * @param {Vector2} [size=(1,1)]
3493
3517
  * @param {EngineObject} [object]
3494
3518
  * @return {Boolean}
3495
3519
  * @memberof TileCollision */
@@ -3504,7 +3528,7 @@ function tileCollisionTest(pos, size=vec2(), object)
3504
3528
  {
3505
3529
  const tileData = tileCollision[y*tileCollisionSize.x+x];
3506
3530
  if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
3507
- return 1;
3531
+ return true;
3508
3532
  }
3509
3533
  }
3510
3534
 
@@ -3570,11 +3594,11 @@ function tileCollisionRaycast(posStart, posEnd, object)
3570
3594
  class TileLayerData
3571
3595
  {
3572
3596
  /** Create a tile layer data object, one for each tile in a TileLayer
3573
- * @param {Number} [tile] - The tile to use, untextured if undefined
3574
- * @param {Number} [direction=0] - Integer direction of tile, in 90 degree increments
3575
- * @param {Boolean} [mirror=0] - If the tile should be mirrored along the x axis
3576
- * @param {Color} [color=Color()] - Color of the tile */
3577
- 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())
3578
3602
  {
3579
3603
  /** @property {Number} - The tile to use, untextured if undefined */
3580
3604
  this.tile = tile;
@@ -3587,7 +3611,7 @@ class TileLayerData
3587
3611
  }
3588
3612
 
3589
3613
  /** Set this tile to clear, it will not be rendered */
3590
- 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; }
3591
3615
  }
3592
3616
 
3593
3617
  /**
@@ -3604,25 +3628,25 @@ class TileLayerData
3604
3628
  */
3605
3629
  class TileLayer extends EngineObject
3606
3630
  {
3607
- /** Create a tile layer object
3608
- * @param {Vector2} [position=Vector2()] - World space position
3609
- * @param {Vector2} [size=tileCollisionSize] - World space size
3610
- * @param {TileInfo} [tileInfo] - Tile info for layer
3611
- * @param {Vector2} [scale=Vector2(1,1)] - How much to scale this layer when rendered
3612
- * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
3631
+ /** Create a tile layer object
3632
+ * @param {Vector2} [position=(0,0)] - World space position
3633
+ * @param {Vector2} [size=tileCollisionSize] - World space size
3634
+ * @param {TileInfo} [tileInfo] - Tile info for layer
3635
+ * @param {Vector2} [scale=(1,1)] - How much to scale this layer when rendered
3636
+ * @param {Number} [renderOrder] - Objects are sorted by renderOrder
3613
3637
  */
3614
- constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderOrder=0)
3638
+ constructor(position, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderOrder=0)
3615
3639
  {
3616
- super(pos, size, tileInfo, 0, undefined, renderOrder);
3640
+ super(position, size, tileInfo, 0, undefined, renderOrder);
3617
3641
 
3618
- /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
3642
+ /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
3619
3643
  this.canvas = document.createElement('canvas');
3620
3644
  /** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
3621
3645
  this.context = this.canvas.getContext('2d');
3622
- /** @property {Vector2} - How much to scale this layer when rendered */
3646
+ /** @property {Vector2} - How much to scale this layer when rendered */
3623
3647
  this.scale = scale;
3624
- /** @property {Boolean} [isOverlay=0] - If true this layer will render to overlay canvas and appear above all objects */
3625
- this.isOverlay;
3648
+ /** @property {Boolean} - If true this layer will render to overlay canvas and appear above all objects */
3649
+ this.isOverlay = false;
3626
3650
 
3627
3651
  // init tile data
3628
3652
  this.data = [];
@@ -3631,10 +3655,10 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3631
3655
  }
3632
3656
 
3633
3657
  /** Set data at a given position in the array
3634
- * @param {Vector2} position - Local position in array
3635
- * @param {TileLayerData} data - Data to set
3636
- * @param {Boolean} [redraw=0] - Force the tile to redraw if true */
3637
- 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)
3638
3662
  {
3639
3663
  if (layerPos.arrayCheck(this.size))
3640
3664
  {
@@ -3655,7 +3679,7 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3655
3679
  // Render the tile layer, called automatically by the engine
3656
3680
  render()
3657
3681
  {
3658
- ASSERT(mainContext != this.context); // must call redrawEnd() after drawing tiles
3682
+ ASSERT(mainContext != this.context, 'must call redrawEnd() after drawing tiles');
3659
3683
 
3660
3684
  // flush and copy gl canvas because tile canvas does not use webgl
3661
3685
  glEnable && !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
@@ -3670,33 +3694,38 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3670
3694
  }
3671
3695
 
3672
3696
  /** Draw all the tile data to an offscreen canvas
3673
- * - This may be slow in some browsers
3674
- */
3697
+ * - This may be slow in some browsers but only needs to be done once */
3675
3698
  redraw()
3676
3699
  {
3677
- this.redrawStart(1);
3678
- this.drawAllTileData();
3700
+ this.redrawStart(true);
3701
+ for (let x = this.size.x; x--;)
3702
+ for (let y = this.size.y; y--;)
3703
+ this.drawTileData(vec2(x,y), false);
3679
3704
  this.redrawEnd();
3680
3705
  }
3681
3706
 
3682
3707
  /** Call to start the redraw process
3683
- * @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
3684
- redrawStart(clear = 0)
3708
+ * - This can be used to manually update small parts of the level
3709
+ * @param {Boolean} [clear] - Should it clear the canvas before drawing */
3710
+ redrawStart(clear=false)
3685
3711
  {
3686
3712
  // save current render settings
3713
+ /** @type {[HTMLCanvasElement, CanvasRenderingContext2D, Vector2, Vector2, number]} */
3687
3714
  this.savedRenderSettings = [mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale];
3688
3715
 
3689
- // hack: use normal rendering system to render the tiles
3716
+ // use webgl rendering system to render the tiles if enabled
3717
+ // this works by temporally taking control of the rendering system
3690
3718
  mainCanvas = this.canvas;
3691
3719
  mainContext = this.context;
3720
+ mainCanvasSize = this.size.multiply(this.tileInfo.size);
3692
3721
  cameraPos = this.size.scale(.5);
3693
3722
  cameraScale = this.tileInfo.size.x;
3694
3723
 
3695
3724
  if (clear)
3696
3725
  {
3697
3726
  // clear and set size
3698
- mainCanvas.width = this.size.x * this.tileInfo.size.x;
3699
- mainCanvas.height = this.size.y * this.tileInfo.size.y;
3727
+ mainCanvas.width = mainCanvasSize.x;
3728
+ mainCanvas.height = mainCanvasSize.y;
3700
3729
  }
3701
3730
 
3702
3731
  // begin a new render for the tile canvas
@@ -3706,40 +3735,41 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3706
3735
  /** Call to end the redraw process */
3707
3736
  redrawEnd()
3708
3737
  {
3709
- ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
3710
- glEnable && glCopyToContext(mainContext, 1);
3738
+ ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
3739
+ glEnable && glCopyToContext(mainContext, true);
3711
3740
  //debugSaveCanvas(this.canvas);
3712
3741
 
3713
3742
  // set stuff back to normal
3714
3743
  [mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale] = this.savedRenderSettings;
3715
3744
  }
3716
3745
 
3717
- /** Draw the tile at a given position
3718
- * @param {Vector2} layerPos */
3719
- drawTileData(layerPos)
3746
+ /** Draw the tile at a given position in the tile grid
3747
+ * This can be used to clear out tiles when they are destroyed
3748
+ * Tiles can also be redrawn if isinde a redrawStart/End block
3749
+ * @param {Vector2} layerPos
3750
+ * @param {Boolean} [clear] - should the old tile be cleared out
3751
+ */
3752
+ drawTileData(layerPos, clear=true)
3720
3753
  {
3721
- // first clear out where the tile was
3722
- const pos = layerPos.floor().add(this.pos).add(vec2(.5));
3723
- this.drawCanvas2D(pos, vec2(1), 0, 0, (context)=>context.clearRect(-.5, -.5, 1, 1));
3754
+ // clear out where the tile was, for full opaque tiles this can be skipped
3755
+ const s = this.tileInfo.size;
3756
+ if (clear)
3757
+ {
3758
+ const pos = layerPos.multiply(s);
3759
+ this.context.clearRect(pos.x, this.canvas.height-pos.y, s.x, -s.y);
3760
+ }
3724
3761
 
3725
3762
  // draw the tile if not undefined
3726
3763
  const d = this.getData(layerPos);
3727
3764
  if (d.tile != undefined)
3728
3765
  {
3729
- ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
3730
- const tileInfo = tile(d.tile, this.tileInfo.size, this.tileInfo.textureIndex);
3766
+ const pos = this.pos.add(layerPos).add(vec2(.5));
3767
+ ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
3768
+ const tileInfo = tile(d.tile, s, this.tileInfo.textureIndex);
3731
3769
  drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
3732
3770
  }
3733
3771
  }
3734
3772
 
3735
- /** Draw all the tiles in this layer */
3736
- drawAllTileData()
3737
- {
3738
- for (let x = this.size.x; x--;)
3739
- for (let y = this.size.y; y--;)
3740
- this.drawTileData(vec2(x,y));
3741
- }
3742
-
3743
3773
  /** Draw directly to the 2D canvas in world space (bipass webgl)
3744
3774
  * @param {Vector2} pos
3745
3775
  * @param {Vector2} size
@@ -3759,11 +3789,11 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3759
3789
  context.restore();
3760
3790
  }
3761
3791
 
3762
- /** Draw a tile directly onto the layer canvas
3792
+ /** Draw a tile directly onto the layer canvas in world space
3763
3793
  * @param {Vector2} pos
3764
- * @param {Vector2} [size=Vector2(1,1)]
3794
+ * @param {Vector2} [size=(1,1)]
3765
3795
  * @param {TileInfo} [tileInfo]
3766
- * @param {Color} [color=Color()]
3796
+ * @param {Color} [color=(1,1,1,1)]
3767
3797
  * @param {Number} [angle=0]
3768
3798
  * @param {Boolean} [mirror=0] */
3769
3799
  drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle, mirror)
@@ -3788,13 +3818,13 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3788
3818
  });
3789
3819
  }
3790
3820
 
3791
- /** Draw a rectangle directly onto the layer canvas
3821
+ /** Draw a rectangle directly onto the layer canvas in world space
3792
3822
  * @param {Vector2} pos
3793
- * @param {Vector2} [size=Vector2(1,1)]
3794
- * @param {Color} [color=Color()]
3823
+ * @param {Vector2} [size=(1,1)]
3824
+ * @param {Color} [color=(1,1,1,1)]
3795
3825
  * @param {Number} [angle=0] */
3796
3826
  drawRect(pos, size, color, angle)
3797
- { this.drawTile(pos, size, -1, 0, color, angle); }
3827
+ { this.drawTile(pos, size, undefined, color, angle); }
3798
3828
  }
3799
3829
  /**
3800
3830
  * LittleJS Particle System
@@ -3823,36 +3853,36 @@ class ParticleEmitter extends EngineObject
3823
3853
  {
3824
3854
  /** Create a particle system with the given settings
3825
3855
  * @param {Vector2} position - World space position of the emitter
3826
- * @param {Number} [angle=0] - Angle to emit the particles
3827
- * @param {Number|Vector2} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect)
3828
- * @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
3829
- * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
3830
- * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
3856
+ * @param {Number} [angle] - Angle to emit the particles
3857
+ * @param {Number|Vector2} [emitSize] - World space size of the emitter (float for circle diameter, vec2 for rect)
3858
+ * @param {Number} [emitTime] - How long to stay alive (0 is forever)
3859
+ * @param {Number} [emitRate] - How many particles per second to spawn, does not emit if 0
3860
+ * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
3831
3861
  * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
3832
- * @param {Color} [colorStartA=Color()] - Color at start of life 1, randomized between start colors
3833
- * @param {Color} [colorStartB=Color()] - Color at start of life 2, randomized between start colors
3834
- * @param {Color} [colorEndA=Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
3835
- * @param {Color} [colorEndB=Color(1,1,1,0)] - Color at end of life 2, randomized between end colors
3836
- * @param {Number} [particleTime=.5] - How long particles live
3837
- * @param {Number} [sizeStart=.1] - How big are particles at start
3838
- * @param {Number} [sizeEnd=1] - How big are particles at end
3839
- * @param {Number} [speed=.1] - How fast are particles when spawned
3840
- * @param {Number} [angleSpeed=.05] - How fast are particles rotating
3841
- * @param {Number} [damping=1] - How much to dampen particle speed
3842
- * @param {Number} [angleDamping=1] - How much to dampen particle angular speed
3843
- * @param {Number} [gravityScale=0] - How much does gravity effect particles
3844
- * @param {Number} [particleConeAngle=PI] - Cone for start particle angle
3845
- * @param {Number} [fadeRate=.1] - How quick to fade in particles at start/end in percent of life
3846
- * @param {Number} [randomness=.2] - Apply extra randomness percent
3847
- * @param {Boolean} [collideTiles=0] - Do particles collide against tiles
3848
- * @param {Boolean} [additive=0] - Should particles use addtive blend
3849
- * @param {Boolean} [randomColorLinear=1] - Should color be randomized linearly or across each component
3850
- * @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
3851
- * @param {Boolean} [localSpace=0] - Should it be in local space of emitter (world space is default)
3862
+ * @param {Color} [colorStartA=(1,1,1,1)] - Color at start of life 1, randomized between start colors
3863
+ * @param {Color} [colorStartB=(1,1,1,1)] - Color at start of life 2, randomized between start colors
3864
+ * @param {Color} [colorEndA=(1,1,1,0)] - Color at end of life 1, randomized between end colors
3865
+ * @param {Color} [colorEndB=(1,1,1,0)] - Color at end of life 2, randomized between end colors
3866
+ * @param {Number} [particleTime] - How long particles live
3867
+ * @param {Number} [sizeStart] - How big are particles at start
3868
+ * @param {Number} [sizeEnd] - How big are particles at end
3869
+ * @param {Number} [speed] - How fast are particles when spawned
3870
+ * @param {Number} [angleSpeed] - How fast are particles rotating
3871
+ * @param {Number} [damping] - How much to dampen particle speed
3872
+ * @param {Number} [angleDamping] - How much to dampen particle angular speed
3873
+ * @param {Number} [gravityScale] - How much gravity effect particles
3874
+ * @param {Number} [particleConeAngle] - Cone for start particle angle
3875
+ * @param {Number} [fadeRate] - How quick to fade particles at start/end in percent of life
3876
+ * @param {Number} [randomness] - Apply extra randomness percent
3877
+ * @param {Boolean} [collideTiles] - Do particles collide against tiles
3878
+ * @param {Boolean} [additive] - Should particles use addtive blend
3879
+ * @param {Boolean} [randomColorLinear] - Should color be randomized linearly or across each component
3880
+ * @param {Number} [renderOrder] - Render order for particles (additive is above other stuff by default)
3881
+ * @param {Boolean} [localSpace] - Should it be in local space of emitter (world space is default)
3852
3882
  */
3853
3883
  constructor
3854
3884
  (
3855
- pos,
3885
+ position,
3856
3886
  angle,
3857
3887
  emitSize = 0,
3858
3888
  emitTime = 0,
@@ -3874,14 +3904,14 @@ class ParticleEmitter extends EngineObject
3874
3904
  particleConeAngle = PI,
3875
3905
  fadeRate = .1,
3876
3906
  randomness = .2,
3877
- collideTiles,
3878
- additive,
3879
- randomColorLinear = 1,
3907
+ collideTiles = false,
3908
+ additive = false,
3909
+ randomColorLinear = true,
3880
3910
  renderOrder = additive ? 1e9 : 0,
3881
- localSpace
3911
+ localSpace = false
3882
3912
  )
3883
3913
  {
3884
- super(pos, vec2(), tileInfo, angle, undefined, renderOrder);
3914
+ super(position, vec2(), tileInfo, angle, undefined, renderOrder);
3885
3915
 
3886
3916
  // emitter settings
3887
3917
  /** @property {Number|Vector2} - World space size of the emitter (float for circle diameter, vec2 for rect) */
@@ -3930,14 +3960,17 @@ class ParticleEmitter extends EngineObject
3930
3960
  this.randomness = randomness;
3931
3961
  /** @property {Boolean} - Do particles collide against tiles */
3932
3962
  this.collideTiles = collideTiles;
3933
- /** @property {Number} - Should particles use addtive blend */
3963
+ /** @property {Boolean} - Should particles use addtive blend */
3934
3964
  this.additive = additive;
3935
3965
  /** @property {Boolean} - Should it be in local space of emitter */
3936
- this.localSpace = localSpace;
3937
- /** @property {Number} - If set the partile is drawn as a trail, stretched in the drection of velocity */
3966
+ this.localSpace = localSpace;
3967
+ /** @property {Number} - If non zero the partile is drawn as a trail, stretched in the drection of velocity */
3938
3968
  this.trailScale = 0;
3939
-
3940
- // internal variables
3969
+ /** @property {Function} - Callback when particle is destroyed */
3970
+ this.particleDestroyCallback = undefined;
3971
+ /** @property {Function} - Callback when particle is created */
3972
+ this.particleCreateCallback = undefined;
3973
+ /** @property {Number} - Track particle emit time */
3941
3974
  this.emitTimeBuffer = 0;
3942
3975
  }
3943
3976
 
@@ -3969,18 +4002,16 @@ class ParticleEmitter extends EngineObject
3969
4002
  emitParticle()
3970
4003
  {
3971
4004
  // spawn a particle
3972
- let pos = this.emitSize.x != undefined ? // check if vec2 was used for size
3973
- vec2(rand(-.5,.5), rand(-.5,.5))
3974
- .multiply(this.emitSize).rotate(this.angle) // box emitter
3975
- : randInCircle(this.emitSize/2); // circle emitter
4005
+ let pos = typeof this.emitSize === 'number' ? // check if number was used
4006
+ randInCircle(this.emitSize/2) // circle emitter
4007
+ : vec2(rand(-.5,.5), rand(-.5,.5)) // box emitter
4008
+ .multiply(this.emitSize).rotate(this.angle)
3976
4009
  let angle = rand(this.particleConeAngle, -this.particleConeAngle);
3977
4010
  if (!this.localSpace)
3978
4011
  {
3979
4012
  pos = this.pos.add(pos);
3980
4013
  angle += this.angle;
3981
4014
  }
3982
-
3983
- const particle = new Particle(pos, this.tileInfo, this.tileSize, angle);
3984
4015
 
3985
4016
  // randomness scales each paremeter by a percentage
3986
4017
  const randomness = this.randomness;
@@ -3996,30 +4027,21 @@ class ParticleEmitter extends EngineObject
3996
4027
  const colorStart = randColor(this.colorStartA, this.colorStartB, this.randomColorLinear);
3997
4028
  const colorEnd = randColor(this.colorEndA, this.colorEndB, this.randomColorLinear);
3998
4029
  const velocityAngle = this.localSpace ? coneAngle : this.angle + coneAngle;
3999
-
4000
- // build particle settings
4001
- particle.colorStart = colorStart;
4002
- particle.colorEndDelta = colorEnd.subtract(colorStart);
4003
- particle.velocity = vec2().setAngle(velocityAngle, speed);
4004
- particle.angleVelocity = angleSpeed;
4005
- particle.lifeTime = particleTime;
4006
- particle.sizeStart = sizeStart;
4007
- particle.sizeEndDelta = sizeEnd - sizeStart;
4008
- particle.fadeRate = this.fadeRate;
4009
- particle.damping = this.damping;
4010
- particle.angleDamping = this.angleDamping;
4011
- particle.elasticity = this.elasticity;
4012
- particle.friction = this.friction;
4013
- particle.gravityScale = this.gravityScale;
4014
- particle.collideTiles = this.collideTiles;
4015
- particle.additive = this.additive;
4016
- particle.renderOrder = this.renderOrder;
4017
- particle.trailScale = this.trailScale;
4018
- particle.mirror = randInt(2);
4019
- particle.localSpaceEmitter = this.localSpace && this;
4020
-
4021
- // setup callbacks for particles
4022
- particle.destroyCallback = this.particleDestroyCallback;
4030
+
4031
+ // build particle
4032
+ const particle = new Particle(pos, this.tileInfo, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, this.fadeRate, this.additive, this.trailScale, this.localSpace && this, this.particleDestroyCallback);
4033
+ particle.velocity = vec2().setAngle(velocityAngle, speed);
4034
+ particle.fadeRate = this.fadeRate;
4035
+ particle.damping = this.damping;
4036
+ particle.angleDamping = this.angleDamping;
4037
+ particle.elasticity = this.elasticity;
4038
+ particle.friction = this.friction;
4039
+ particle.gravityScale = this.gravityScale;
4040
+ particle.collideTiles = this.collideTiles;
4041
+ particle.renderOrder = this.renderOrder;
4042
+ particle.mirror = !!randInt(2);
4043
+
4044
+ // call particle create callaback
4023
4045
  this.particleCreateCallback && this.particleCreateCallback(particle);
4024
4046
 
4025
4047
  // return the newly created particle
@@ -4038,13 +4060,47 @@ class ParticleEmitter extends EngineObject
4038
4060
  class Particle extends EngineObject
4039
4061
  {
4040
4062
  /**
4041
- * Create a particle with the given settings
4042
- * @param {Vector2} position - World space position of the particle
4043
- * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
4044
- * @param {Number} [angle=0] - Angle to rotate the particle
4063
+ * Create a particle with the given shis.colorStart = undefined;ettings
4064
+ * @param {Vector2} position - World space position of the particle
4065
+ * @param {TileInfo} [tileInfo] - Tile info to render particles
4066
+ * @param {Number} [angle] - Angle to rotate the particle
4067
+ * @param {Color} [colorStart] - Color at start of life
4068
+ * @param {Color} [colorEnd] - Color at end of life
4069
+ * @param {Number} [lifeTime] - How long to live for
4070
+ * @param {Number} [sizeStart] - Angle to rotate the particle
4071
+ * @param {Number} [sizeEnd] - Angle to rotate the particle
4072
+ * @param {Number} [fadeRate] - Angle to rotate the particle
4073
+ * @param {Boolean} [additive] - Angle to rotate the particle
4074
+ * @param {Number} [trailScale] - If a trail, how long to make it
4075
+ * @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
4076
+ * @param {Function} [destroyCallback] - Called when particle dies
4045
4077
  */
4046
- constructor(pos, tileInfo, angle)
4047
- { super(pos, vec2(), tileInfo, angle); }
4078
+ constructor(position, tileInfo, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, fadeRate, additive, trailScale, localSpaceEmitter, destroyCallback
4079
+ )
4080
+ {
4081
+ super(position, vec2(), tileInfo, angle);
4082
+
4083
+ /** @property {Color} - Color at start of life */
4084
+ this.colorStart = colorStart;
4085
+ /** @property {Color} - Calculated change in color */
4086
+ this.colorEndDelta = colorEnd.subtract(colorStart);
4087
+ /** @property {Number} - How long to live for */
4088
+ this.lifeTime = lifeTime;
4089
+ /** @property {Number} - Size at start of life */
4090
+ this.sizeStart = sizeStart;
4091
+ /** @property {Number} - Calculated change in size */
4092
+ this.sizeEndDelta = sizeEnd - sizeStart;
4093
+ /** @property {Number} - How quick to fade in/out */
4094
+ this.fadeRate = fadeRate;
4095
+ /** @property {Boolean} - Is it additive */
4096
+ this.additive = additive;
4097
+ /** @property {Number} - If a trail, how long to make it */
4098
+ this.trailScale = trailScale;
4099
+ /** @property {ParticleEmitter} - Parent emitter if local space */
4100
+ this.localSpaceEmitter = localSpaceEmitter;
4101
+ /** @property {Function} - Called when particle dies */
4102
+ this.destroyCallback = destroyCallback;
4103
+ }
4048
4104
 
4049
4105
  /** Render the particle, automatically called each frame, sorted by renderOrder */
4050
4106
  render()
@@ -4062,7 +4118,7 @@ class Particle extends EngineObject
4062
4118
  (p < fadeRate ? p/fadeRate : p > 1-fadeRate ? (1-p)/fadeRate : 1)); // fade alpha
4063
4119
 
4064
4120
  // draw the particle
4065
- this.additive && setBlendMode(1);
4121
+ this.additive && setBlendMode(true);
4066
4122
 
4067
4123
  let pos = this.pos, angle = this.angle;
4068
4124
  if (this.localSpaceEmitter)
@@ -4152,7 +4208,7 @@ class Medal
4152
4208
  * @param {Number} id - The unique identifier of the medal
4153
4209
  * @param {String} name - Name of the medal
4154
4210
  * @param {String} [description] - Description of the medal
4155
- * @param {String} [icon='🏆'] - Icon for the medal
4211
+ * @param {String} [icon] - Icon for the medal
4156
4212
  * @param {String} [src] - Image location for the medal
4157
4213
  */
4158
4214
  constructor(id, name, description='', icon='🏆', src)
@@ -4175,14 +4231,14 @@ class Medal
4175
4231
  return;
4176
4232
 
4177
4233
  // save the medal
4178
- ASSERT(medalsSaveName); // save name must be set
4234
+ ASSERT(medalsSaveName, 'save name must be set');
4179
4235
  localStorage[this.storageKey()] = this.unlocked = 1;
4180
4236
  medalsDisplayQueue.push(this);
4181
4237
  newgrounds && newgrounds.unlockMedal(this.id);
4182
4238
  }
4183
4239
 
4184
4240
  /** Render a medal
4185
- * @param {Number} [hidePercent=0] - How much to slide the medal off screen
4241
+ * @param {Number} [hidePercent] - How much to slide the medal off screen
4186
4242
  */
4187
4243
  render(hidePercent=0)
4188
4244
  {
@@ -4194,25 +4250,25 @@ class Medal
4194
4250
  // draw containing rect and clip to that region
4195
4251
  context.save();
4196
4252
  context.beginPath();
4197
- context.fillStyle = new Color(.9,.9,.9);
4198
- context.strokeStyle = new Color(0,0,0);
4253
+ context.fillStyle = rgb(.9,.9,.9).toString();
4254
+ context.strokeStyle = rgb(0,0,0).toString();
4199
4255
  context.lineWidth = 3;
4200
- context.fill(context.rect(x, y, width, medalDisplaySize.y));
4256
+ context.rect(x, y, width, medalDisplaySize.y);
4257
+ context.fill();
4201
4258
  context.stroke();
4202
4259
  context.clip();
4203
4260
 
4204
4261
  // draw the icon and text
4205
4262
  this.renderIcon(vec2(x+15+medalDisplayIconSize/2, y+medalDisplaySize.y/2));
4206
4263
  const pos = vec2(x+medalDisplayIconSize+30, y+28);
4207
- drawTextScreen(this.name, pos, 38, new Color(0,0,0), 0, 0, 'left');
4264
+ drawTextScreen(this.name, pos, 38, new Color(0,0,0), 0, undefined, 'left');
4208
4265
  pos.y += 32;
4209
- drawTextScreen(this.description, pos, 24, new Color(0,0,0), 0, 0, 'left');
4266
+ drawTextScreen(this.description, pos, 24, new Color(0,0,0), 0, undefined, 'left');
4210
4267
  context.restore();
4211
4268
  }
4212
4269
 
4213
4270
  /** Render the icon for a medal
4214
- * @param {Number} x - Screen space X position
4215
- * @param {Number} y - Screen space Y position
4271
+ * @param {Vector2} pos - Screen space position
4216
4272
  * @param {Number} [size=medalDisplayIconSize] - Screen space size
4217
4273
  */
4218
4274
  renderIcon(pos, size=medalDisplayIconSize)
@@ -4240,7 +4296,10 @@ function medalsRender()
4240
4296
  if (!medalsDisplayTimeLast)
4241
4297
  medalsDisplayTimeLast = timeReal;
4242
4298
  else if (time > medalDisplayTime)
4243
- medalsDisplayQueue.shift(medalsDisplayTimeLast = 0);
4299
+ {
4300
+ medalsDisplayTimeLast = 0;
4301
+ medalsDisplayQueue.shift();
4302
+ }
4244
4303
  else
4245
4304
  {
4246
4305
  // slide on/off medals
@@ -4280,8 +4339,8 @@ class Newgrounds
4280
4339
  * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher */
4281
4340
  constructor(app_id, cipher, cryptoJS)
4282
4341
  {
4283
- ASSERT(!newgrounds && app_id); // can only be one newgrounds object
4284
- ASSERT(!cipher || cryptoJS); // must provide cryptojs if there is a cipher
4342
+ ASSERT(!newgrounds && app_id>0, 'there can only be one newgrounds object');
4343
+ ASSERT(!cipher || cryptoJS, 'must provide cryptojs if there is a cipher');
4285
4344
 
4286
4345
  this.app_id = app_id;
4287
4346
  this.cipher = cipher;
@@ -4324,39 +4383,39 @@ class Newgrounds
4324
4383
  debugMedals && console.log(this.scoreboards);
4325
4384
 
4326
4385
  const keepAliveMS = 5 * 60 * 1e3;
4327
- setInterval(()=>this.call('Gateway.ping', 0, 1), keepAliveMS);
4386
+ setInterval(()=>this.call('Gateway.ping', 0, true), keepAliveMS);
4328
4387
  }
4329
4388
 
4330
4389
  /** Send message to unlock a medal by id
4331
4390
  * @param {Number} id - The medal id */
4332
- unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, 1); }
4391
+ unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, true); }
4333
4392
 
4334
4393
  /** Send message to post score
4335
4394
  * @param {Number} id - The scoreboard id
4336
4395
  * @param {Number} value - The score value */
4337
- postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); }
4396
+ postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, true); }
4338
4397
 
4339
4398
  /** Get scores from a scoreboard
4340
- * @param {Number} id - The scoreboard id
4341
- * @param {String} [user=0] - A user's id or name
4342
- * @param {Number} [social=0] - If true, only social scores will be loaded
4343
- * @param {Number} [skip=0] - Number of scores to skip before start
4344
- * @param {Number} [limit=10] - Number of scores to include in the list
4345
- * @return {Object} - The response JSON object
4399
+ * @param {Number} id - The scoreboard id
4400
+ * @param {String} [user] - A user's id or name
4401
+ * @param {Number} [social] - If true, only social scores will be loaded
4402
+ * @param {Number} [skip] - Number of scores to skip before start
4403
+ * @param {Number} [limit] - Number of scores to include in the list
4404
+ * @return {Object} - The response JSON object
4346
4405
  */
4347
- getScores(id, user=0, social=0, skip=0, limit=10)
4406
+ getScores(id, user, social=0, skip=0, limit=10)
4348
4407
  { return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
4349
4408
 
4350
4409
  /** Send message to log a view */
4351
- logView() { return this.call('App.logView', {'host':this.host}, 1); }
4410
+ logView() { return this.call('App.logView', {'host':this.host}, true); }
4352
4411
 
4353
4412
  /** Send a message to call a component of the Newgrounds API
4354
- * @param {String} component - Name of the component
4355
- * @param {Object} [parameters=0] - Parameters to use for call
4356
- * @param {Boolean} [async=0] - If true, don't wait for response before continuing (avoid stall)
4357
- * @return {Object} - The response JSON object
4413
+ * @param {String} component - Name of the component
4414
+ * @param {Object} [parameters] - Parameters to use for call
4415
+ * @param {Boolean} [async] - If true, don't wait for response before continuing
4416
+ * @return {Object} - The response JSON object
4358
4417
  */
4359
- call(component, parameters=0, async=0)
4418
+ call(component, parameters, async=false)
4360
4419
  {
4361
4420
  const call = {'component':component, 'parameters':parameters};
4362
4421
  if (this.cipher)
@@ -4391,7 +4450,7 @@ class Newgrounds
4391
4450
  return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
4392
4451
  }
4393
4452
  }
4394
- /**
4453
+ /**
4395
4454
  * LittleJS WebGL Interface
4396
4455
  * - All webgl used by the engine is wrapped up here
4397
4456
  * - For normal stuff you won't need to see or call anything in this file
@@ -4410,13 +4469,13 @@ class Newgrounds
4410
4469
  * @memberof WebGL */
4411
4470
  let glCanvas;
4412
4471
 
4413
- /** 2d context for glCanvas
4414
- * @type {WebGLRenderingContext}
4472
+ /** 2d context for glCanvas
4473
+ * @type {WebGL2RenderingContext}
4415
4474
  * @memberof WebGL */
4416
4475
  let glContext;
4417
4476
 
4418
4477
  // WebGL internal variables not exposed to documentation
4419
- let glActiveTexture, glShader, glArrayBuffer, glVertexData, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
4478
+ let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glInstanceCount, glAdditive, glBatchAdditive;
4420
4479
 
4421
4480
  ///////////////////////////////////////////////////////////////////////////////
4422
4481
 
@@ -4432,73 +4491,89 @@ function glInit()
4432
4491
 
4433
4492
  // setup vertex and fragment shaders
4434
4493
  glShader = glCreateProgram(
4435
- '#version 300 es\n' + // specify GLSL ES version
4436
- 'precision highp float;'+ // use highp for better accuracy
4437
- 'uniform mat4 m;'+ // transform matrix
4438
- 'in vec4 p,c,a;'+ // position, uv, color, additiveColor
4439
- 'out vec4 v,d,e;'+ // return uv, color, additiveColor
4440
- 'void main(){'+ // shader entry point
4441
- 'gl_Position=m*vec4(p.xy,1,1);'+ // transform position
4442
- 'v=p;d=c;e=a;'+ // pass stuff to fragment shader
4443
- '}' // end of shader
4494
+ '#version 300 es\n' + // specify GLSL ES version
4495
+ 'precision highp float;'+ // use highp for better accuracy
4496
+ 'uniform mat4 m;'+ // transform matrix
4497
+ 'in vec2 g;'+ // geometry
4498
+ 'in vec4 p,u,c,a;'+ // position/size, uvs, color, additiveColor
4499
+ 'in float r;'+ // rotation
4500
+ 'out vec2 v;'+ // return uv, color, additiveColor
4501
+ 'out vec4 d,e;'+ // return uv, color, additiveColor
4502
+ 'void main(){'+ // shader entry point
4503
+ 'vec2 s=(g-.5)*p.zw;'+ // get size offset
4504
+ 'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
4505
+ 'v=mix(u.xw,u.zy,g);'+ // pass uv to fragment shader
4506
+ 'd=c;e=a;'+ // pass colors to fragment shader
4507
+ '}' // end of shader
4444
4508
  ,
4445
- '#version 300 es\n' + // specify GLSL ES version
4446
- 'precision highp float;'+ // use highp for better accuracy
4447
- 'in vec4 v,d,e;'+ // position, uv, color, additiveColor
4448
- 'uniform sampler2D s;'+ // texture
4449
- 'out vec4 c;'+ // out color
4450
- 'void main(){'+ // shader entry point
4451
- 'c=texture(s,v.zw)*d+e;'+ // modulate texture by color plus additive
4452
- '}' // end of shader
4509
+ '#version 300 es\n' + // specify GLSL ES version
4510
+ 'precision highp float;'+ // use highp for better accuracy
4511
+ 'in vec2 v;'+ // uv
4512
+ 'in vec4 d,e;'+ // color, additiveColor
4513
+ 'uniform sampler2D s;'+ // texture
4514
+ 'out vec4 c;'+ // out color
4515
+ 'void main(){'+ // shader entry point
4516
+ 'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
4517
+ '}' // end of shader
4453
4518
  );
4454
4519
 
4455
4520
  // init buffers
4456
- glVertexData = new ArrayBuffer(gl_VERTEX_BUFFER_SIZE);
4457
- glPositionData = new Float32Array(glVertexData);
4458
- glColorData = new Uint32Array(glVertexData);
4521
+ const glInstanceData = new ArrayBuffer(gl_INSTANCE_BUFFER_SIZE);
4522
+ glPositionData = new Float32Array(glInstanceData);
4523
+ glColorData = new Uint32Array(glInstanceData);
4459
4524
  glArrayBuffer = glContext.createBuffer();
4460
- glBatchCount = 0;
4525
+ glGeometryBuffer = glContext.createBuffer();
4526
+
4527
+ // create the geometry buffer, triangle strip square
4528
+ const geometry = new Float32Array([glInstanceCount=0,0,1,0,0,1,1,1]);
4529
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
4530
+ glContext.bufferData(gl_ARRAY_BUFFER, geometry, gl_STATIC_DRAW);
4461
4531
  }
4462
4532
 
4463
4533
  // Setup render each frame, called automatically by engine
4464
4534
  function glPreRender()
4465
4535
  {
4466
4536
  // clear and set to same size as main canvas
4467
- glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
4537
+ glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
4468
4538
  glContext.clear(gl_COLOR_BUFFER_BIT);
4469
4539
 
4470
4540
  // set up the shader
4471
4541
  glContext.useProgram(glShader);
4472
4542
  glContext.activeTexture(gl_TEXTURE0);
4473
4543
  glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
4474
- glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
4475
- glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
4476
- glAdditive = 0;
4477
-
4544
+
4478
4545
  // set vertex attributes
4479
- let offset = 0;
4480
- const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
4546
+ let offset = glAdditive = glBatchAdditive = 0;
4547
+ let initVertexAttribArray = (name, type, typeSize, size)=>
4481
4548
  {
4482
4549
  const location = glContext.getAttribLocation(glShader, name);
4550
+ const stride = typeSize && gl_INSTANCE_BYTE_STRIDE; // only if not geometry
4551
+ const divisor = typeSize && 1; // only if not geometry
4552
+ const normalize = typeSize==1; // only if color
4483
4553
  glContext.enableVertexAttribArray(location);
4484
- glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
4554
+ glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
4555
+ glContext.vertexAttribDivisor(location, divisor);
4485
4556
  offset += size*typeSize;
4486
4557
  }
4487
- initVertexAttribArray('p', gl_FLOAT, 4, 4); // position & texture
4488
- initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
4489
- initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
4558
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
4559
+ initVertexAttribArray('g', gl_FLOAT, 0, 2); // geometry
4560
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
4561
+ glContext.bufferData(gl_ARRAY_BUFFER, gl_INSTANCE_BUFFER_SIZE, gl_DYNAMIC_DRAW);
4562
+ initVertexAttribArray('p', gl_FLOAT, 4, 4); // position & size
4563
+ initVertexAttribArray('u', gl_FLOAT, 4, 4); // texture coords
4564
+ initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4); // color
4565
+ initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4); // additiveColor
4566
+ initVertexAttribArray('r', gl_FLOAT, 4, 1); // rotation
4490
4567
 
4491
4568
  // build the transform matrix
4492
- const sx = 2 * cameraScale / mainCanvas.width;
4493
- const sy = 2 * cameraScale / mainCanvas.height;
4494
- const cx = -1 - sx*cameraPos.x;
4495
- const cy = -1 - sy*cameraPos.y;
4496
- glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
4569
+ const s = vec2(2*cameraScale).divide(mainCanvasSize);
4570
+ const p = vec2(-1).subtract(cameraPos.multiply(s));
4571
+ glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), false,
4497
4572
  new Float32Array([
4498
- sx, 0, 0, 0,
4499
- 0, sy, 0, 0,
4500
- 1, 1, -1, 1,
4501
- cx, cy, 0, 0
4573
+ s.x, 0, 0, 0,
4574
+ 0, s.y, 0, 0,
4575
+ 1, 1, 1, 1,
4576
+ p.x, p.y, 0, 0
4502
4577
  ])
4503
4578
  );
4504
4579
  }
@@ -4519,7 +4594,7 @@ function glSetTexture(texture)
4519
4594
 
4520
4595
  /** Compile WebGL shader of the given type, will throw errors if in debug mode
4521
4596
  * @param {String} source
4522
- * @param type
4597
+ * @param {Number} type
4523
4598
  * @return {WebGLShader}
4524
4599
  * @memberof WebGL */
4525
4600
  function glCompileShader(source, type)
@@ -4536,8 +4611,8 @@ function glCompileShader(source, type)
4536
4611
  }
4537
4612
 
4538
4613
  /** Create WebGL program with given shaders
4539
- * @param {WebGLShader} vsSource
4540
- * @param {WebGLShader} fsSource
4614
+ * @param {String} vsSource
4615
+ * @param {String} fsSource
4541
4616
  * @return {WebGLProgram}
4542
4617
  * @memberof WebGL */
4543
4618
  function glCreateProgram(vsSource, fsSource)
@@ -4555,7 +4630,7 @@ function glCreateProgram(vsSource, fsSource)
4555
4630
  }
4556
4631
 
4557
4632
  /** Create WebGL texture from an image and init the texture settings
4558
- * @param {Image} image
4633
+ * @param {HTMLImageElement} image
4559
4634
  * @return {WebGLTexture}
4560
4635
  * @memberof WebGL */
4561
4636
  function glCreateTexture(image)
@@ -4565,7 +4640,7 @@ function glCreateTexture(image)
4565
4640
  glContext.bindTexture(gl_TEXTURE_2D, texture);
4566
4641
  if (image)
4567
4642
  glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
4568
-
4643
+
4569
4644
  // use point filtering for pixelated rendering
4570
4645
  const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
4571
4646
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
@@ -4580,29 +4655,31 @@ function glCreateTexture(image)
4580
4655
  * @memberof WebGL */
4581
4656
  function glFlush()
4582
4657
  {
4583
- if (!glBatchCount) return;
4658
+ if (!glInstanceCount) return;
4584
4659
 
4585
4660
  const destBlend = glBatchAdditive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
4586
4661
  glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
4587
4662
  glContext.enable(gl_BLEND);
4588
4663
 
4589
4664
  // draw all the sprites in the batch and reset the buffer
4590
- glContext.bufferSubData(gl_ARRAY_BUFFER, 0, glVertexData);
4591
- glContext.drawArrays(gl_TRIANGLE_STRIP, 0, glBatchCount);
4592
- glBatchCount = 0;
4665
+ glContext.bufferSubData(gl_ARRAY_BUFFER, 0, glPositionData);
4666
+ glContext.drawArraysInstanced(gl_TRIANGLE_STRIP, 0, 4, glInstanceCount);
4667
+ if (showWatermark)
4668
+ drawCount += glInstanceCount;
4669
+ glInstanceCount = 0;
4593
4670
  glBatchAdditive = glAdditive;
4594
4671
  }
4595
4672
 
4596
4673
  /** Draw any sprites still in the buffer, copy to main canvas and clear
4597
4674
  * @param {CanvasRenderingContext2D} context
4598
- * @param {Boolean} [forceDraw=0]
4675
+ * @param {Boolean} [forceDraw]
4599
4676
  * @memberof WebGL */
4600
- function glCopyToContext(context, forceDraw)
4677
+ function glCopyToContext(context, forceDraw=false)
4601
4678
  {
4602
- if (!glBatchCount && !forceDraw) return;
4603
-
4679
+ if (!glInstanceCount && !forceDraw) return;
4680
+
4604
4681
  glFlush();
4605
-
4682
+
4606
4683
  // do not draw in overlay mode because the canvas is visible
4607
4684
  if (!glOverlay || forceDraw)
4608
4685
  context.drawImage(glCanvas, 0, 0);
@@ -4623,61 +4700,25 @@ function glCopyToContext(context, forceDraw)
4623
4700
  * @memberof WebGL */
4624
4701
  function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdditive=0)
4625
4702
  {
4626
- // flush if there is not enough room or if different blend mode
4627
- const vertCount = 6;
4628
- if (glBatchCount >= gl_MAX_BATCH-vertCount || glBatchAdditive != glAdditive)
4629
- glFlush();
4630
-
4631
- // prepare to create the verts from size and angle
4632
- const c = Math.cos(angle)/2, s = Math.sin(angle)/2;
4633
- const cx = c*sizeX, cy = c*sizeY, sx = s*sizeX, sy = s*sizeY;
4634
- const positionData =
4635
- [
4636
- x-cx+sy, y+cy+sx, uv0X, uv0Y,
4637
- x-cx-sy, y-cy+sx, uv0X, uv1Y,
4638
- x+cx+sy, y+cy-sx, uv1X, uv0Y,
4639
- x+cx-sy, y-cy-sx, uv1X, uv1Y,
4640
- ];
4641
-
4642
- // setup 2 triangle strip quad
4643
- for(let i = vertCount, offset = glBatchCount * gl_INDICIES_PER_VERT; i--;)
4644
- {
4645
- const j = clamp(i-1, 0, 3)*4; // degenerate tri at ends
4646
- glPositionData[offset++] = positionData[j+0];
4647
- glPositionData[offset++] = positionData[j+1];
4648
- glPositionData[offset++] = positionData[j+2];
4649
- glPositionData[offset++] = positionData[j+3];
4650
- glColorData[offset++] = rgba;
4651
- glColorData[offset++] = rgbaAdditive;
4652
- }
4653
- glBatchCount += vertCount;
4654
- }
4703
+ ASSERT(typeof rgba == 'number' && typeof rgbaAdditive == 'number', 'invalid color');
4655
4704
 
4656
- /** Add a convex polygon to the gl draw list
4657
- * @param {Array} points - Array of Vector2 points
4658
- * @param {Number} rgba - Color of the polygon
4659
- * @memberof WebGL */
4660
- function glDrawPoints(points, rgba)
4661
- {
4662
4705
  // flush if there is not enough room or if different blend mode
4663
- const vertCount = points.length + 2;
4664
- if (glBatchCount >= gl_MAX_BATCH-vertCount || glBatchAdditive != glAdditive)
4706
+ if (glInstanceCount >= gl_MAX_INSTANCES-1 || glBatchAdditive != glAdditive)
4665
4707
  glFlush();
4666
-
4667
- // setup triangle strip from list of points
4668
- for(let i = vertCount, offset = glBatchCount * gl_INDICIES_PER_VERT; i--;)
4669
- {
4670
- const j = clamp(i-1, 0, vertCount-3); // degenerate tri at ends
4671
- const h = j>>1;
4672
- const point = points[j%2? h : vertCount-3-h];
4673
- glPositionData[offset++] = point.x;
4674
- glPositionData[offset++] = point.y;
4675
- glPositionData[offset++] = 0; // uvx
4676
- glPositionData[offset++] = 0; // uvy
4677
- glColorData[offset++] = 0; // nothing to tint
4678
- glColorData[offset++] = rgba; // apply rgba via additive
4679
- }
4680
- glBatchCount += vertCount;
4708
+
4709
+ let offset = glInstanceCount * gl_INDICIES_PER_INSTANCE;
4710
+ glPositionData[offset++] = x;
4711
+ glPositionData[offset++] = y;
4712
+ glPositionData[offset++] = sizeX;
4713
+ glPositionData[offset++] = sizeY;
4714
+ glPositionData[offset++] = uv0X;
4715
+ glPositionData[offset++] = uv0Y;
4716
+ glPositionData[offset++] = uv1X;
4717
+ glPositionData[offset++] = uv1Y;
4718
+ glColorData[offset++] = rgba;
4719
+ glColorData[offset++] = rgbaAdditive;
4720
+ glPositionData[offset++] = angle;
4721
+ glInstanceCount++;
4681
4722
  }
4682
4723
 
4683
4724
  ///////////////////////////////////////////////////////////////////////////////
@@ -4689,9 +4730,9 @@ let glPostShader, glPostArrayBuffer, glPostTexture, glPostIncludeOverlay;
4689
4730
  * @param {String} shaderCode
4690
4731
  * @param {Boolean} includeOverlay
4691
4732
  * @memberof WebGL */
4692
- function glInitPostProcess(shaderCode, includeOverlay)
4733
+ function glInitPostProcess(shaderCode, includeOverlay=false)
4693
4734
  {
4694
- ASSERT(!glPostShader); // can only have 1 post effects shader
4735
+ ASSERT(!glPostShader, 'can only have 1 post effects shader');
4695
4736
 
4696
4737
  if (!shaderCode) // default shader pass through
4697
4738
  shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
@@ -4702,14 +4743,14 @@ function glInitPostProcess(shaderCode, includeOverlay)
4702
4743
  'precision highp float;'+ // use highp for better accuracy
4703
4744
  'in vec2 p;'+ // position
4704
4745
  'void main(){'+ // shader entry point
4705
- 'gl_Position=vec4(p,1,1);'+ // set position
4746
+ 'gl_Position=vec4(p+p-1.,1,1);'+ // set position
4706
4747
  '}' // end of shader
4707
4748
  ,
4708
4749
  '#version 300 es\n' + // specify GLSL ES version
4709
4750
  'precision highp float;'+ // use highp for better accuracy
4710
4751
  'uniform sampler2D iChannel0;'+ // input texture
4711
4752
  'uniform vec3 iResolution;'+ // size of output texture
4712
- 'uniform float iTime;'+ // time passed
4753
+ 'uniform float iTime;'+ // time
4713
4754
  'out vec4 c;'+ // out color
4714
4755
  '\n' + shaderCode + '\n'+ // insert custom shader code
4715
4756
  'void main(){'+ // shader entry point
@@ -4720,11 +4761,13 @@ function glInitPostProcess(shaderCode, includeOverlay)
4720
4761
 
4721
4762
  // create buffer and texture
4722
4763
  glPostArrayBuffer = glContext.createBuffer();
4723
- glPostTexture = glCreateTexture();
4764
+ glPostTexture = glCreateTexture(undefined);
4724
4765
  glPostIncludeOverlay = includeOverlay;
4725
4766
 
4726
4767
  // hide the original 2d canvas
4727
4768
  mainCanvas.style.visibility = 'hidden';
4769
+ if (glPostIncludeOverlay)
4770
+ overlayCanvas.style.visibility = 'hidden';
4728
4771
  }
4729
4772
 
4730
4773
  // Render the post processing shader, called automatically by the engine
@@ -4745,21 +4788,14 @@ function glRenderPostProcess()
4745
4788
  glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
4746
4789
  }
4747
4790
 
4748
- if (glPostIncludeOverlay)
4749
- {
4750
- // copy overlay canvas so it will be included in post processing
4751
- mainContext.drawImage(overlayCanvas, 0, 0);
4752
-
4753
- // clear overlay canvas
4754
- overlayCanvas.width = mainCanvas.width;
4755
- }
4791
+ // copy overlay canvas so it will be included in post processing
4792
+ glPostIncludeOverlay && mainContext.drawImage(overlayCanvas, 0, 0);
4756
4793
 
4757
4794
  // setup shader program to draw one triangle
4758
4795
  glContext.useProgram(glPostShader);
4796
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
4797
+ glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, 1);
4759
4798
  glContext.disable(gl_BLEND);
4760
- glContext.bindBuffer(gl_ARRAY_BUFFER, glPostArrayBuffer);
4761
- glContext.bufferData(gl_ARRAY_BUFFER, new Float32Array([-3,1,1,-3,1,1]), gl_STATIC_DRAW);
4762
- glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, true);
4763
4799
 
4764
4800
  // set textures, pass in the 2d canvas and gl canvas in separate texture channels
4765
4801
  glContext.activeTexture(gl_TEXTURE0);
@@ -4770,19 +4806,19 @@ function glRenderPostProcess()
4770
4806
  const vertexByteStride = 8;
4771
4807
  const pLocation = glContext.getAttribLocation(glPostShader, 'p');
4772
4808
  glContext.enableVertexAttribArray(pLocation);
4773
- glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, 0, vertexByteStride, 0);
4809
+ glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, false, vertexByteStride, 0);
4774
4810
 
4775
4811
  // set uniforms and draw
4776
4812
  const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
4777
4813
  glContext.uniform1i(uniformLocation('iChannel0'), 0);
4778
4814
  glContext.uniform1f(uniformLocation('iTime'), time);
4779
4815
  glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
4780
- glContext.drawArrays(gl_TRIANGLE_STRIP, 0, 3);
4816
+ glContext.drawArrays(gl_TRIANGLE_STRIP, 0, 4);
4781
4817
  }
4782
4818
 
4783
4819
  ///////////////////////////////////////////////////////////////////////////////
4784
4820
  // store gl constants as integers so their name doesn't use space in minifed
4785
- const
4821
+ const
4786
4822
  gl_ONE = 1,
4787
4823
  gl_TRIANGLE_STRIP = 5,
4788
4824
  gl_SRC_ALPHA = 770,
@@ -4804,17 +4840,17 @@ gl_TEXTURE0 = 33984,
4804
4840
  gl_ARRAY_BUFFER = 34962,
4805
4841
  gl_STATIC_DRAW = 35044,
4806
4842
  gl_DYNAMIC_DRAW = 35048,
4807
- gl_FRAGMENT_SHADER = 35632,
4843
+ gl_FRAGMENT_SHADER = 35632,
4808
4844
  gl_VERTEX_SHADER = 35633,
4809
4845
  gl_COMPILE_STATUS = 35713,
4810
4846
  gl_LINK_STATUS = 35714,
4811
4847
  gl_UNPACK_FLIP_Y_WEBGL = 37440,
4812
4848
 
4813
4849
  // constants for batch rendering
4814
- gl_INDICIES_PER_VERT = 6,
4815
- gl_MAX_BATCH = 1e5,
4816
- gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
4817
- gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTEX_BYTE_STRIDE;
4850
+ gl_INDICIES_PER_INSTANCE = 11,
4851
+ gl_MAX_INSTANCES = 1e4,
4852
+ gl_INSTANCE_BYTE_STRIDE = gl_INDICIES_PER_INSTANCE * 4, // 11 * 4
4853
+ gl_INSTANCE_BUFFER_SIZE = gl_MAX_INSTANCES * gl_INSTANCE_BYTE_STRIDE;
4818
4854
  /**
4819
4855
  * LittleJS - The Tiny JavaScript Game Engine That Can!
4820
4856
  * MIT License - Copyright 2021 Frank Force
@@ -4847,7 +4883,7 @@ const engineName = 'LittleJS';
4847
4883
  * @type {String}
4848
4884
  * @default
4849
4885
  * @memberof Engine */
4850
- const engineVersion = '1.8.9';
4886
+ const engineVersion = '1.9.1';
4851
4887
 
4852
4888
  /** Frames per second to update objects
4853
4889
  * @type {Number}
@@ -4888,14 +4924,14 @@ let timeReal = 0;
4888
4924
 
4889
4925
  /** Is the game paused? Causes time and objects to not be updated
4890
4926
  * @type {Boolean}
4891
- * @default 0
4927
+ * @default false
4892
4928
  * @memberof Engine */
4893
- let paused = 0;
4929
+ let paused = false;
4894
4930
 
4895
4931
  /** Set if game is paused
4896
- * @param {Boolean} paused
4932
+ * @param {Boolean} isPaused
4897
4933
  * @memberof Engine */
4898
- function setPaused(_paused) { paused = _paused; }
4934
+ function setPaused(isPaused) { paused = isPaused; }
4899
4935
 
4900
4936
  // Frame time tracking
4901
4937
  let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
@@ -4912,7 +4948,7 @@ let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
4912
4948
  * @memberof Engine */
4913
4949
  function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=['tiles.png'])
4914
4950
  {
4915
- ASSERT(Array.isArray(imageSources)); // pass in images as array
4951
+ ASSERT(Array.isArray(imageSources), 'pass in images as array');
4916
4952
 
4917
4953
  // internal update loop for engine
4918
4954
  function engineUpdate(frameTimeMS=0)
@@ -4922,12 +4958,12 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4922
4958
  frameTimeLastMS = frameTimeMS;
4923
4959
  if (debug || showWatermark)
4924
4960
  averageFPS = lerp(.05, averageFPS, 1e3/(frameTimeDeltaMS||1));
4925
- const debugSpeedUp = debug && keyIsDown(107); // +
4926
- const debugSpeedDown = debug && keyIsDown(109); // -
4961
+ const debugSpeedUp = debug && keyIsDown('Equal'); // +
4962
+ const debugSpeedDown = debug && keyIsDown('Minus'); // -
4927
4963
  if (debug) // +/- to speed/slow time
4928
4964
  frameTimeDeltaMS *= debugSpeedUp ? 5 : debugSpeedDown ? .2 : 1;
4929
4965
  timeReal += frameTimeDeltaMS / 1e3;
4930
- frameTimeBufferMS += !paused * frameTimeDeltaMS;
4966
+ frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
4931
4967
  if (!debugSpeedUp)
4932
4968
  frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp incase of slow framerate
4933
4969
 
@@ -5037,7 +5073,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5037
5073
  'user-select:none;' + // prevent mobile hold to select
5038
5074
  '-webkit-user-select:none;' + // compatibility for ios
5039
5075
  '-webkit-touch-callout:none'; // compatibility for ios
5040
- document.body.style = styleBody;
5076
+ document.body.style.cssText = styleBody;
5041
5077
  document.body.appendChild(mainCanvas = document.createElement('canvas'));
5042
5078
  mainContext = mainCanvas.getContext('2d');
5043
5079
 
@@ -5050,10 +5086,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5050
5086
  overlayContext = overlayCanvas.getContext('2d');
5051
5087
 
5052
5088
  // set canvas style
5053
- const styleCanvas =
5054
- 'position:absolute;' + // position
5089
+ const styleCanvas = 'position:absolute;' + // position
5055
5090
  'top:50%;left:50%;transform:translate(-50%,-50%)'; // center
5056
- (glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
5091
+ (glCanvas||mainCanvas).style.cssText = mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
5057
5092
 
5058
5093
  // create promises for loading images
5059
5094
  const promises = imageSources.map((src, textureIndex)=>
@@ -5073,13 +5108,13 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5073
5108
  showSplashScreen && promises.push(new Promise(resolve =>
5074
5109
  {
5075
5110
  let t = 0;
5076
- console.log(`LittleJS Engine v${engineVersion}`);
5111
+ console.log(`${engineName} Engine v${engineVersion}`);
5077
5112
  updateSplash();
5078
5113
  function updateSplash()
5079
5114
  {
5080
5115
  clearInput();
5081
5116
  drawEngineSplashScreen(t+=.01);
5082
- t>1 ? resolve() : setTimeout(updateSplash,16);
5117
+ t>1 ? resolve() : setTimeout(updateSplash, 16);
5083
5118
  }
5084
5119
  }));
5085
5120
 
@@ -5140,7 +5175,7 @@ function engineObjectsDestroy()
5140
5175
 
5141
5176
  /** Triggers a callback for each object within a given area
5142
5177
  * @param {Vector2} [pos] - Center of test area
5143
- * @param {Number} [size] - Radius of circle if float, rectangle size if Vector2
5178
+ * @param {Number|Vector2} [size] - Radius of circle if float, rectangle size if Vector2
5144
5179
  * @param {Function} [callbackFunction] - Calls this function on every object that passes the test
5145
5180
  * @param {Array} [objects=engineObjects] - List of objects to check
5146
5181
  * @memberof Engine */
@@ -5151,7 +5186,7 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
5151
5186
  for (const o of objects)
5152
5187
  callbackFunction(o);
5153
5188
  }
5154
- else if (size.x != undefined) // bounding box test
5189
+ else if (typeof size === 'object') // bounding box test
5155
5190
  {
5156
5191
  for (const o of objects)
5157
5192
  isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
@@ -5169,23 +5204,23 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
5169
5204
 
5170
5205
  function drawEngineSplashScreen(t)
5171
5206
  {
5172
- const x = mainContext;
5173
- const w = mainCanvas.width = innerWidth;
5174
- const h = mainCanvas.height = innerHeight;
5207
+ const x = overlayContext;
5208
+ const w = overlayCanvas.width = innerWidth;
5209
+ const h = overlayCanvas.height = innerHeight;
5210
+
5175
5211
  {
5176
5212
  // background
5177
5213
  const p3 = percent(t, 1, .8);
5178
5214
  const p4 = percent(t, 0, .5);
5179
5215
  const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,Math.hypot(w,h)*.7);
5180
- g.addColorStop(0,hsl(0,0,lerp(p4,0,p3/2),p3));
5181
- g.addColorStop(1,hsl(0,0,0,p3));
5216
+ g.addColorStop(0,hsl(0,0,lerp(p4,0,p3/2),p3).toString());
5217
+ g.addColorStop(1,hsl(0,0,0,p3).toString());
5182
5218
  x.save();
5183
5219
  x.fillStyle = g;
5184
5220
  x.fillRect(0,0,w,h);
5185
5221
  }
5186
5222
 
5187
5223
  // draw LittleJS logo...
5188
-
5189
5224
  const rect = (X, Y, W, H, C)=>
5190
5225
  {
5191
5226
  x.beginPath();
@@ -5210,7 +5245,7 @@ function drawEngineSplashScreen(t)
5210
5245
  C ? x.fill() : x.stroke();
5211
5246
  };
5212
5247
  const color = (c=0, l=0) =>
5213
- hsl([.98,.3,.57,.14][c%4]-10,.8,[0,.3,.5,.8,.9][l]);
5248
+ hsl([.98,.3,.57,.14][c%4]-10,.8,[0,.3,.5,.8,.9][l]).toString();
5214
5249
  const alpha = wave(1,1,t);
5215
5250
  const p = percent(alpha, .1, .5);
5216
5251
 
@@ -5220,7 +5255,7 @@ function drawEngineSplashScreen(t)
5220
5255
  x.scale(size,size);
5221
5256
  x.translate(-40,-35);
5222
5257
  x.lineJoin = x.lineCap = 'round';
5223
- x.lineWidth = 1+p;
5258
+ x.lineWidth = .1 + p*1.9;
5224
5259
 
5225
5260
  // drawing effect
5226
5261
  const p2 = percent(alpha,.1,1);
@@ -5245,7 +5280,7 @@ function drawEngineSplashScreen(t)
5245
5280
 
5246
5281
  // little stack
5247
5282
  rect(37,14,9,6,color(3,2));
5248
- rect(37,14,4,6,color(3,3));
5283
+ rect(37,14,4.5,6,color(3,3));
5249
5284
  rect(37,14,9,6);
5250
5285
 
5251
5286
  // big stack
@@ -5289,7 +5324,8 @@ function drawEngineSplashScreen(t)
5289
5324
  x.lineTo(53+(1+i*2.9)*p,40);
5290
5325
  x.lineTo(53+(4+i*3.5)*p,54);
5291
5326
  x.fillStyle = color(0,i%2+2);
5292
- x.fill() || i%2 && x.stroke();
5327
+ x.fill();
5328
+ i%2 && x.stroke();
5293
5329
  }
5294
5330
 
5295
5331
  // wheels
@@ -5312,7 +5348,7 @@ function drawEngineSplashScreen(t)
5312
5348
  x.font = '900 16px arial';
5313
5349
  x.textAlign = 'center';
5314
5350
  x.textBaseline = 'top';
5315
- x.lineWidth = 1+p*3;
5351
+ x.lineWidth = .1+p*3.9;
5316
5352
  let w2 = 0;
5317
5353
  for (let i=0; i<s.length; ++i)
5318
5354
  w2 += x.measureText(s[i]).width;
@@ -5353,6 +5389,7 @@ export {
5353
5389
 
5354
5390
  // Globals
5355
5391
  debug,
5392
+ debugOverlay,
5356
5393
  showWatermark,
5357
5394
 
5358
5395
  // Debug