littlejsengine 1.11.1 → 1.11.4

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 (61) hide show
  1. package/.vscode/launch.json +14 -0
  2. package/README.md +14 -20
  3. package/dist/littlejs.d.ts +41 -30
  4. package/dist/littlejs.esm.js +145 -141
  5. package/dist/littlejs.esm.min.js +1 -1
  6. package/dist/littlejs.js +141 -140
  7. package/dist/littlejs.min.js +1 -1
  8. package/dist/littlejs.release.js +138 -133
  9. package/examples/box2d/gameObjects.js +4 -4
  10. package/examples/box2d/scenes.js +2 -2
  11. package/examples/breakout/gameObjects.js +2 -2
  12. package/examples/breakoutTutorial/game.js +1 -1
  13. package/examples/index.html +1 -7
  14. package/examples/logo.png +0 -0
  15. package/examples/module/game.js +1 -1
  16. package/examples/platformer/game.js +2 -2
  17. package/examples/platformer/gameCharacter.js +5 -5
  18. package/examples/platformer/gameEffects.js +4 -4
  19. package/examples/platformer/gameObjects.js +2 -2
  20. package/examples/puzzle/game.js +2 -2
  21. package/examples/starter/game.js +10 -44
  22. package/examples/starter/tiles.png +0 -0
  23. package/jsconfig.json +1 -0
  24. package/package.json +2 -2
  25. package/plugins/box2d.js +10 -10
  26. package/plugins/newgrounds.js +1 -1
  27. package/src/engine.js +5 -3
  28. package/src/engineAudio.js +4 -4
  29. package/src/engineDebug.js +3 -7
  30. package/src/engineDraw.js +50 -25
  31. package/src/engineExport.js +4 -1
  32. package/src/engineInput.js +1 -1
  33. package/src/engineObject.js +8 -8
  34. package/src/engineParticles.js +6 -6
  35. package/src/engineSettings.js +5 -5
  36. package/src/engineTileLayer.js +2 -2
  37. package/src/engineUtilities.js +5 -5
  38. package/src/engineWebGL.js +52 -74
  39. package/LittleJS.zip +0 -0
  40. package/examples/electron/build.js +0 -107
  41. package/examples/electron/electron.js +0 -43
  42. package/examples/electron/game.js +0 -131
  43. package/examples/electron/index.html +0 -13
  44. package/examples/electron/package.json +0 -22
  45. package/examples/electron/tiles.png +0 -0
  46. package/examples/js13k/build.bat +0 -2
  47. package/examples/js13k/build.js +0 -131
  48. package/examples/js13k/game.js +0 -118
  49. package/examples/js13k/index.html +0 -21
  50. package/examples/js13k/tiles.png +0 -0
  51. package/examples/typescript/build.bat +0 -5
  52. package/examples/typescript/build.js +0 -33
  53. package/examples/typescript/game.js +0 -102
  54. package/examples/typescript/game.ts +0 -134
  55. package/examples/typescript/index.html +0 -10
  56. package/examples/typescript/tiles.png +0 -0
  57. package/examples/typescript/tsconfig.json +0 -8
  58. package/shortExamples/engine.html +0 -71
  59. package/shortExamples/index.html +0 -47
  60. package/shortExamples/index.js +0 -0
  61. package/shortExamples/tiles.png +0 -0
@@ -20,7 +20,7 @@
20
20
  * @memberof Debug */
21
21
  const debug = true;
22
22
 
23
- /** True if asserts are enaled
23
+ /** True if asserts are enabled
24
24
  * @type {Boolean}
25
25
  * @default
26
26
  * @memberof Debug */
@@ -248,12 +248,8 @@ function debugRender()
248
248
 
249
249
  if (debugTakeScreenshot)
250
250
  {
251
- // composite canvas
252
- glCopyToContext(mainContext, true);
253
- mainContext.drawImage(overlayCanvas, 0, 0);
254
- overlayCanvas.width |= 0;
255
-
256
- // remove alpha and save
251
+ // combine canvases, remove alpha and save
252
+ combineCanvases();
257
253
  const w = mainCanvas.width, h = mainCanvas.height;
258
254
  overlayContext.fillRect(0,0,w,h);
259
255
  overlayContext.drawImage(mainCanvas, 0, 0);
@@ -486,7 +482,7 @@ function debugRender()
486
482
  * @memberof Utilities */
487
483
  const PI = Math.PI;
488
484
 
489
- /** Returns absoulte value of value passed in
485
+ /** Returns absolute value of value passed in
490
486
  * @param {Number} value
491
487
  * @return {Number}
492
488
  * @memberof Utilities */
@@ -519,7 +515,7 @@ function sign(value) { return Math.sign(value); }
519
515
  * @memberof Utilities */
520
516
  function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
521
517
 
522
- /** Clamps the value beween max and min
518
+ /** Clamps the value between max and min
523
519
  * @param {Number} value
524
520
  * @param {Number} [min]
525
521
  * @param {Number} [max]
@@ -955,7 +951,7 @@ class Vector2
955
951
  return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
956
952
  }
957
953
 
958
- /** Set the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
954
+ /** Set the integer direction of this vector, corresponding to multiples of 90 degree rotation (0-3)
959
955
  * @param {Number} [direction]
960
956
  * @param {Number} [length] */
961
957
  setDirection(direction, length=1)
@@ -966,7 +962,7 @@ class Vector2
966
962
  direction%2 ? 0 : direction ? -length : length);
967
963
  }
968
964
 
969
- /** Returns the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
965
+ /** Returns the integer direction of this vector, corresponding to multiples of 90 degree rotation (0-3)
970
966
  * @return {Number} */
971
967
  direction()
972
968
  { return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
@@ -1351,7 +1347,7 @@ const MAGENTA = rgb(1,0,1);
1351
1347
  * a.set(3); // sets the timer to 3 seconds
1352
1348
  *
1353
1349
  * let b = new Timer(1); // creates a timer with 1 second left
1354
- * b.unset(); // unsets the timer
1350
+ * b.unset(); // unset the timer
1355
1351
  */
1356
1352
  class Timer
1357
1353
  {
@@ -1502,7 +1498,7 @@ let tileFixBleedScale = 0;
1502
1498
  * @memberof Settings */
1503
1499
  let enablePhysicsSolver = true;
1504
1500
 
1505
- /** Default object mass for collision calcuations (how heavy objects are)
1501
+ /** Default object mass for collision calculations (how heavy objects are)
1506
1502
  * @type {Number}
1507
1503
  * @default
1508
1504
  * @memberof Settings */
@@ -1713,7 +1709,7 @@ function setFontDefault(font) { fontDefault = font; }
1713
1709
  * @memberof Settings */
1714
1710
  function setShowSplashScreen(show) { showSplashScreen = show; }
1715
1711
 
1716
- /** Set to disalbe rendering, audio, and input for servers
1712
+ /** Set to disable rendering, audio, and input for servers
1717
1713
  * @param {Boolean} headless
1718
1714
  * @memberof Settings */
1719
1715
  function setHeadlessMode(headless) { headlessMode = headless; }
@@ -1743,7 +1739,7 @@ function setTileFixBleedScale(scale) { tileFixBleedScale = scale; }
1743
1739
  * @memberof Settings */
1744
1740
  function setEnablePhysicsSolver(enable) { enablePhysicsSolver = enable; }
1745
1741
 
1746
- /** Set default object mass for collison calcuations
1742
+ /** Set default object mass for collision calculations
1747
1743
  * @param {Number} mass
1748
1744
  * @memberof Settings */
1749
1745
  function setObjectDefaultMass(mass) { objectDefaultMass = mass; }
@@ -1813,7 +1809,7 @@ function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
1813
1809
  * @memberof Settings */
1814
1810
  function setTouchGamepadAnalog(analog) { touchGamepadAnalog = analog; }
1815
1811
 
1816
- /** Set size of virutal gamepad for touch devices in pixels
1812
+ /** Set size of virtual gamepad for touch devices in pixels
1817
1813
  * @param {Number} size
1818
1814
  * @memberof Settings */
1819
1815
  function setTouchGamepadSize(size) { touchGamepadSize = size; }
@@ -1840,7 +1836,7 @@ function setSoundVolume(volume)
1840
1836
  {
1841
1837
  soundVolume = volume;
1842
1838
  if (soundEnable && !headlessMode && audioGainNode)
1843
- audioGainNode.gain.value = volume; // update gain immediatly
1839
+ audioGainNode.gain.value = volume; // update gain immediately
1844
1840
  }
1845
1841
 
1846
1842
  /** Set default range where sound no longer plays
@@ -2044,7 +2040,7 @@ class EngineObject
2044
2040
  const oldPos = this.pos.copy();
2045
2041
  this.velocity.x *= this.damping;
2046
2042
  this.velocity.y *= this.damping;
2047
- if (this.mass) // dont apply gravity to static objects
2043
+ if (this.mass) // don't apply gravity to static objects
2048
2044
  this.velocity.y += gravity * this.gravityScale;
2049
2045
  this.pos.x += this.velocity.x;
2050
2046
  this.pos.y += this.velocity.y;
@@ -2053,7 +2049,7 @@ class EngineObject
2053
2049
  // physics sanity checks
2054
2050
  ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
2055
2051
  ASSERT(this.damping >= 0 && this.damping <= 1);
2056
- if (!enablePhysicsSolver || !this.mass) // dont do collision for static objects
2052
+ if (!enablePhysicsSolver || !this.mass) // don't do collision for static objects
2057
2053
  return;
2058
2054
 
2059
2055
  const wasMovingDown = this.velocity.y < 0;
@@ -2072,7 +2068,7 @@ class EngineObject
2072
2068
  const epsilon = .001; // necessary to push slightly outside of the collision
2073
2069
  for (const o of engineObjectsCollide)
2074
2070
  {
2075
- // non solid objects don't collide with eachother
2071
+ // non solid objects don't collide with each other
2076
2072
  if (!this.isSolid && !o.isSolid || o.destroyed || o.parent || o == this)
2077
2073
  continue;
2078
2074
 
@@ -2132,7 +2128,7 @@ class EngineObject
2132
2128
  const elastic1 = o.velocity.y * (o.mass - this.mass) / (this.mass + o.mass)
2133
2129
  + this.velocity.y * 2 * this.mass / (this.mass + o.mass);
2134
2130
 
2135
- // lerp betwen elastic or inelastic based on elasticity
2131
+ // lerp between elastic or inelastic based on elasticity
2136
2132
  this.velocity.y = lerp(elasticity, inelastic, elastic0);
2137
2133
  o.velocity.y = lerp(elasticity, inelastic, elastic1);
2138
2134
  }
@@ -2152,7 +2148,7 @@ class EngineObject
2152
2148
  const elastic1 = o.velocity.x * (o.mass - this.mass) / (this.mass + o.mass)
2153
2149
  + this.velocity.x * 2 * this.mass / (this.mass + o.mass);
2154
2150
 
2155
- // lerp betwen elastic or inelastic based on elasticity
2151
+ // lerp between elastic or inelastic based on elasticity
2156
2152
  this.velocity.x = lerp(elasticity, inelastic, elastic0);
2157
2153
  o.velocity.x = lerp(elasticity, inelastic, elastic1);
2158
2154
  }
@@ -2218,7 +2214,7 @@ class EngineObject
2218
2214
  if (this.destroyed)
2219
2215
  return;
2220
2216
 
2221
- // disconnect from parent and destroy chidren
2217
+ // disconnect from parent and destroy children
2222
2218
  this.destroyed = 1;
2223
2219
  this.parent && this.parent.removeChild(this);
2224
2220
  for (const child of this.children)
@@ -2243,7 +2239,7 @@ class EngineObject
2243
2239
 
2244
2240
  /** Called to check if a tile collision should be resolved
2245
2241
  * @param {Number} tileData - the value of the tile at the position
2246
- * @param {Vector2} pos - tile where the collision occured
2242
+ * @param {Vector2} pos - tile where the collision occurred
2247
2243
  * @return {Boolean} - true if the collision should be resolved */
2248
2244
  collideWithTile(tileData, pos) { return tileData > 0; }
2249
2245
 
@@ -2306,7 +2302,7 @@ class EngineObject
2306
2302
  this.collideRaycast = collideRaycast;
2307
2303
  }
2308
2304
 
2309
- /** Returns string containg info about this object for debugging
2305
+ /** Returns string containing info about this object for debugging
2310
2306
  * @return {String} */
2311
2307
  toString()
2312
2308
  {
@@ -2392,7 +2388,7 @@ let overlayContext;
2392
2388
  let mainCanvasSize = vec2();
2393
2389
 
2394
2390
  /** Array containing texture info for batch rendering system
2395
- * @type {Array}
2391
+ * @type {Array<TextureInfo>}
2396
2392
  * @memberof Draw */
2397
2393
  let textureInfos = [];
2398
2394
 
@@ -2431,11 +2427,13 @@ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
2431
2427
 
2432
2428
  // use pos as a tile index
2433
2429
  const textureInfo = textureInfos[textureIndex];
2434
- ASSERT(textureInfo, 'Texture not loaded');
2430
+ ASSERT(!!textureInfo, 'Texture not loaded');
2435
2431
  const sizePadded = size.add(vec2(padding*2));
2436
- const cols = textureInfo.size.x / sizePadded.x |0;
2437
2432
  if (typeof pos === 'number')
2438
- pos = vec2(pos%cols, pos/cols|0);
2433
+ {
2434
+ const cols = textureInfo.size.x / sizePadded.x |0;
2435
+ pos = cols>0 ? vec2(pos%cols, pos/cols|0) : vec2();
2436
+ }
2439
2437
  pos = vec2(pos.x*sizePadded.x+padding, pos.y*sizePadded.y+padding);
2440
2438
 
2441
2439
  // return a tile info object
@@ -2741,24 +2739,6 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
2741
2739
  context.restore();
2742
2740
  }
2743
2741
 
2744
- /** Enable normal or additive blend mode
2745
- * @param {Boolean} [additive]
2746
- * @param {Boolean} [useWebGL=glEnable]
2747
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
2748
- * @memberof Draw */
2749
- function setBlendMode(additive, useWebGL=glEnable, context)
2750
- {
2751
- ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
2752
- if (useWebGL)
2753
- glAdditive = additive;
2754
- else
2755
- {
2756
- if (!context)
2757
- context = mainContext;
2758
- context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
2759
- }
2760
- }
2761
-
2762
2742
  /** Draw text on main canvas in world space
2763
2743
  * Automatically splits new lines into rows
2764
2744
  * @param {String} text
@@ -2826,6 +2806,38 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
2826
2806
  });
2827
2807
  }
2828
2808
 
2809
+ /** Enable normal or additive blend mode
2810
+ * @param {Boolean} [additive]
2811
+ * @param {Boolean} [useWebGL=glEnable]
2812
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
2813
+ * @memberof Draw */
2814
+ function setBlendMode(additive, useWebGL=glEnable, context)
2815
+ {
2816
+ ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
2817
+ if (useWebGL)
2818
+ glAdditive = additive;
2819
+ else
2820
+ {
2821
+ if (!context)
2822
+ context = mainContext;
2823
+ context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
2824
+ }
2825
+ }
2826
+
2827
+ /** Combines all LittleJS canvases onto the main canvas and clears them
2828
+ * This is necessary for things like saving a screenshot
2829
+ * @memberof Draw */
2830
+ function combineCanvases()
2831
+ {
2832
+ // combine canvases
2833
+ glCopyToContext(mainContext, true);
2834
+ mainContext.drawImage(overlayCanvas, 0, 0);
2835
+
2836
+ // clear canvases
2837
+ glClearCanvas();
2838
+ overlayCanvas.width |= 0;
2839
+ }
2840
+
2829
2841
  ///////////////////////////////////////////////////////////////////////////////
2830
2842
 
2831
2843
  let engineFontImage;
@@ -2837,7 +2849,7 @@ let engineFontImage;
2837
2849
  * - You can also use fonts from the main tile sheet
2838
2850
  * @example
2839
2851
  * // use built in font
2840
- * const font = new ImageFont;
2852
+ * const font = new FontImage;
2841
2853
  *
2842
2854
  * // draw text
2843
2855
  * font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
@@ -2912,14 +2924,14 @@ class FontImage
2912
2924
  }
2913
2925
 
2914
2926
  ///////////////////////////////////////////////////////////////////////////////
2915
- // Fullscreen mode
2927
+ // Display functions
2916
2928
 
2917
2929
  /** Returns true if fullscreen mode is active
2918
2930
  * @return {Boolean}
2919
2931
  * @memberof Draw */
2920
2932
  function isFullscreen() { return !!document.fullscreenElement; }
2921
2933
 
2922
- /** Toggle fullsceen mode
2934
+ /** Toggle fullscreen mode
2923
2935
  * @memberof Draw */
2924
2936
  function toggleFullscreen()
2925
2937
  {
@@ -2931,6 +2943,15 @@ function toggleFullscreen()
2931
2943
  }
2932
2944
  else if (rootElement.requestFullscreen)
2933
2945
  rootElement.requestFullscreen();
2946
+ }
2947
+
2948
+ /** Set the cursor style
2949
+ * @param {String} cursorStyle - CSS cursor style (auto, none, crosshair, etc)
2950
+ * @memberof Draw */
2951
+ function setCursor(cursorStyle = 'auto')
2952
+ {
2953
+ const rootElement = mainCanvas.parentElement;
2954
+ rootElement.style.cursor = cursorStyle;
2934
2955
  }
2935
2956
  /**
2936
2957
  * LittleJS Input System
@@ -3275,7 +3296,7 @@ function vibrateStop() { vibrate(0); }
3275
3296
 
3276
3297
  /** True if a touch device has been detected
3277
3298
  * @memberof Input */
3278
- const isTouchDevice = window.ontouchstart !== undefined;
3299
+ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
3279
3300
 
3280
3301
  // touch gamepad internal variables
3281
3302
  let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
@@ -3473,7 +3494,7 @@ function audioInit()
3473
3494
  {
3474
3495
  if (!soundEnable || headlessMode) return;
3475
3496
 
3476
- // (createGain is more widely spported then GainNode construtor)
3497
+ // (createGain is more widely supported then GainNode constructor)
3477
3498
  audioGainNode = audioContext.createGain();
3478
3499
  audioGainNode.connect(audioContext.destination);
3479
3500
  audioGainNode.gain.value = soundVolume; // set starting value
@@ -3823,7 +3844,7 @@ const zzfxR = 44100;
3823
3844
  * @param {Number} [sustain] - Sustain time, how long sound holds (seconds)
3824
3845
  * @param {Number} [release] - Release time, how fast sound fades out (seconds)
3825
3846
  * @param {Number} [shape] - Shape of the sound wave
3826
- * @param {Number} [shapeCurve] - Squarenes of wave (0=square, 1=normal, 2=pointy)
3847
+ * @param {Number} [shapeCurve] - Squareness of wave (0=square, 1=normal, 2=pointy)
3827
3848
  * @param {Number} [slide] - How much to slide frequency (kHz/s)
3828
3849
  * @param {Number} [deltaSlide] - How much to change slide (kHz/s/s)
3829
3850
  * @param {Number} [pitchJump] - Frequency of pitch jump (Hz)
@@ -3835,7 +3856,7 @@ const zzfxR = 44100;
3835
3856
  * @param {Number} [delay] - Overlap sound with itself for reverb and flanger effects (seconds)
3836
3857
  * @param {Number} [sustainVolume] - Volume level for sustain (percent)
3837
3858
  * @param {Number} [decay] - Decay time, how long to reach sustain after attack (seconds)
3838
- * @param {Number} [tremolo] - Trembling effect, rate controlled by repeat time (precent)
3859
+ * @param {Number} [tremolo] - Trembling effect, rate controlled by repeat time (percent)
3839
3860
  * @param {Number} [filter] - Filter cutoff frequency, positive for HPF, negative for LPF (Hz)
3840
3861
  * @return {Array} - Array of audio samples
3841
3862
  * @memberof Audio
@@ -3939,7 +3960,7 @@ function zzfxG
3939
3960
  // ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
3940
3961
 
3941
3962
  /** Generate samples for a ZzFM song with given parameters
3942
- * @param {Array} instruments - Array of ZzFX sound paramaters
3963
+ * @param {Array} instruments - Array of ZzFX sound parameters
3943
3964
  * @param {Array} patterns - Array of pattern data
3944
3965
  * @param {Array} sequence - Array of pattern indexes
3945
3966
  * @param {Number} [BPM] - Playback speed of the song in BPM
@@ -4342,7 +4363,7 @@ class TileLayer extends EngineObject
4342
4363
 
4343
4364
  /** Draw the tile at a given position in the tile grid
4344
4365
  * This can be used to clear out tiles when they are destroyed
4345
- * Tiles can also be redrawn if isinde a redrawStart/End block
4366
+ * Tiles can also be redrawn if inside a redrawStart/End block
4346
4367
  * @param {Vector2} layerPos
4347
4368
  * @param {Boolean} [clear] - should the old tile be cleared out
4348
4369
  */
@@ -4367,7 +4388,7 @@ class TileLayer extends EngineObject
4367
4388
  }
4368
4389
  }
4369
4390
 
4370
- /** Draw directly to the 2D canvas in world space (bipass webgl)
4391
+ /** Draw directly to the 2D canvas in world space (bypass webgl)
4371
4392
  * @param {Vector2} pos
4372
4393
  * @param {Vector2} size
4373
4394
  * @param {Number} angle
@@ -4437,7 +4458,7 @@ class TileLayer extends EngineObject
4437
4458
  * let pos = vec2(2,3);
4438
4459
  * let particleEmitter = new ParticleEmitter
4439
4460
  * (
4440
- * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
4461
+ * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emitCone
4441
4462
  * tile(0, 16), // tileInfo
4442
4463
  * rgb(1,1,1), rgb(0,0,0), // colorStartA, colorStartB
4443
4464
  * rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
@@ -4472,7 +4493,7 @@ class ParticleEmitter extends EngineObject
4472
4493
  * @param {Number} [fadeRate] - How quick to fade particles at start/end in percent of life
4473
4494
  * @param {Number} [randomness] - Apply extra randomness percent
4474
4495
  * @param {Boolean} [collideTiles] - Do particles collide against tiles
4475
- * @param {Boolean} [additive] - Should particles use addtive blend
4496
+ * @param {Boolean} [additive] - Should particles use additive blend
4476
4497
  * @param {Boolean} [randomColorLinear] - Should color be randomized linearly or across each component
4477
4498
  * @param {Number} [renderOrder] - Render order for particles (additive is above other stuff by default)
4478
4499
  * @param {Boolean} [localSpace] - Should it be in local space of emitter (world space is default)
@@ -4557,11 +4578,11 @@ class ParticleEmitter extends EngineObject
4557
4578
  this.randomness = randomness;
4558
4579
  /** @property {Boolean} - Do particles collide against tiles */
4559
4580
  this.collideTiles = collideTiles;
4560
- /** @property {Boolean} - Should particles use addtive blend */
4581
+ /** @property {Boolean} - Should particles use additive blend */
4561
4582
  this.additive = additive;
4562
4583
  /** @property {Boolean} - Should it be in local space of emitter */
4563
4584
  this.localSpace = localSpace;
4564
- /** @property {Number} - If non zero the partile is drawn as a trail, stretched in the drection of velocity */
4585
+ /** @property {Number} - If non zero the particle is drawn as a trail, stretched in the direction of velocity */
4565
4586
  this.trailScale = 0;
4566
4587
  /** @property {Function} - Callback when particle is destroyed */
4567
4588
  this.particleDestroyCallback = undefined;
@@ -4610,7 +4631,7 @@ class ParticleEmitter extends EngineObject
4610
4631
  angle += this.angle;
4611
4632
  }
4612
4633
 
4613
- // randomness scales each paremeter by a percentage
4634
+ // randomness scales each parameter by a percentage
4614
4635
  const randomness = this.randomness;
4615
4636
  const randomizeScale = (v)=> v + v*rand(randomness, -randomness);
4616
4637
 
@@ -4639,7 +4660,7 @@ class ParticleEmitter extends EngineObject
4639
4660
  particle.renderOrder = this.renderOrder;
4640
4661
  particle.mirror = !!randInt(2);
4641
4662
 
4642
- // call particle create callaback
4663
+ // call particle create callback
4643
4664
  this.particleCreateCallback && this.particleCreateCallback(particle);
4644
4665
 
4645
4666
  // return the newly created particle
@@ -4959,7 +4980,7 @@ let glCanvas;
4959
4980
  * @memberof WebGL */
4960
4981
  let glContext;
4961
4982
 
4962
- /** Shoule webgl be setup with antialiasing, must be set before calling engineInit
4983
+ /** Should webgl be setup with anti-aliasing? must be set before calling engineInit
4963
4984
  * @type {Boolean}
4964
4985
  * @memberof WebGL */
4965
4986
  let glAntialias = true;
@@ -4967,9 +4988,15 @@ let glAntialias = true;
4967
4988
  // WebGL internal variables not exposed to documentation
4968
4989
  let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glInstanceCount, glAdditive, glBatchAdditive;
4969
4990
 
4991
+ // WebGL internal constants
4992
+ const gl_MAX_INSTANCES = 1e4;
4993
+ const gl_INDICES_PER_INSTANCE = 11;
4994
+ const gl_INSTANCE_BYTE_STRIDE = gl_INDICES_PER_INSTANCE * 4;
4995
+ const gl_INSTANCE_BUFFER_SIZE = gl_MAX_INSTANCES * gl_INSTANCE_BYTE_STRIDE;
4996
+
4970
4997
  ///////////////////////////////////////////////////////////////////////////////
4971
4998
 
4972
- // Initalize WebGL, called automatically by the engine
4999
+ // Initialize WebGL, called automatically by the engine
4973
5000
  function glInit()
4974
5001
  {
4975
5002
  if (!glEnable || headlessMode) return;
@@ -5019,8 +5046,8 @@ function glInit()
5019
5046
 
5020
5047
  // create the geometry buffer, triangle strip square
5021
5048
  const geometry = new Float32Array([glInstanceCount=0,0,1,0,0,1,1,1]);
5022
- glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
5023
- glContext.bufferData(gl_ARRAY_BUFFER, geometry, gl_STATIC_DRAW);
5049
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
5050
+ glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
5024
5051
  }
5025
5052
 
5026
5053
  // Setup render each frame, called automatically by engine
@@ -5028,15 +5055,12 @@ function glPreRender()
5028
5055
  {
5029
5056
  if (!glEnable || headlessMode) return;
5030
5057
 
5031
- // clear and set to same size as main canvas
5032
- glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
5033
- glContext.clear(gl_COLOR_BUFFER_BIT);
5034
-
5035
- // set up the shader
5058
+ // set up the shader and canvas
5059
+ glClearCanvas();
5036
5060
  glContext.useProgram(glShader);
5037
- glContext.activeTexture(gl_TEXTURE0);
5061
+ glContext.activeTexture(glContext.TEXTURE0);
5038
5062
  if (textureInfos[0])
5039
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
5063
+ glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
5040
5064
 
5041
5065
  // set vertex attributes
5042
5066
  let offset = glAdditive = glBatchAdditive = 0;
@@ -5051,15 +5075,15 @@ function glPreRender()
5051
5075
  glContext.vertexAttribDivisor(location, divisor);
5052
5076
  offset += size*typeSize;
5053
5077
  }
5054
- glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
5055
- initVertexAttribArray('g', gl_FLOAT, 0, 2); // geometry
5056
- glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
5057
- glContext.bufferData(gl_ARRAY_BUFFER, gl_INSTANCE_BUFFER_SIZE, gl_DYNAMIC_DRAW);
5058
- initVertexAttribArray('p', gl_FLOAT, 4, 4); // position & size
5059
- initVertexAttribArray('u', gl_FLOAT, 4, 4); // texture coords
5060
- initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4); // color
5061
- initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4); // additiveColor
5062
- initVertexAttribArray('r', gl_FLOAT, 4, 1); // rotation
5078
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
5079
+ initVertexAttribArray('g', glContext.FLOAT, 0, 2); // geometry
5080
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
5081
+ glContext.bufferData(glContext.ARRAY_BUFFER, gl_INSTANCE_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
5082
+ initVertexAttribArray('p', glContext.FLOAT, 4, 4); // position & size
5083
+ initVertexAttribArray('u', glContext.FLOAT, 4, 4); // texture coords
5084
+ initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
5085
+ initVertexAttribArray('a', glContext.UNSIGNED_BYTE, 1, 4); // additiveColor
5086
+ initVertexAttribArray('r', glContext.FLOAT, 4, 1); // rotation
5063
5087
 
5064
5088
  // build the transform matrix
5065
5089
  const s = vec2(2*cameraScale).divide(mainCanvasSize);
@@ -5074,6 +5098,15 @@ function glPreRender()
5074
5098
  );
5075
5099
  }
5076
5100
 
5101
+ /** Clear the canvas and setup the viewport
5102
+ * @memberof WebGL */
5103
+ function glClearCanvas()
5104
+ {
5105
+ // clear and set to same size as main canvas
5106
+ glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
5107
+ glContext.clear(glContext.COLOR_BUFFER_BIT);
5108
+ }
5109
+
5077
5110
  /** Set the WebGl texture, called automatically if using multiple textures
5078
5111
  * - This may also flush the gl buffer resulting in more draw calls and worse performance
5079
5112
  * @param {WebGLTexture} texture
@@ -5085,7 +5118,7 @@ function glSetTexture(texture)
5085
5118
  return;
5086
5119
 
5087
5120
  glFlush();
5088
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = texture);
5121
+ glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture = texture);
5089
5122
  }
5090
5123
 
5091
5124
  /** Compile WebGL shader of the given type, will throw errors if in debug mode
@@ -5101,7 +5134,7 @@ function glCompileShader(source, type)
5101
5134
  glContext.compileShader(shader);
5102
5135
 
5103
5136
  // check for errors
5104
- if (debug && !glContext.getShaderParameter(shader, gl_COMPILE_STATUS))
5137
+ if (debug && !glContext.getShaderParameter(shader, glContext.COMPILE_STATUS))
5105
5138
  throw glContext.getShaderInfoLog(shader);
5106
5139
  return shader;
5107
5140
  }
@@ -5115,12 +5148,12 @@ function glCreateProgram(vsSource, fsSource)
5115
5148
  {
5116
5149
  // build the program
5117
5150
  const program = glContext.createProgram();
5118
- glContext.attachShader(program, glCompileShader(vsSource, gl_VERTEX_SHADER));
5119
- glContext.attachShader(program, glCompileShader(fsSource, gl_FRAGMENT_SHADER));
5151
+ glContext.attachShader(program, glCompileShader(vsSource, glContext.VERTEX_SHADER));
5152
+ glContext.attachShader(program, glCompileShader(fsSource, glContext.FRAGMENT_SHADER));
5120
5153
  glContext.linkProgram(program);
5121
5154
 
5122
5155
  // check for errors
5123
- if (debug && !glContext.getProgramParameter(program, gl_LINK_STATUS))
5156
+ if (debug && !glContext.getProgramParameter(program, glContext.LINK_STATUS))
5124
5157
  throw glContext.getProgramInfoLog(program);
5125
5158
  return program;
5126
5159
  }
@@ -5133,20 +5166,20 @@ function glCreateTexture(image)
5133
5166
  {
5134
5167
  // build the texture
5135
5168
  const texture = glContext.createTexture();
5136
- glContext.bindTexture(gl_TEXTURE_2D, texture);
5169
+ glContext.bindTexture(glContext.TEXTURE_2D, texture);
5137
5170
  if (image && image.width)
5138
- glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
5171
+ glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
5139
5172
  else
5140
5173
  {
5141
5174
  // create a white texture
5142
5175
  const whitePixel = new Uint8Array([255, 255, 255, 255]);
5143
- glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, 1, 1, 0, gl_RGBA, gl_UNSIGNED_BYTE, whitePixel);
5176
+ glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
5144
5177
  }
5145
5178
 
5146
5179
  // use point filtering for pixelated rendering
5147
- const filter = tilesPixelated ? gl_NEAREST : gl_LINEAR;
5148
- glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
5149
- glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
5180
+ const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
5181
+ glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
5182
+ glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, filter);
5150
5183
  return texture;
5151
5184
  }
5152
5185
 
@@ -5156,20 +5189,20 @@ function glFlush()
5156
5189
  {
5157
5190
  if (!glInstanceCount) return;
5158
5191
 
5159
- const destBlend = glBatchAdditive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
5160
- glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
5161
- glContext.enable(gl_BLEND);
5192
+ const destBlend = glBatchAdditive ? glContext.ONE : glContext.ONE_MINUS_SRC_ALPHA;
5193
+ glContext.blendFuncSeparate(glContext.SRC_ALPHA, destBlend, glContext.ONE, destBlend);
5194
+ glContext.enable(glContext.BLEND);
5162
5195
 
5163
5196
  // draw all the sprites in the batch and reset the buffer
5164
- glContext.bufferSubData(gl_ARRAY_BUFFER, 0, glPositionData);
5165
- glContext.drawArraysInstanced(gl_TRIANGLE_STRIP, 0, 4, glInstanceCount);
5197
+ glContext.bufferSubData(glContext.ARRAY_BUFFER, 0, glPositionData);
5198
+ glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP, 0, 4, glInstanceCount);
5166
5199
  if (showWatermark)
5167
5200
  drawCount += glInstanceCount;
5168
5201
  glInstanceCount = 0;
5169
5202
  glBatchAdditive = glAdditive;
5170
5203
  }
5171
5204
 
5172
- /** Draw any sprites still in the buffer, copy to main canvas and clear
5205
+ /** Draw any sprites still in the buffer and copy to main canvas
5173
5206
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
5174
5207
  * @param {Boolean} [forceDraw]
5175
5208
  * @memberof WebGL */
@@ -5184,7 +5217,7 @@ function glCopyToContext(context, forceDraw=false)
5184
5217
  context.drawImage(glCanvas, 0, 0);
5185
5218
  }
5186
5219
 
5187
- /** Set antialiasing for webgl canvas
5220
+ /** Set anti-aliasing for webgl canvas
5188
5221
  * @param {Boolean} [antialias]
5189
5222
  * @memberof WebGL */
5190
5223
  function glSetAntialias(antialias=true)
@@ -5214,7 +5247,7 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
5214
5247
  if (glInstanceCount >= gl_MAX_INSTANCES || glBatchAdditive != glAdditive)
5215
5248
  glFlush();
5216
5249
 
5217
- let offset = glInstanceCount * gl_INDICIES_PER_INSTANCE;
5250
+ let offset = glInstanceCount++ * gl_INDICES_PER_INSTANCE;
5218
5251
  glPositionData[offset++] = x;
5219
5252
  glPositionData[offset++] = y;
5220
5253
  glPositionData[offset++] = sizeX;
@@ -5226,41 +5259,7 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
5226
5259
  glColorData[offset++] = rgba;
5227
5260
  glColorData[offset++] = rgbaAdditive;
5228
5261
  glPositionData[offset++] = angle;
5229
- glInstanceCount++;
5230
- }
5231
-
5232
- ///////////////////////////////////////////////////////////////////////////////
5233
- // store gl constants as integers so their name doesn't use space in minifed
5234
- const
5235
- gl_ONE = 1,
5236
- gl_TRIANGLE_STRIP = 5,
5237
- gl_SRC_ALPHA = 770,
5238
- gl_ONE_MINUS_SRC_ALPHA = 771,
5239
- gl_BLEND = 3042,
5240
- gl_TEXTURE_2D = 3553,
5241
- gl_UNSIGNED_BYTE = 5121,
5242
- gl_FLOAT = 5126,
5243
- gl_RGBA = 6408,
5244
- gl_NEAREST = 9728,
5245
- gl_LINEAR = 9729,
5246
- gl_TEXTURE_MAG_FILTER = 10240,
5247
- gl_TEXTURE_MIN_FILTER = 10241,
5248
- gl_COLOR_BUFFER_BIT = 16384,
5249
- gl_TEXTURE0 = 33984,
5250
- gl_ARRAY_BUFFER = 34962,
5251
- gl_STATIC_DRAW = 35044,
5252
- gl_DYNAMIC_DRAW = 35048,
5253
- gl_FRAGMENT_SHADER = 35632,
5254
- gl_VERTEX_SHADER = 35633,
5255
- gl_COMPILE_STATUS = 35713,
5256
- gl_LINK_STATUS = 35714,
5257
- gl_UNPACK_FLIP_Y_WEBGL = 37440,
5258
-
5259
- // constants for batch rendering
5260
- gl_INDICIES_PER_INSTANCE = 11,
5261
- gl_MAX_INSTANCES = 1e4,
5262
- gl_INSTANCE_BYTE_STRIDE = gl_INDICIES_PER_INSTANCE * 4, // 11 * 4
5263
- gl_INSTANCE_BUFFER_SIZE = gl_MAX_INSTANCES * gl_INSTANCE_BYTE_STRIDE;
5262
+ }
5264
5263
  /**
5265
5264
  * LittleJS - The Tiny Fast JavaScript Game Engine
5266
5265
  * MIT License - Copyright 2021 Frank Force
@@ -5293,7 +5292,7 @@ const engineName = 'LittleJS';
5293
5292
  * @type {String}
5294
5293
  * @default
5295
5294
  * @memberof Engine */
5296
- const engineVersion = '1.11.1';
5295
+ const engineVersion = '1.11.4';
5297
5296
 
5298
5297
  /** Frames per second to update
5299
5298
  * @type {Number}
@@ -5376,6 +5375,7 @@ function engineAddPlugin(updateFunction, renderFunction)
5376
5375
  * @memberof Engine */
5377
5376
  function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
5378
5377
  {
5378
+ ASSERT(!mainContext, 'engine already initialized');
5379
5379
  ASSERT(Array.isArray(imageSources), 'pass in images as array');
5380
5380
 
5381
5381
  // Called automatically by engine to setup render system
@@ -5542,7 +5542,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5542
5542
  (canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
5543
5543
  'user-select:none;' + // prevent hold to select
5544
5544
  '-webkit-user-select:none;' + // compatibility for ios
5545
- (!touchInputEnable ? '' : // no touch css setttings
5545
+ (!touchInputEnable ? '' : // no touch css settings
5546
5546
  'touch-action:none;' + // prevent mobile pinch to resize
5547
5547
  '-webkit-touch-callout:none');// compatibility for ios
5548
5548
  rootElement.style.cssText = styleRoot;
@@ -5571,6 +5571,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5571
5571
  new Promise(resolve =>
5572
5572
  {
5573
5573
  const image = new Image;
5574
+ image.crossOrigin = 'anonymous';
5574
5575
  image.onerror = image.onload = ()=>
5575
5576
  {
5576
5577
  textureInfos[textureIndex] = new TextureInfo(image);
@@ -5615,7 +5616,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5615
5616
  * @memberof Engine */
5616
5617
  function engineObjectsUpdate()
5617
5618
  {
5618
- // get list of solid objects for physics optimzation
5619
+ // get list of solid objects for physics optimization
5619
5620
  engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
5620
5621
 
5621
5622
  // recursive object update
@@ -6077,14 +6078,16 @@ export {
6077
6078
  drawEllipse,
6078
6079
  drawCircle,
6079
6080
  drawCanvas2D,
6080
- setBlendMode,
6081
6081
  drawText,
6082
6082
  drawTextOverlay,
6083
6083
  drawTextScreen,
6084
+ setBlendMode,
6085
+ combineCanvases,
6084
6086
  engineFontImage,
6085
6087
  FontImage,
6086
6088
  isFullscreen,
6087
6089
  toggleFullscreen,
6090
+ setCursor,
6088
6091
  getCameraSize,
6089
6092
 
6090
6093
  // WebGL
@@ -6098,6 +6101,7 @@ export {
6098
6101
  glFlush,
6099
6102
  glSetTexture,
6100
6103
  glSetAntialias,
6104
+ glClearCanvas,
6101
6105
  glAntialias,
6102
6106
  glShader,
6103
6107
  glActiveTexture,