littlejsengine 1.3.8 → 1.4.5

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 (42) hide show
  1. package/README.md +30 -9
  2. package/build.bat +1 -1
  3. package/buildSetup.bat +2 -2
  4. package/engine/engine.all.js +276 -167
  5. package/engine/engine.all.min.js +1 -1
  6. package/engine/engine.all.module.js +4812 -0
  7. package/engine/engine.all.release.js +259 -143
  8. package/engine/engine.all.release.module.js +4457 -0
  9. package/engine/engine.js +12 -8
  10. package/engine/engineBuild.bat +1 -1
  11. package/engine/engineBuildModule.mjs +14 -0
  12. package/engine/engineDebug.js +17 -24
  13. package/engine/engineDraw.js +16 -16
  14. package/engine/engineExportModule.js +333 -0
  15. package/engine/engineInput.js +9 -8
  16. package/engine/engineMedals.js +10 -6
  17. package/engine/engineObject.js +4 -4
  18. package/engine/engineParticles.js +45 -21
  19. package/engine/engineTileLayer.js +2 -2
  20. package/engine/engineUtilities.js +47 -25
  21. package/engine/engineWebGL.js +114 -54
  22. package/engine/index.d.ts +64 -30
  23. package/examples/breakout/game.js +63 -11
  24. package/examples/breakout/gameObjects.js +14 -12
  25. package/examples/breakout/index.html +3 -3
  26. package/examples/empty/game.js +35 -0
  27. package/examples/empty/index.html +9 -0
  28. package/examples/empty/tiles.png +0 -0
  29. package/examples/module/game.js +122 -0
  30. package/examples/module/index.html +9 -0
  31. package/examples/module/tiles.png +0 -0
  32. package/examples/particles/index.html +14 -14
  33. package/examples/particles/tiles.png +0 -0
  34. package/examples/platformer/game.js +1 -1
  35. package/examples/platformer/gameObjects.js +7 -7
  36. package/examples/platformer/gamePlayer.js +7 -5
  37. package/examples/platformer/index.html +6 -6
  38. package/examples/puzzle/index.html +2 -2
  39. package/examples/stress/index.html +2 -2
  40. package/game.js +10 -12
  41. package/index.html +13 -13
  42. package/package.json +36 -32
@@ -233,55 +233,49 @@ const debugRender = ()=>
233
233
 
234
234
  if (debugOverlay)
235
235
  {
236
+ const saveContext = mainContext;
237
+ mainContext = overlayContext;
238
+
236
239
  // mouse pick
237
240
  let bestDistance = Infinity, bestObject;
238
241
  for (const o of engineObjects)
239
242
  {
240
243
  if (o.canvas || o.destroyed)
241
- continue; // skip tile layers
242
-
243
- const size = o.size.copy();
244
- if (!size.x || !size.y)
244
+ continue;
245
+ if (!o.size.x || !o.size.y)
245
246
  continue;
246
247
 
247
248
  const distance = mousePos.distanceSquared(o.pos);
248
249
  if (distance < bestDistance)
249
250
  {
250
251
  bestDistance = distance;
251
- bestObject = o
252
+ bestObject = o;
252
253
  }
253
254
 
254
255
  // show object info
255
- const color = new Color(
256
- o.collideTiles?1:0,
257
- o.collideSolidObjects?1:0,
258
- o.isSolid?1:0,
259
- o.parent ? .2 : .5);
260
- size.x = max(size.x, .2);
261
- size.y = max(size.y, .2);
262
- drawRect(o.pos, size, color);
263
- drawRect(o.pos, size.scale(.8), o.parent ? new Color(1,1,1,.5) : new Color(0,0,0,.8));
264
- o.parent && drawLine(o.pos, o.parent.pos, .1, new Color(0,0,1,.5));
256
+ const size = vec2(max(o.size.x, .2), max(o.size.y, .2));
257
+ const color1 = new Color(!!o.collideTiles, !!o.collideSolidObjects, !!o.isSolid, o.parent?.2:.5);
258
+ const color2 = o.parent ? new Color(1,1,1,.5) : new Color(0,0,0,.8);
259
+ drawRect(o.pos, size, color1, o.angle, 0);
260
+ drawRect(o.pos, size.scale(.8), color2, o.angle, 0);
261
+ o.parent && drawLine(o.pos, o.parent.pos, .1, new Color(0,0,1,.5), 0);
265
262
  }
266
263
 
267
264
  if (bestObject)
268
265
  {
269
- const saveContext = mainContext;
270
- mainContext = overlayContext
271
266
  const raycastHitPos = tileCollisionRaycast(bestObject.pos, mousePos);
272
267
  raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), new Color(0,1,1,.3));
273
- drawRect(mousePos.floor().add(vec2(.5)), vec2(1), new Color(0,0,1,.5));
274
- drawLine(mousePos, bestObject.pos, .1, !raycastHitPos ? new Color(0,1,0,.5) : new Color(1,0,0,.5));
268
+ drawRect(mousePos.floor().add(vec2(.5)), vec2(1), new Color(0,0,1,.5), 0, 0);
269
+ drawLine(mousePos, bestObject.pos, .1, raycastHitPos ? new Color(1,0,0,.5) : new Color(0,1,0,.5), 0);
275
270
 
276
271
  const debugText = 'mouse pos = ' + mousePos +
277
272
  '\nmouse collision = ' + getTileCollisionData(mousePos) +
278
273
  '\n\n--- object info ---\n' +
279
274
  bestObject.toString();
280
- drawTextScreen(debugText, mousePosScreen, 24, new Color, .05, undefined, undefined, 'monospace');
281
- mainContext = saveContext;
275
+ drawTextScreen(debugText, mousePosScreen, 24, new Color, .05, 0, 0, 'monospace');
282
276
  }
283
277
 
284
- glCopyToContext(mainContext);
278
+ glCopyToContext(mainContext = saveContext);
285
279
  }
286
280
 
287
281
  {
@@ -294,7 +288,6 @@ const debugRender = ()=>
294
288
 
295
289
  // create canvas transform from world space to screen space
296
290
  const pos = worldToScreen(p.pos);
297
-
298
291
  overlayContext.translate(pos.x|0, pos.y|0);
299
292
  overlayContext.rotate(p.angle);
300
293
  overlayContext.fillStyle = overlayContext.strokeStyle = p.color;
@@ -332,7 +325,7 @@ const debugRender = ()=>
332
325
  });
333
326
 
334
327
  // remove expired pritives
335
- debugPrimitives = debugPrimitives.filter(r=>r.time.get()<0);
328
+ debugPrimitives = debugPrimitives.filter(r=>r.time<0);
336
329
  }
337
330
 
338
331
  {
@@ -717,6 +710,28 @@ class Vector2
717
710
 
718
711
  ///////////////////////////////////////////////////////////////////////////////
719
712
 
713
+ /**
714
+ * Create a color object with RGBA values
715
+ * @param {Number} [r=1]
716
+ * @param {Number} [g=1]
717
+ * @param {Number} [b=1]
718
+ * @param {Number} [a=1]
719
+ * @return {Color}
720
+ * @memberof Utilities
721
+ */
722
+ const colorRGBA = (r, g, b, a)=> new Color(r, g, b, a);
723
+
724
+ /**
725
+ * Create a color object with HSLA values
726
+ * @param {Number} [h=0]
727
+ * @param {Number} [s=0]
728
+ * @param {Number} [l=1]
729
+ * @param {Number} [a=1]
730
+ * @return {Color}
731
+ * @memberof Utilities
732
+ */
733
+ const colorHSLA = (h, s, l, a)=> new Color().setHSLA(h, s, l, a);
734
+
720
735
  /**
721
736
  * Color object (red, green, blue, alpha) with some helpful functions
722
737
  * @example
@@ -808,10 +823,10 @@ class Color
808
823
  * @return {Array} */
809
824
  getHSLA()
810
825
  {
811
- const r = this.r;
812
- const g = this.g;
813
- const b = this.b;
814
- const a = this.a;
826
+ const r = clamp(this.r);
827
+ const g = clamp(this.g);
828
+ const b = clamp(this.b);
829
+ const a = clamp(this.a);
815
830
  const max = Math.max(r, g, b);
816
831
  const min = Math.min(r, g, b);
817
832
  const l = (max + min) / 2;
@@ -847,20 +862,13 @@ class Color
847
862
  ).clamp();
848
863
  }
849
864
 
850
- /** Returns this color expressed as an CSS color value
865
+ /** Returns this color expressed as a hex color code
866
+ * @param {Boolean} [useAlpha=1] - if alpha should be included in result
851
867
  * @return {String} */
852
- toString()
868
+ toString(useAlpha = 1)
853
869
  {
854
- ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1 && this.a>=0 && this.a<=1);
855
- return `rgb(${this.r*255|0},${this.g*255|0},${this.b*255|0},${this.a})`;
856
- }
857
-
858
- /** Returns this color expressed as 32 bit integer RGBA value
859
- * @return {Number} */
860
- rgbaInt()
861
- {
862
- ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1 && this.a>=0 && this.a<=1);
863
- return (this.r*255|0) + (this.g*255<<8) + (this.b*255<<16) + (this.a*255<<24);
870
+ const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
871
+ return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
864
872
  }
865
873
 
866
874
  /** Set this color from a hex code
@@ -868,21 +876,24 @@ class Color
868
876
  * @return {Color} */
869
877
  setHex(hex)
870
878
  {
871
- const fromHex = (a)=> parseInt(hex.slice(a,a+2), 16)/255;
879
+ const fromHex = (c)=> clamp(parseInt(hex.slice(c,c+2),16)/255);
872
880
  this.r = fromHex(1);
873
881
  this.g = fromHex(3),
874
882
  this.b = fromHex(5);
875
- this.a = 1;
876
- ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1);
883
+ this.a = hex.length > 7 ? fromHex(7) : 1;
877
884
  return this;
878
885
  }
879
-
880
- /** Returns this color expressed as a hex code
881
- * @return {String} */
882
- getHex()
886
+
887
+ /** Returns this color expressed as 32 bit RGBA value
888
+ * @return {Number} */
889
+ rgbaInt()
883
890
  {
884
- const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
885
- return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b);
891
+ const toByte = (c)=> clamp(c)*255|0;
892
+ const r = toByte(this.r);
893
+ const g = toByte(this.g)<<8;
894
+ const b = toByte(this.b)<<16;
895
+ const a = toByte(this.a)<<24;
896
+ return r + g + b + a;
886
897
  }
887
898
  }
888
899
 
@@ -933,6 +944,10 @@ class Timer
933
944
  /** Returns this timer expressed as a string
934
945
  * @return {String} */
935
946
  toString() { if (debug) { return this.unset() ? 'unset' : Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ); } }
947
+
948
+ /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
949
+ * @return {Number} */
950
+ valueOf() { return this.get(); }
936
951
  }
937
952
  /**
938
953
  * LittleJS Engine Settings
@@ -1455,7 +1470,7 @@ class EngineObject
1455
1470
  * @return {Boolean} - true if the raycast should hit */
1456
1471
  collideWithTileRaycast(tileData, pos) { return tileData > 0; }
1457
1472
 
1458
- /** Called to check if a tile raycast hit
1473
+ /** Called to check if a object collision should be resolved
1459
1474
  * @param {EngineObject} object - the object to test against
1460
1475
  * @return {Boolean} - true if the collision should be resolved
1461
1476
  */
@@ -1500,10 +1515,10 @@ class EngineObject
1500
1515
  }
1501
1516
 
1502
1517
  /** Set how this object collides
1503
- * @param {boolean} [collideSolidObjects=0] - Does it collide with solid objects
1504
- * @param {boolean} [isSolid=0] - Does it collide with and block other objects (expensive in large numbers)
1518
+ * @param {boolean} [collideSolidObjects=1] - Does it collide with solid objects
1519
+ * @param {boolean} [isSolid=1] - Does it collide with and block other objects (expensive in large numbers)
1505
1520
  * @param {boolean} [collideTiles=1] - Does it collide with the tile collision */
1506
- setCollision(collideSolidObjects=0, isSolid=0, collideTiles=1)
1521
+ setCollision(collideSolidObjects=1, isSolid=1, collideTiles=1)
1507
1522
  {
1508
1523
  ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
1509
1524
 
@@ -1720,7 +1735,7 @@ function drawLine(posA, posB, thickness=.1, color, useWebGL)
1720
1735
  {
1721
1736
  const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
1722
1737
  const size = vec2(thickness, halfDelta.length()*2);
1723
- drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), 0, 0, useWebGL);
1738
+ drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), useWebGL);
1724
1739
  }
1725
1740
 
1726
1741
  /** Draw directly to a 2d canvas context in world space
@@ -1737,7 +1752,7 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
1737
1752
  pos = worldToScreen(pos);
1738
1753
  size = size.scale(cameraScale);
1739
1754
  context.save();
1740
- context.translate(pos.x+.5|0, pos.y-.5|0);
1755
+ context.translate(pos.x+.5|0, pos.y+.5|0);
1741
1756
  context.rotate(angle);
1742
1757
  context.scale(mirror ? -size.x : size.x, size.y);
1743
1758
  drawFunction(context);
@@ -1766,21 +1781,21 @@ function setBlendMode(additive, useWebGL=glEnable)
1766
1781
  * @param {Color} [lineColor=new Color(0,0,0)]
1767
1782
  * @param {String} [textAlign='center']
1768
1783
  * @memberof Draw */
1769
- function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault)
1784
+ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
1770
1785
  {
1771
- overlayContext.fillStyle = color;
1772
- overlayContext.lineWidth = lineWidth;
1773
- overlayContext.strokeStyle = lineColor;
1774
- overlayContext.textAlign = textAlign;
1775
- overlayContext.font = size + 'px '+ font;
1776
- overlayContext.textBaseline = 'middle';
1777
- overlayContext.lineJoin = 'round';
1786
+ context.fillStyle = color;
1787
+ context.lineWidth = lineWidth;
1788
+ context.strokeStyle = lineColor;
1789
+ context.textAlign = textAlign;
1790
+ context.font = size + 'px '+ font;
1791
+ context.textBaseline = 'middle';
1792
+ context.lineJoin = 'round';
1778
1793
 
1779
1794
  pos = pos.copy();
1780
1795
  (text+'').split('\n').forEach(line=>
1781
1796
  {
1782
- lineWidth && overlayContext.strokeText(line, pos.x, pos.y);
1783
- overlayContext.fillText(line, pos.x, pos.y);
1797
+ lineWidth && context.strokeText(line, pos.x, pos.y);
1798
+ context.fillText(line, pos.x, pos.y);
1784
1799
  pos.y += size;
1785
1800
  });
1786
1801
  }
@@ -1797,7 +1812,7 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
1797
1812
  * @memberof Draw */
1798
1813
  function drawText(text, pos, size=1, color, lineWidth, lineColor, textAlign, font)
1799
1814
  {
1800
- drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font);
1815
+ drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, mainContext);
1801
1816
  }
1802
1817
 
1803
1818
  ///////////////////////////////////////////////////////////////////////////////
@@ -1820,7 +1835,7 @@ let engineFontImage;
1820
1835
  class FontImage
1821
1836
  {
1822
1837
  /** Create an image font
1823
- * @param {Image} [image] - The image the font is stored in, if undefined the default font is used
1838
+ * @param {HTMLImageElement} [image] - The image the font is stored in, if undefined the default font is used
1824
1839
  * @param {Vector2} [tileSize=vec2(8)] - The size of the font source tiles
1825
1840
  * @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
1826
1841
  * @param {Number} [startTileIndex=0] - Tile index in image where font starts
@@ -1857,7 +1872,7 @@ class FontImage
1857
1872
  const size = this.tileSize;
1858
1873
  const drawSize = size.add(this.paddingSize).scale(scale);
1859
1874
  const cols = this.image.width / this.tileSize.x |0;
1860
- text.split('\n').forEach((line, i)=>
1875
+ (text+'').split('\n').forEach((line, i)=>
1861
1876
  {
1862
1877
  const centerOffset = center ? line.length * size.x * scale / 2 |0 : 0;
1863
1878
  for(let j=line.length; j--;)
@@ -1919,6 +1934,7 @@ function toggleFullscreen()
1919
1934
  document.body.mozRequestFullScreen();
1920
1935
  }
1921
1936
  }
1937
+
1922
1938
  /**
1923
1939
  * LittleJS Input System
1924
1940
  * <br> - Tracks key down, pressed, and released
@@ -2033,7 +2049,7 @@ let inputData = [[]];
2033
2049
  function inputUpdate()
2034
2050
  {
2035
2051
  // clear input when lost focus (prevent stuck keys)
2036
- document.hasFocus() || clearInput();
2052
+ isTouchDevice || document.hasFocus() || clearInput();
2037
2053
 
2038
2054
  // update mouse world space position
2039
2055
  mousePos = screenToWorld(mousePosScreen);
@@ -2108,7 +2124,7 @@ function gamepadsUpdate()
2108
2124
  }
2109
2125
  }
2110
2126
 
2111
- if (!gamepadsEnable || !navigator.getGamepads || !document.hasFocus() && !debug)
2127
+ if (!gamepadsEnable || !navigator || !navigator.getGamepads || !document.hasFocus() && !debug)
2112
2128
  return;
2113
2129
 
2114
2130
  // poll gamepads
@@ -2157,7 +2173,7 @@ function gamepadsUpdate()
2157
2173
  /** Pulse the vibration hardware if it exists
2158
2174
  * @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array
2159
2175
  * @memberof Input */
2160
- const vibrate = (pattern)=> vibrateEnable && Navigator.vibrate && Navigator.vibrate(pattern);
2176
+ const vibrate = (pattern)=> vibrateEnable && navigator && navigator.vibrate && navigator.vibrate(pattern);
2161
2177
 
2162
2178
  /** Cancel any ongoing vibration
2163
2179
  * @memberof Input */
@@ -2175,7 +2191,7 @@ const isTouchDevice = window.ontouchstart !== undefined;
2175
2191
  if (isTouchDevice)
2176
2192
  {
2177
2193
  // handle all touch events the same way
2178
- let wasTouching, hadTouchInput;
2194
+ let wasTouching, hadTouch;
2179
2195
  ontouchstart = ontouchmove = ontouchend = (e)=>
2180
2196
  {
2181
2197
  e.button = 0; // all touches are left click
@@ -2184,7 +2200,8 @@ if (isTouchDevice)
2184
2200
  const touching = e.touches.length;
2185
2201
  if (touching)
2186
2202
  {
2187
- hadTouchInput || zzfx(0, hadTouchInput=1) ; // fix mobile audio, force it to play a sound the first time
2203
+ // fix mobile audio, force it to play a sound on first touch
2204
+ hadTouch || zzfx(0, hadTouch=1);
2188
2205
 
2189
2206
  // set event pos and pass it along
2190
2207
  e.x = e.touches[0].clientX;
@@ -2197,8 +2214,8 @@ if (isTouchDevice)
2197
2214
  // set was touching
2198
2215
  wasTouching = touching;
2199
2216
 
2200
- // prevent normal mouse events from being called
2201
- return !e.cancelable;
2217
+ // must return true so the document will get focus
2218
+ return true;
2202
2219
  }
2203
2220
  }
2204
2221
 
@@ -2283,7 +2300,7 @@ function touchGamepadRender()
2283
2300
  return;
2284
2301
 
2285
2302
  // fade off when not touching or paused
2286
- const alpha = percent(touchGamepadTimer.get(), 4, 3);
2303
+ const alpha = percent(touchGamepadTimer, 4, 3);
2287
2304
  if (!alpha || paused)
2288
2305
  return;
2289
2306
 
@@ -3055,7 +3072,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3055
3072
  redrawEnd()
3056
3073
  {
3057
3074
  ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
3058
- glCopyToContext(mainContext, 1);
3075
+ glEnable && glCopyToContext(mainContext, 1);
3059
3076
  //debugSaveCanvas(this.canvas);
3060
3077
 
3061
3078
  // set stuff back to normal
@@ -3129,7 +3146,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3129
3146
  const cols = tileImage.width/tileSize.x;
3130
3147
  context.globalAlpha = color.a; // only alpha, no color, is supported in this mode
3131
3148
  context.drawImage(tileImage,
3132
- (tileIndex%cols)*tileSize.x, (tileIndex/cols|0)*tileSize.x,
3149
+ (tileIndex%cols)*tileSize.x, (tileIndex/cols|0)*tileSize.y,
3133
3150
  tileSize.x, tileSize.y, -.5, -.5, 1, 1);
3134
3151
  }
3135
3152
  });
@@ -3199,6 +3216,7 @@ class ParticleEmitter extends EngineObject
3199
3216
  * @param {Boolean} [additive=0] - Should particles use addtive blend
3200
3217
  * @param {Boolean} [randomColorLinear=1] - Should color be randomized linearly or across each component
3201
3218
  * @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
3219
+ * @param {Boolean} [localSpace=0] - Should it be in local space of emitter (world space is default)
3202
3220
  */
3203
3221
  constructor
3204
3222
  (
@@ -3228,7 +3246,8 @@ class ParticleEmitter extends EngineObject
3228
3246
  collideTiles,
3229
3247
  additive,
3230
3248
  randomColorLinear = 1,
3231
- renderOrder = additive ? 1e9 : 0
3249
+ renderOrder = additive ? 1e9 : 0,
3250
+ localSpace
3232
3251
  )
3233
3252
  {
3234
3253
  super(pos, new Vector2, tileIndex, tileSize, angle, undefined, renderOrder);
@@ -3282,6 +3301,8 @@ class ParticleEmitter extends EngineObject
3282
3301
  this.collideTiles = collideTiles;
3283
3302
  /** @property {Number} - Should particles use addtive blend */
3284
3303
  this.additive = additive;
3304
+ /** @property {Boolean} - Should it be in local space of emitter */
3305
+ this.localSpace = localSpace;
3285
3306
  /** @property {Number} - If set the partile is drawn as a trail, stretched in the drection of velocity */
3286
3307
  this.trailScale = 0;
3287
3308
 
@@ -3317,30 +3338,38 @@ class ParticleEmitter extends EngineObject
3317
3338
  emitParticle()
3318
3339
  {
3319
3340
  // spawn a particle
3320
- const pos = this.emitSize.x != undefined ? // check if vec2 was used for size
3321
- (new Vector2(rand(-.5,.5), rand(-.5,.5))).multiply(this.emitSize).rotate(this.angle) // box emitter
3322
- : randInCircle(this.emitSize * .5); // circle emitter
3323
- const particle = new Particle(this.pos.add(pos), this.tileIndex, this.tileSize,
3324
- this.angle + rand(this.particleConeAngle, -this.particleConeAngle));
3341
+ let pos = this.emitSize.x != undefined ? // check if vec2 was used for size
3342
+ (new Vector2(rand(-.5,.5), rand(-.5,.5)))
3343
+ .multiply(this.emitSize).rotate(this.angle) // box emitter
3344
+ : randInCircle(this.emitSize * .5); // circle emitter
3345
+ let angle = rand(this.particleConeAngle, -this.particleConeAngle);
3346
+ if (!this.localSpace)
3347
+ {
3348
+ pos = this.pos.add(pos);
3349
+ angle += this.angle;
3350
+ }
3351
+
3352
+ const particle = new Particle(pos, this.tileIndex, this.tileSize, angle);
3325
3353
 
3326
3354
  // randomness scales each paremeter by a percentage
3327
3355
  const randomness = this.randomness;
3328
3356
  const randomizeScale = (v)=> v + v*rand(randomness, -randomness);
3329
3357
 
3330
3358
  // randomize particle settings
3331
- const particleTime = randomizeScale(this.particleTime);
3332
- const sizeStart = randomizeScale(this.sizeStart);
3333
- const sizeEnd = randomizeScale(this.sizeEnd);
3334
- const speed = randomizeScale(this.speed);
3335
- const angleSpeed = randomizeScale(this.angleSpeed) * randSign();
3336
- const coneAngle = rand(this.emitConeAngle, -this.emitConeAngle);
3337
- const colorStart = randColor(this.colorStartA, this.colorStartB, this.randomColorLinear);
3338
- const colorEnd = randColor(this.colorEndA, this.colorEndB, this.randomColorLinear);
3359
+ const particleTime = randomizeScale(this.particleTime);
3360
+ const sizeStart = randomizeScale(this.sizeStart);
3361
+ const sizeEnd = randomizeScale(this.sizeEnd);
3362
+ const speed = randomizeScale(this.speed);
3363
+ const angleSpeed = randomizeScale(this.angleSpeed) * randSign();
3364
+ const coneAngle = rand(this.emitConeAngle, -this.emitConeAngle);
3365
+ const colorStart = randColor(this.colorStartA, this.colorStartB, this.randomColorLinear);
3366
+ const colorEnd = randColor(this.colorEndA, this.colorEndB, this.randomColorLinear);
3367
+ const velocityAngle = this.localSpace ? coneAngle : this.angle + coneAngle;
3339
3368
 
3340
3369
  // build particle settings
3341
3370
  particle.colorStart = colorStart;
3342
3371
  particle.colorEndDelta = colorEnd.subtract(colorStart);
3343
- particle.velocity = (new Vector2).setAngle(this.angle + coneAngle, speed);
3372
+ particle.velocity = (new Vector2).setAngle(velocityAngle, speed);
3344
3373
  particle.angleVelocity = angleSpeed;
3345
3374
  particle.lifeTime = particleTime;
3346
3375
  particle.sizeStart = sizeStart;
@@ -3356,6 +3385,7 @@ class ParticleEmitter extends EngineObject
3356
3385
  particle.renderOrder = this.renderOrder;
3357
3386
  particle.trailScale = this.trailScale;
3358
3387
  particle.mirror = rand()<.5;
3388
+ particle.localSpaceEmitter = this.localSpace && this;
3359
3389
 
3360
3390
  // setup callbacks for particles
3361
3391
  particle.destroyCallback = this.particleDestroyCallback;
@@ -3402,20 +3432,31 @@ class Particle extends EngineObject
3402
3432
 
3403
3433
  // draw the particle
3404
3434
  this.additive && setBlendMode(1);
3435
+
3436
+ let pos = this.pos, angle = this.angle;
3437
+ if (this.localSpaceEmitter)
3438
+ {
3439
+ // in local space of emitter
3440
+ pos = this.localSpaceEmitter.pos.add(pos.rotate(-this.localSpaceEmitter.angle));
3441
+ angle += this.localSpaceEmitter.angle;
3442
+ }
3405
3443
  if (this.trailScale)
3406
3444
  {
3407
3445
  // trail style particles
3408
- const speed = this.velocity.length();
3409
- const direction = this.velocity.scale(1/speed);
3446
+ let velocity = this.velocity;
3447
+ if (this.localSpaceEmitter)
3448
+ velocity = velocity.rotate(-this.localSpaceEmitter.angle);
3449
+ const speed = velocity.length();
3450
+ const direction = velocity.scale(1/speed);
3410
3451
  const trailLength = speed * this.trailScale;
3411
3452
  size.y = max(size.x, trailLength);
3412
- this.angle = direction.angle();
3413
- drawTile(this.pos.add(direction.multiply(vec2(0,-trailLength/2))), size, this.tileIndex, this.tileSize, color, this.angle, this.mirror);
3453
+ angle = direction.angle();
3454
+ drawTile(pos.add(direction.multiply(vec2(0,-trailLength/2))), size, this.tileIndex, this.tileSize, color, angle, this.mirror);
3414
3455
  }
3415
3456
  else
3416
- drawTile(this.pos, size, this.tileIndex, this.tileSize, color, this.angle, this.mirror);
3457
+ drawTile(pos, size, this.tileIndex, this.tileSize, color, angle, this.mirror);
3417
3458
  this.additive && setBlendMode();
3418
- debugParticles && debugRect(this.pos, size, '#f005', 0, this.angle);
3459
+ debugParticles && debugRect(pos, size, '#f005', 0, angle);
3419
3460
 
3420
3461
  if (p == 1)
3421
3462
  {
@@ -3445,11 +3486,6 @@ const medals = [];
3445
3486
  * @memberof Medals */
3446
3487
  let medalsPreventUnlock;
3447
3488
 
3448
- /** This can used to enable Newgrounds functionality
3449
- * @type {Newgrounds}
3450
- * @memberof Medals */
3451
- let newgrounds;
3452
-
3453
3489
  // Engine internal variables not exposed to documentation
3454
3490
  let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
3455
3491
 
@@ -3464,7 +3500,7 @@ function medalsInit(saveName)
3464
3500
  {
3465
3501
  // check if medals are unlocked
3466
3502
  medalsSaveName = saveName;
3467
- debugMedals || medals.forEach(medal=> localStorage[medal.storageKey()]);
3503
+ debugMedals || medals.forEach(medal=> medal.unlocked = (localStorage[medal.storageKey()] | 0));
3468
3504
  }
3469
3505
 
3470
3506
  /**
@@ -3597,6 +3633,15 @@ function medalsRender()
3597
3633
 
3598
3634
  ///////////////////////////////////////////////////////////////////////////////
3599
3635
 
3636
+ // global Newgrounds object
3637
+ let newgrounds;
3638
+
3639
+ /** This can used to enable Newgrounds functionality
3640
+ * @param {Number} app_id - The newgrounds App ID
3641
+ * @param {String} [cipher] - The encryption Key (AES-128/Base64)
3642
+ * @memberof Medals */
3643
+ function newgroundsInit(app_id, cipher) { newgrounds = new Newgrounds(app_id, cipher); }
3644
+
3600
3645
  /**
3601
3646
  * Newgrounds API wrapper object
3602
3647
  * @example
@@ -3757,15 +3802,13 @@ let glContext;
3757
3802
  let glTileTexture;
3758
3803
 
3759
3804
  // WebGL internal variables not exposed to documentation
3760
- let glActiveTexture, glShader, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
3805
+ let glActiveTexture, glShader, glArrayBuffer, glVertexData, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
3761
3806
 
3762
3807
  ///////////////////////////////////////////////////////////////////////////////
3763
3808
 
3764
3809
  // Init WebGL, called automatically by the engine
3765
3810
  function glInit()
3766
3811
  {
3767
- if (!glEnable) return;
3768
-
3769
3812
  // create the canvas and tile texture
3770
3813
  glCanvas = document.createElement('canvas');
3771
3814
  glContext = glCanvas.getContext('webgl', {antialias: false});
@@ -3798,24 +3841,11 @@ function glInit()
3798
3841
  );
3799
3842
 
3800
3843
  // init buffers
3801
- const glVertexData = new ArrayBuffer(gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE);
3802
- glCreateBuffer(gl_ARRAY_BUFFER, glVertexData.byteLength, gl_DYNAMIC_DRAW);
3844
+ glVertexData = new ArrayBuffer(gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE);
3845
+ glArrayBuffer = glContext.createBuffer();
3803
3846
  glPositionData = new Float32Array(glVertexData);
3804
3847
  glColorData = new Uint32Array(glVertexData);
3805
-
3806
- // setup the vertex data array
3807
- let offset = glBatchCount = 0;
3808
- const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
3809
- {
3810
- const location = glContext.getAttribLocation(glShader, name);
3811
- glContext.enableVertexAttribArray(location);
3812
- glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
3813
- offset += size*typeSize;
3814
- }
3815
- initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
3816
- initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
3817
- initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
3818
- initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
3848
+ glBatchCount = 0;
3819
3849
  }
3820
3850
 
3821
3851
  /** Set the WebGl blend mode, normally you should call setBlendMode instead
@@ -3823,8 +3853,6 @@ function glInit()
3823
3853
  * @memberof WebGL */
3824
3854
  function glSetBlendMode(additive)
3825
3855
  {
3826
- if (!glEnable) return;
3827
-
3828
3856
  // setup blending
3829
3857
  glAdditive = additive;
3830
3858
  }
@@ -3835,8 +3863,6 @@ function glSetBlendMode(additive)
3835
3863
  * @memberof WebGL */
3836
3864
  function glSetTexture(texture=glTileTexture)
3837
3865
  {
3838
- if (!glEnable) return;
3839
-
3840
3866
  // must flush cache with the old texture to set a new one
3841
3867
  if (texture != glActiveTexture)
3842
3868
  glFlush();
@@ -3851,8 +3877,6 @@ function glSetTexture(texture=glTileTexture)
3851
3877
  * @memberof WebGL */
3852
3878
  function glCompileShader(source, type)
3853
3879
  {
3854
- if (!glEnable) return;
3855
-
3856
3880
  // build the shader
3857
3881
  const shader = glContext.createShader(type);
3858
3882
  glContext.shaderSource(shader, source);
@@ -3871,8 +3895,6 @@ function glCompileShader(source, type)
3871
3895
  * @memberof WebGL */
3872
3896
  function glCreateProgram(vsSource, fsSource)
3873
3897
  {
3874
- if (!glEnable) return;
3875
-
3876
3898
  // build the program
3877
3899
  const program = glContext.createProgram();
3878
3900
  glContext.attachShader(program, glCompileShader(vsSource, gl_VERTEX_SHADER));
@@ -3885,35 +3907,16 @@ function glCreateProgram(vsSource, fsSource)
3885
3907
  return program;
3886
3908
  }
3887
3909
 
3888
- /** Create WebGL buffer
3889
- * @param bufferType
3890
- * @param size
3891
- * @param usage
3892
- * @return {WebGLBuffer}
3893
- * @memberof WebGL */
3894
- function glCreateBuffer(bufferType, size, usage)
3895
- {
3896
- if (!glEnable) return;
3897
-
3898
- // build the buffer
3899
- const buffer = glContext.createBuffer();
3900
- glContext.bindBuffer(bufferType, buffer);
3901
- glContext.bufferData(bufferType, size, usage);
3902
- return buffer;
3903
- }
3904
-
3905
3910
  /** Create WebGL texture from an image and set the texture settings
3906
3911
  * @param {Image} image
3907
3912
  * @return {WebGLTexture}
3908
3913
  * @memberof WebGL */
3909
3914
  function glCreateTexture(image)
3910
3915
  {
3911
- if (!glEnable || !image || !image.width) return;
3912
-
3913
3916
  // build the texture
3914
3917
  const texture = glContext.createTexture();
3915
3918
  glContext.bindTexture(gl_TEXTURE_2D, texture);
3916
- glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
3919
+ image && image.width && glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
3917
3920
 
3918
3921
  // use point filtering for pixelated rendering
3919
3922
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, cavasPixelated ? gl_NEAREST : gl_LINEAR);
@@ -3926,16 +3929,31 @@ function glCreateTexture(image)
3926
3929
  // called automatically by engine before render
3927
3930
  function glPreRender(width, height, cameraX, cameraY, cameraScale)
3928
3931
  {
3929
- if (!glEnable) return;
3930
-
3931
3932
  // clear and set to same size as main canvas
3932
3933
  glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
3933
3934
  glContext.clear(gl_COLOR_BUFFER_BIT);
3934
3935
 
3935
3936
  // set up the shader
3936
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
3937
3937
  glContext.useProgram(glShader);
3938
+ glContext.activeTexture(gl_TEXTURE0);
3939
+ glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
3940
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
3941
+ glContext.bufferData(gl_ARRAY_BUFFER, glVertexData.byteLength, gl_DYNAMIC_DRAW);
3938
3942
  glSetBlendMode();
3943
+
3944
+ // set vertex attributes
3945
+ let offset = 0;
3946
+ const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
3947
+ {
3948
+ const location = glContext.getAttribLocation(glShader, name);
3949
+ glContext.enableVertexAttribArray(location);
3950
+ glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
3951
+ offset += size*typeSize;
3952
+ }
3953
+ initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
3954
+ initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
3955
+ initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
3956
+ initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
3939
3957
 
3940
3958
  // build the transform matrix
3941
3959
  const sx = 2 * cameraScale / width;
@@ -3954,7 +3972,7 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
3954
3972
  * @memberof WebGL */
3955
3973
  function glFlush()
3956
3974
  {
3957
- if (!glEnable || !glBatchCount) return;
3975
+ if (!glBatchCount) return;
3958
3976
 
3959
3977
  const destBlend = glBatchAdditive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
3960
3978
  glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
@@ -3974,7 +3992,7 @@ function glFlush()
3974
3992
  * @memberof WebGL */
3975
3993
  function glCopyToContext(context, forceDraw)
3976
3994
  {
3977
- if ((!glEnable || !glBatchCount) && !forceDraw) return;
3995
+ if (!glBatchCount && !forceDraw) return;
3978
3996
 
3979
3997
  glFlush();
3980
3998
 
@@ -3998,8 +4016,6 @@ function glCopyToContext(context, forceDraw)
3998
4016
  * @memberof WebGL */
3999
4017
  function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffffff, rgbaAdditive=0)
4000
4018
  {
4001
- if (!glEnable) return;
4002
-
4003
4019
  // flush if there is no room for more verts or if different blend mode
4004
4020
  if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
4005
4021
  glFlush();
@@ -4048,6 +4064,90 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffff
4048
4064
  glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
4049
4065
  }
4050
4066
 
4067
+ ///////////////////////////////////////////////////////////////////////////////
4068
+ // post processing - can be enabled to pass other canvases through a final shader
4069
+
4070
+ let glPostShader, glPostArrayBuffer, glPostTexture;
4071
+
4072
+ /** Set up a post processing shader
4073
+ * @param {String} shaderCode
4074
+ * @memberof WebGL */
4075
+ function glInitPostProcess(shaderCode)
4076
+ {
4077
+ ASSERT(!glPostShader); // can only have 1 post effects shader
4078
+
4079
+ if (!shaderCode) // default shader
4080
+ shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture2D(iChannel0,p/iResolution.xy);}';
4081
+
4082
+ // create the shader
4083
+ glPostShader = glCreateProgram(
4084
+ 'precision highp float;'+ // use highp for better accuracy
4085
+ 'attribute vec2 p;'+ // position
4086
+ 'void main(){'+ // shader entry point
4087
+ 'gl_Position=vec4(p,1,1);'+ // set position
4088
+ '}' // end of shader
4089
+ ,
4090
+ 'precision highp float;'+ // use highp for better accuracy
4091
+ 'uniform sampler2D iChannel0;'+ // input texture
4092
+ 'uniform vec3 iResolution;'+ // size of output texture
4093
+ 'uniform float iTime;'+ // time passed
4094
+ '\n' + shaderCode + '\n'+ // insert custom shader code
4095
+ 'void main(){'+ // shader entry point
4096
+ 'mainImage(gl_FragColor,gl_FragCoord.xy);'+ // call post process function
4097
+ 'gl_FragColor.a=1.;'+ // always use full alpha
4098
+ '}' // end of shader
4099
+ );
4100
+
4101
+ // create buffer and texture
4102
+ glPostArrayBuffer = glContext.createBuffer();
4103
+ glPostTexture = glCreateTexture();
4104
+
4105
+ // hide the original 2d canvas
4106
+ mainCanvas.style.visibility = 'hidden';
4107
+ }
4108
+
4109
+ // Render the post processing shader, called automatically by the engine
4110
+ function glRenderPostProcess()
4111
+ {
4112
+ if (!glPostShader)
4113
+ return;
4114
+
4115
+ // prepare to render post process shader
4116
+ const width = mainCanvas.width, height = mainCanvas.height;
4117
+ if (glEnable)
4118
+ {
4119
+ glFlush(); // clear out the buffer
4120
+ mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
4121
+ }
4122
+ else
4123
+ glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height); // set viewport
4124
+
4125
+ // setup shader program to draw one triangle
4126
+ glContext.useProgram(glPostShader);
4127
+ glContext.disable(gl_BLEND);
4128
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glPostArrayBuffer);
4129
+ glContext.bufferData(gl_ARRAY_BUFFER, new Float32Array([-3,1,1,-3,1,1]), gl_STATIC_DRAW);
4130
+ glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, true);
4131
+
4132
+ // set textures, pass in the 2d canvas and gl canvas in separate texture channels
4133
+ glContext.activeTexture(gl_TEXTURE0);
4134
+ glContext.bindTexture(gl_TEXTURE_2D, glPostTexture);
4135
+ glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, mainCanvas);
4136
+
4137
+ // set vertex position attribute
4138
+ const vertexByteStride = 8;
4139
+ const pLocation = glContext.getAttribLocation(glPostShader, 'p');
4140
+ glContext.enableVertexAttribArray(pLocation);
4141
+ glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, 0, vertexByteStride, 0);
4142
+
4143
+ // set uniforms and draw
4144
+ const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
4145
+ glContext.uniform1i(uniformLocation('iChannel0'), 0);
4146
+ glContext.uniform1f(uniformLocation('iTime'), time);
4147
+ glContext.uniform3f(uniformLocation('iResolution'), width, height, 1);
4148
+ glContext.drawArrays(gl_TRIANGLES, 0, 3);
4149
+ }
4150
+
4051
4151
  ///////////////////////////////////////////////////////////////////////////////
4052
4152
  // store gl constants as integers so their name doesn't use space in minifed
4053
4153
  const
@@ -4058,6 +4158,7 @@ gl_ONE_MINUS_SRC_ALPHA = 771,
4058
4158
  gl_BLEND = 3042,
4059
4159
  gl_TEXTURE_2D = 3553,
4060
4160
  gl_UNSIGNED_BYTE = 5121,
4161
+ gl_BYTE = 5120,
4061
4162
  gl_FLOAT = 5126,
4062
4163
  gl_RGBA = 6408,
4063
4164
  gl_NEAREST = 9728,
@@ -4068,12 +4169,16 @@ gl_TEXTURE_WRAP_S = 10242,
4068
4169
  gl_TEXTURE_WRAP_T = 10243,
4069
4170
  gl_COLOR_BUFFER_BIT = 16384,
4070
4171
  gl_CLAMP_TO_EDGE = 33071,
4172
+ gl_TEXTURE0 = 33984,
4173
+ gl_TEXTURE1 = 33985,
4071
4174
  gl_ARRAY_BUFFER = 34962,
4175
+ gl_STATIC_DRAW = 35044,
4072
4176
  gl_DYNAMIC_DRAW = 35048,
4073
4177
  gl_FRAGMENT_SHADER = 35632,
4074
4178
  gl_VERTEX_SHADER = 35633,
4075
4179
  gl_COMPILE_STATUS = 35713,
4076
4180
  gl_LINK_STATUS = 35714,
4181
+ gl_UNPACK_FLIP_Y_WEBGL = 37440,
4077
4182
 
4078
4183
  // constants for batch rendering
4079
4184
  gl_VERTICES_PER_QUAD = 6,
@@ -4104,7 +4209,7 @@ gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2
4104
4209
  const engineName = 'LittleJS';
4105
4210
 
4106
4211
  /** Version of engine */
4107
- const engineVersion = '1.3.8';
4212
+ const engineVersion = '1.4.5';
4108
4213
 
4109
4214
  /** Frames per second to update objects
4110
4215
  * @default */
@@ -4139,8 +4244,7 @@ let frameTimeLastMS = 0, frameTimeBufferMS = 0, tileImageSize, tileImageFixBleed
4139
4244
  let averageFPS, drawCount;
4140
4245
 
4141
4246
  // css text used for elements created by engine
4142
- const styleBody = 'margin:0;overflow:hidden;background:#000' +
4143
- ';touch-action:none;user-select:none;-webkit-user-select:none;-moz-user-select:none';
4247
+ const styleBody = 'margin:0;overflow:hidden;background:#000';
4144
4248
  const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
4145
4249
 
4146
4250
  ///////////////////////////////////////////////////////////////////////////////
@@ -4170,7 +4274,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4170
4274
 
4171
4275
  // init stuff and start engine
4172
4276
  debugInit();
4173
- glInit();
4277
+ glEnable && glInit();
4174
4278
 
4175
4279
  // create overlay canvas for hud to appear above gl canvas
4176
4280
  document.body.appendChild(overlayCanvas = document.createElement('canvas'));
@@ -4190,10 +4294,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4190
4294
  frameTimeLastMS = frameTimeMS;
4191
4295
  if (debug || showWatermark)
4192
4296
  averageFPS = lerp(.05, averageFPS || 0, 1e3/(frameTimeDeltaMS||1));
4297
+ const debugSpeedUp = debug && keyIsDown(107); // +
4298
+ const debugSpeedDown = debug && keyIsDown(109); // -
4193
4299
  if (debug)
4194
- frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
4300
+ frameTimeDeltaMS *= debugSpeedUp ? 5 : debugSpeedDown ? .2 : 1; // +/- to speed/slow time
4195
4301
  timeReal += frameTimeDeltaMS / 1e3;
4196
- frameTimeBufferMS = min(frameTimeBufferMS + !paused * frameTimeDeltaMS, 50); // clamp incase of slow framerate
4302
+ frameTimeBufferMS += !paused * frameTimeDeltaMS;
4303
+ if (!debugSpeedUp)
4304
+ frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp incase of slow framerate
4197
4305
 
4198
4306
  if (canvasFixedSize.x)
4199
4307
  {
@@ -4266,10 +4374,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4266
4374
  for (const o of engineObjects)
4267
4375
  o.destroyed || o.render();
4268
4376
  gameRenderPost();
4377
+ glRenderPostProcess();
4269
4378
  medalsRender();
4270
4379
  touchGamepadRender();
4271
4380
  debugRender();
4272
- glCopyToContext(mainContext);
4381
+ glEnable && glCopyToContext(mainContext);
4273
4382
 
4274
4383
  if (showWatermark)
4275
4384
  {
@@ -4304,7 +4413,7 @@ function enginePreRender()
4304
4413
  mainContext.imageSmoothingEnabled = !cavasPixelated;
4305
4414
 
4306
4415
  // setup gl rendering if enabled
4307
- glPreRender(mainCanvas.width, mainCanvas.height, cameraPos.x, cameraPos.y, cameraScale);
4416
+ glEnable && glPreRender(mainCanvas.width, mainCanvas.height, cameraPos.x, cameraPos.y, cameraScale);
4308
4417
  }
4309
4418
 
4310
4419
  ///////////////////////////////////////////////////////////////////////////////