littlejsengine 1.11.2 → 1.11.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 (73) hide show
  1. package/.vscode/launch.json +14 -0
  2. package/README.md +16 -18
  3. package/dist/littlejs.d.ts +41 -30
  4. package/dist/littlejs.esm.js +165 -143
  5. package/dist/littlejs.esm.min.js +1 -1
  6. package/dist/littlejs.js +161 -142
  7. package/dist/littlejs.min.js +1 -1
  8. package/dist/littlejs.release.js +157 -135
  9. package/examples/box2d/gameObjects.js +4 -4
  10. package/examples/box2d/scenes.js +2 -2
  11. package/examples/breakout/game.js +1 -1
  12. package/examples/breakout/gameObjects.js +28 -29
  13. package/examples/breakout/index.html +1 -1
  14. package/examples/breakoutTutorial/game.js +1 -1
  15. package/examples/index.html +1 -7
  16. package/examples/logo.png +0 -0
  17. package/examples/module/game.js +1 -1
  18. package/examples/platformer/game.js +2 -2
  19. package/examples/platformer/gameCharacter.js +5 -5
  20. package/examples/platformer/gameEffects.js +4 -4
  21. package/examples/platformer/gameObjects.js +2 -2
  22. package/examples/puzzle/game.js +3 -3
  23. package/examples/starter/game.js +11 -45
  24. package/examples/starter/tiles.png +0 -0
  25. package/jsconfig.json +1 -0
  26. package/package.json +1 -1
  27. package/plugins/box2d.js +10 -10
  28. package/plugins/newgrounds.js +1 -1
  29. package/plugins/postProcess.js +8 -8
  30. package/shortExamples/base.html +36 -0
  31. package/shortExamples/code/blending.js +12 -0
  32. package/shortExamples/code/helloWorld.js +7 -0
  33. package/shortExamples/code/particles.js +28 -0
  34. package/shortExamples/code/playSound.js +36 -0
  35. package/shortExamples/code/pong.js +40 -0
  36. package/shortExamples/code/shapes.js +24 -0
  37. package/shortExamples/code/spriteAtlas.js +27 -0
  38. package/shortExamples/code/systemFont.js +14 -0
  39. package/shortExamples/code/texture.js +12 -0
  40. package/shortExamples/code/tileLayer.js +50 -0
  41. package/shortExamples/index.html +242 -0
  42. package/shortExamples/tiles.png +0 -0
  43. package/src/engine.js +17 -3
  44. package/src/engineAudio.js +4 -4
  45. package/src/engineBuild.js +2 -1
  46. package/src/engineDebug.js +4 -7
  47. package/src/engineDraw.js +50 -25
  48. package/src/engineExport.js +4 -1
  49. package/src/engineInput.js +1 -1
  50. package/src/engineObject.js +8 -8
  51. package/src/engineParticles.js +7 -7
  52. package/src/engineSettings.js +5 -5
  53. package/src/engineTileLayer.js +6 -2
  54. package/src/engineUtilities.js +5 -5
  55. package/src/engineWebGL.js +52 -74
  56. package/examples/electron/build.js +0 -107
  57. package/examples/electron/electron.js +0 -43
  58. package/examples/electron/game.js +0 -131
  59. package/examples/electron/index.html +0 -13
  60. package/examples/electron/package.json +0 -22
  61. package/examples/electron/tiles.png +0 -0
  62. package/examples/js13k/build.bat +0 -2
  63. package/examples/js13k/build.js +0 -131
  64. package/examples/js13k/game.js +0 -118
  65. package/examples/js13k/index.html +0 -21
  66. package/examples/js13k/tiles.png +0 -0
  67. package/examples/typescript/build.bat +0 -5
  68. package/examples/typescript/build.js +0 -33
  69. package/examples/typescript/game.js +0 -102
  70. package/examples/typescript/game.ts +0 -134
  71. package/examples/typescript/index.html +0 -10
  72. package/examples/typescript/tiles.png +0 -0
  73. package/examples/typescript/tsconfig.json +0 -8
package/dist/littlejs.js CHANGED
@@ -1,4 +1,5 @@
1
- // LittleJS - MIT License - Copyright 2021 Frank Force
1
+ // LittleJS Engine - MIT License - Copyright 2021 Frank Force
2
+ // https://github.com/KilledByAPixel/LittleJS
2
3
 
3
4
  'use strict';
4
5
 
@@ -20,7 +21,7 @@
20
21
  * @memberof Debug */
21
22
  const debug = true;
22
23
 
23
- /** True if asserts are enaled
24
+ /** True if asserts are enabled
24
25
  * @type {Boolean}
25
26
  * @default
26
27
  * @memberof Debug */
@@ -204,6 +205,7 @@ function debugShowErrors()
204
205
  const showError = (message)=>
205
206
  {
206
207
  // replace entire page with error message
208
+ document.body.style.display = '';
207
209
  document.body.style.backgroundColor = '#111';
208
210
  document.body.innerHTML = `<pre style=color:#f00;font-size:50px>` + message;
209
211
  }
@@ -248,12 +250,8 @@ function debugRender()
248
250
 
249
251
  if (debugTakeScreenshot)
250
252
  {
251
- // composite canvas
252
- glCopyToContext(mainContext, true);
253
- mainContext.drawImage(overlayCanvas, 0, 0);
254
- overlayCanvas.width |= 0;
255
-
256
- // remove alpha and save
253
+ // combine canvases, remove alpha and save
254
+ combineCanvases();
257
255
  const w = mainCanvas.width, h = mainCanvas.height;
258
256
  overlayContext.fillRect(0,0,w,h);
259
257
  overlayContext.drawImage(mainCanvas, 0, 0);
@@ -486,7 +484,7 @@ function debugRender()
486
484
  * @memberof Utilities */
487
485
  const PI = Math.PI;
488
486
 
489
- /** Returns absoulte value of value passed in
487
+ /** Returns absolute value of value passed in
490
488
  * @param {Number} value
491
489
  * @return {Number}
492
490
  * @memberof Utilities */
@@ -519,7 +517,7 @@ function sign(value) { return Math.sign(value); }
519
517
  * @memberof Utilities */
520
518
  function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
521
519
 
522
- /** Clamps the value beween max and min
520
+ /** Clamps the value between max and min
523
521
  * @param {Number} value
524
522
  * @param {Number} [min]
525
523
  * @param {Number} [max]
@@ -955,7 +953,7 @@ class Vector2
955
953
  return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
956
954
  }
957
955
 
958
- /** Set the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
956
+ /** Set the integer direction of this vector, corresponding to multiples of 90 degree rotation (0-3)
959
957
  * @param {Number} [direction]
960
958
  * @param {Number} [length] */
961
959
  setDirection(direction, length=1)
@@ -966,7 +964,7 @@ class Vector2
966
964
  direction%2 ? 0 : direction ? -length : length);
967
965
  }
968
966
 
969
- /** Returns the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
967
+ /** Returns the integer direction of this vector, corresponding to multiples of 90 degree rotation (0-3)
970
968
  * @return {Number} */
971
969
  direction()
972
970
  { return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
@@ -1351,7 +1349,7 @@ const MAGENTA = rgb(1,0,1);
1351
1349
  * a.set(3); // sets the timer to 3 seconds
1352
1350
  *
1353
1351
  * let b = new Timer(1); // creates a timer with 1 second left
1354
- * b.unset(); // unsets the timer
1352
+ * b.unset(); // unset the timer
1355
1353
  */
1356
1354
  class Timer
1357
1355
  {
@@ -1502,7 +1500,7 @@ let tileFixBleedScale = 0;
1502
1500
  * @memberof Settings */
1503
1501
  let enablePhysicsSolver = true;
1504
1502
 
1505
- /** Default object mass for collision calcuations (how heavy objects are)
1503
+ /** Default object mass for collision calculations (how heavy objects are)
1506
1504
  * @type {Number}
1507
1505
  * @default
1508
1506
  * @memberof Settings */
@@ -1713,7 +1711,7 @@ function setFontDefault(font) { fontDefault = font; }
1713
1711
  * @memberof Settings */
1714
1712
  function setShowSplashScreen(show) { showSplashScreen = show; }
1715
1713
 
1716
- /** Set to disalbe rendering, audio, and input for servers
1714
+ /** Set to disable rendering, audio, and input for servers
1717
1715
  * @param {Boolean} headless
1718
1716
  * @memberof Settings */
1719
1717
  function setHeadlessMode(headless) { headlessMode = headless; }
@@ -1743,7 +1741,7 @@ function setTileFixBleedScale(scale) { tileFixBleedScale = scale; }
1743
1741
  * @memberof Settings */
1744
1742
  function setEnablePhysicsSolver(enable) { enablePhysicsSolver = enable; }
1745
1743
 
1746
- /** Set default object mass for collison calcuations
1744
+ /** Set default object mass for collision calculations
1747
1745
  * @param {Number} mass
1748
1746
  * @memberof Settings */
1749
1747
  function setObjectDefaultMass(mass) { objectDefaultMass = mass; }
@@ -1813,7 +1811,7 @@ function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
1813
1811
  * @memberof Settings */
1814
1812
  function setTouchGamepadAnalog(analog) { touchGamepadAnalog = analog; }
1815
1813
 
1816
- /** Set size of virutal gamepad for touch devices in pixels
1814
+ /** Set size of virtual gamepad for touch devices in pixels
1817
1815
  * @param {Number} size
1818
1816
  * @memberof Settings */
1819
1817
  function setTouchGamepadSize(size) { touchGamepadSize = size; }
@@ -1840,7 +1838,7 @@ function setSoundVolume(volume)
1840
1838
  {
1841
1839
  soundVolume = volume;
1842
1840
  if (soundEnable && !headlessMode && audioGainNode)
1843
- audioGainNode.gain.value = volume; // update gain immediatly
1841
+ audioGainNode.gain.value = volume; // update gain immediately
1844
1842
  }
1845
1843
 
1846
1844
  /** Set default range where sound no longer plays
@@ -2044,7 +2042,7 @@ class EngineObject
2044
2042
  const oldPos = this.pos.copy();
2045
2043
  this.velocity.x *= this.damping;
2046
2044
  this.velocity.y *= this.damping;
2047
- if (this.mass) // dont apply gravity to static objects
2045
+ if (this.mass) // don't apply gravity to static objects
2048
2046
  this.velocity.y += gravity * this.gravityScale;
2049
2047
  this.pos.x += this.velocity.x;
2050
2048
  this.pos.y += this.velocity.y;
@@ -2053,7 +2051,7 @@ class EngineObject
2053
2051
  // physics sanity checks
2054
2052
  ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
2055
2053
  ASSERT(this.damping >= 0 && this.damping <= 1);
2056
- if (!enablePhysicsSolver || !this.mass) // dont do collision for static objects
2054
+ if (!enablePhysicsSolver || !this.mass) // don't do collision for static objects
2057
2055
  return;
2058
2056
 
2059
2057
  const wasMovingDown = this.velocity.y < 0;
@@ -2072,7 +2070,7 @@ class EngineObject
2072
2070
  const epsilon = .001; // necessary to push slightly outside of the collision
2073
2071
  for (const o of engineObjectsCollide)
2074
2072
  {
2075
- // non solid objects don't collide with eachother
2073
+ // non solid objects don't collide with each other
2076
2074
  if (!this.isSolid && !o.isSolid || o.destroyed || o.parent || o == this)
2077
2075
  continue;
2078
2076
 
@@ -2132,7 +2130,7 @@ class EngineObject
2132
2130
  const elastic1 = o.velocity.y * (o.mass - this.mass) / (this.mass + o.mass)
2133
2131
  + this.velocity.y * 2 * this.mass / (this.mass + o.mass);
2134
2132
 
2135
- // lerp betwen elastic or inelastic based on elasticity
2133
+ // lerp between elastic or inelastic based on elasticity
2136
2134
  this.velocity.y = lerp(elasticity, inelastic, elastic0);
2137
2135
  o.velocity.y = lerp(elasticity, inelastic, elastic1);
2138
2136
  }
@@ -2152,7 +2150,7 @@ class EngineObject
2152
2150
  const elastic1 = o.velocity.x * (o.mass - this.mass) / (this.mass + o.mass)
2153
2151
  + this.velocity.x * 2 * this.mass / (this.mass + o.mass);
2154
2152
 
2155
- // lerp betwen elastic or inelastic based on elasticity
2153
+ // lerp between elastic or inelastic based on elasticity
2156
2154
  this.velocity.x = lerp(elasticity, inelastic, elastic0);
2157
2155
  o.velocity.x = lerp(elasticity, inelastic, elastic1);
2158
2156
  }
@@ -2218,7 +2216,7 @@ class EngineObject
2218
2216
  if (this.destroyed)
2219
2217
  return;
2220
2218
 
2221
- // disconnect from parent and destroy chidren
2219
+ // disconnect from parent and destroy children
2222
2220
  this.destroyed = 1;
2223
2221
  this.parent && this.parent.removeChild(this);
2224
2222
  for (const child of this.children)
@@ -2243,7 +2241,7 @@ class EngineObject
2243
2241
 
2244
2242
  /** Called to check if a tile collision should be resolved
2245
2243
  * @param {Number} tileData - the value of the tile at the position
2246
- * @param {Vector2} pos - tile where the collision occured
2244
+ * @param {Vector2} pos - tile where the collision occurred
2247
2245
  * @return {Boolean} - true if the collision should be resolved */
2248
2246
  collideWithTile(tileData, pos) { return tileData > 0; }
2249
2247
 
@@ -2306,7 +2304,7 @@ class EngineObject
2306
2304
  this.collideRaycast = collideRaycast;
2307
2305
  }
2308
2306
 
2309
- /** Returns string containg info about this object for debugging
2307
+ /** Returns string containing info about this object for debugging
2310
2308
  * @return {String} */
2311
2309
  toString()
2312
2310
  {
@@ -2392,7 +2390,7 @@ let overlayContext;
2392
2390
  let mainCanvasSize = vec2();
2393
2391
 
2394
2392
  /** Array containing texture info for batch rendering system
2395
- * @type {Array}
2393
+ * @type {Array<TextureInfo>}
2396
2394
  * @memberof Draw */
2397
2395
  let textureInfos = [];
2398
2396
 
@@ -2431,11 +2429,13 @@ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
2431
2429
 
2432
2430
  // use pos as a tile index
2433
2431
  const textureInfo = textureInfos[textureIndex];
2434
- ASSERT(textureInfo, 'Texture not loaded');
2432
+ ASSERT(!!textureInfo, 'Texture not loaded');
2435
2433
  const sizePadded = size.add(vec2(padding*2));
2436
- const cols = textureInfo.size.x / sizePadded.x |0;
2437
2434
  if (typeof pos === 'number')
2438
- pos = vec2(pos%cols, pos/cols|0);
2435
+ {
2436
+ const cols = textureInfo.size.x / sizePadded.x |0;
2437
+ pos = cols>0 ? vec2(pos%cols, pos/cols|0) : vec2();
2438
+ }
2439
2439
  pos = vec2(pos.x*sizePadded.x+padding, pos.y*sizePadded.y+padding);
2440
2440
 
2441
2441
  // return a tile info object
@@ -2741,24 +2741,6 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
2741
2741
  context.restore();
2742
2742
  }
2743
2743
 
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
2744
  /** Draw text on main canvas in world space
2763
2745
  * Automatically splits new lines into rows
2764
2746
  * @param {String} text
@@ -2826,6 +2808,38 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
2826
2808
  });
2827
2809
  }
2828
2810
 
2811
+ /** Enable normal or additive blend mode
2812
+ * @param {Boolean} [additive]
2813
+ * @param {Boolean} [useWebGL=glEnable]
2814
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
2815
+ * @memberof Draw */
2816
+ function setBlendMode(additive, useWebGL=glEnable, context)
2817
+ {
2818
+ ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
2819
+ if (useWebGL)
2820
+ glAdditive = additive;
2821
+ else
2822
+ {
2823
+ if (!context)
2824
+ context = mainContext;
2825
+ context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
2826
+ }
2827
+ }
2828
+
2829
+ /** Combines all LittleJS canvases onto the main canvas and clears them
2830
+ * This is necessary for things like saving a screenshot
2831
+ * @memberof Draw */
2832
+ function combineCanvases()
2833
+ {
2834
+ // combine canvases
2835
+ glCopyToContext(mainContext, true);
2836
+ mainContext.drawImage(overlayCanvas, 0, 0);
2837
+
2838
+ // clear canvases
2839
+ glClearCanvas();
2840
+ overlayCanvas.width |= 0;
2841
+ }
2842
+
2829
2843
  ///////////////////////////////////////////////////////////////////////////////
2830
2844
 
2831
2845
  let engineFontImage;
@@ -2837,7 +2851,7 @@ let engineFontImage;
2837
2851
  * - You can also use fonts from the main tile sheet
2838
2852
  * @example
2839
2853
  * // use built in font
2840
- * const font = new ImageFont;
2854
+ * const font = new FontImage;
2841
2855
  *
2842
2856
  * // draw text
2843
2857
  * font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
@@ -2912,14 +2926,14 @@ class FontImage
2912
2926
  }
2913
2927
 
2914
2928
  ///////////////////////////////////////////////////////////////////////////////
2915
- // Fullscreen mode
2929
+ // Display functions
2916
2930
 
2917
2931
  /** Returns true if fullscreen mode is active
2918
2932
  * @return {Boolean}
2919
2933
  * @memberof Draw */
2920
2934
  function isFullscreen() { return !!document.fullscreenElement; }
2921
2935
 
2922
- /** Toggle fullsceen mode
2936
+ /** Toggle fullscreen mode
2923
2937
  * @memberof Draw */
2924
2938
  function toggleFullscreen()
2925
2939
  {
@@ -2931,6 +2945,15 @@ function toggleFullscreen()
2931
2945
  }
2932
2946
  else if (rootElement.requestFullscreen)
2933
2947
  rootElement.requestFullscreen();
2948
+ }
2949
+
2950
+ /** Set the cursor style
2951
+ * @param {String} cursorStyle - CSS cursor style (auto, none, crosshair, etc)
2952
+ * @memberof Draw */
2953
+ function setCursor(cursorStyle = 'auto')
2954
+ {
2955
+ const rootElement = mainCanvas.parentElement;
2956
+ rootElement.style.cursor = cursorStyle;
2934
2957
  }
2935
2958
  /**
2936
2959
  * LittleJS Input System
@@ -3275,7 +3298,7 @@ function vibrateStop() { vibrate(0); }
3275
3298
 
3276
3299
  /** True if a touch device has been detected
3277
3300
  * @memberof Input */
3278
- const isTouchDevice = window.ontouchstart !== undefined;
3301
+ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
3279
3302
 
3280
3303
  // touch gamepad internal variables
3281
3304
  let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
@@ -3473,7 +3496,7 @@ function audioInit()
3473
3496
  {
3474
3497
  if (!soundEnable || headlessMode) return;
3475
3498
 
3476
- // (createGain is more widely spported then GainNode construtor)
3499
+ // (createGain is more widely supported then GainNode constructor)
3477
3500
  audioGainNode = audioContext.createGain();
3478
3501
  audioGainNode.connect(audioContext.destination);
3479
3502
  audioGainNode.gain.value = soundVolume; // set starting value
@@ -3823,7 +3846,7 @@ const zzfxR = 44100;
3823
3846
  * @param {Number} [sustain] - Sustain time, how long sound holds (seconds)
3824
3847
  * @param {Number} [release] - Release time, how fast sound fades out (seconds)
3825
3848
  * @param {Number} [shape] - Shape of the sound wave
3826
- * @param {Number} [shapeCurve] - Squarenes of wave (0=square, 1=normal, 2=pointy)
3849
+ * @param {Number} [shapeCurve] - Squareness of wave (0=square, 1=normal, 2=pointy)
3827
3850
  * @param {Number} [slide] - How much to slide frequency (kHz/s)
3828
3851
  * @param {Number} [deltaSlide] - How much to change slide (kHz/s/s)
3829
3852
  * @param {Number} [pitchJump] - Frequency of pitch jump (Hz)
@@ -3835,7 +3858,7 @@ const zzfxR = 44100;
3835
3858
  * @param {Number} [delay] - Overlap sound with itself for reverb and flanger effects (seconds)
3836
3859
  * @param {Number} [sustainVolume] - Volume level for sustain (percent)
3837
3860
  * @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)
3861
+ * @param {Number} [tremolo] - Trembling effect, rate controlled by repeat time (percent)
3839
3862
  * @param {Number} [filter] - Filter cutoff frequency, positive for HPF, negative for LPF (Hz)
3840
3863
  * @return {Array} - Array of audio samples
3841
3864
  * @memberof Audio
@@ -3939,7 +3962,7 @@ function zzfxG
3939
3962
  // ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
3940
3963
 
3941
3964
  /** Generate samples for a ZzFM song with given parameters
3942
- * @param {Array} instruments - Array of ZzFX sound paramaters
3965
+ * @param {Array} instruments - Array of ZzFX sound parameters
3943
3966
  * @param {Array} patterns - Array of pattern data
3944
3967
  * @param {Array} sequence - Array of pattern indexes
3945
3968
  * @param {Number} [BPM] - Playback speed of the song in BPM
@@ -4280,6 +4303,10 @@ class TileLayer extends EngineObject
4280
4303
 
4281
4304
  // draw the entire cached level onto the canvas
4282
4305
  const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
4306
+
4307
+ // fix canvas jitter in some browsers if position is not an integer
4308
+ pos.x |= 0; pos.y |= 0;
4309
+
4283
4310
  (this.isOverlay ? overlayContext : mainContext).drawImage
4284
4311
  (
4285
4312
  this.canvas, pos.x, pos.y,
@@ -4342,7 +4369,7 @@ class TileLayer extends EngineObject
4342
4369
 
4343
4370
  /** Draw the tile at a given position in the tile grid
4344
4371
  * This can be used to clear out tiles when they are destroyed
4345
- * Tiles can also be redrawn if isinde a redrawStart/End block
4372
+ * Tiles can also be redrawn if inside a redrawStart/End block
4346
4373
  * @param {Vector2} layerPos
4347
4374
  * @param {Boolean} [clear] - should the old tile be cleared out
4348
4375
  */
@@ -4367,7 +4394,7 @@ class TileLayer extends EngineObject
4367
4394
  }
4368
4395
  }
4369
4396
 
4370
- /** Draw directly to the 2D canvas in world space (bipass webgl)
4397
+ /** Draw directly to the 2D canvas in world space (bypass webgl)
4371
4398
  * @param {Vector2} pos
4372
4399
  * @param {Vector2} size
4373
4400
  * @param {Number} angle
@@ -4437,7 +4464,7 @@ class TileLayer extends EngineObject
4437
4464
  * let pos = vec2(2,3);
4438
4465
  * let particleEmitter = new ParticleEmitter
4439
4466
  * (
4440
- * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
4467
+ * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emitCone
4441
4468
  * tile(0, 16), // tileInfo
4442
4469
  * rgb(1,1,1), rgb(0,0,0), // colorStartA, colorStartB
4443
4470
  * rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
@@ -4472,7 +4499,7 @@ class ParticleEmitter extends EngineObject
4472
4499
  * @param {Number} [fadeRate] - How quick to fade particles at start/end in percent of life
4473
4500
  * @param {Number} [randomness] - Apply extra randomness percent
4474
4501
  * @param {Boolean} [collideTiles] - Do particles collide against tiles
4475
- * @param {Boolean} [additive] - Should particles use addtive blend
4502
+ * @param {Boolean} [additive] - Should particles use additive blend
4476
4503
  * @param {Boolean} [randomColorLinear] - Should color be randomized linearly or across each component
4477
4504
  * @param {Number} [renderOrder] - Render order for particles (additive is above other stuff by default)
4478
4505
  * @param {Boolean} [localSpace] - Should it be in local space of emitter (world space is default)
@@ -4557,11 +4584,11 @@ class ParticleEmitter extends EngineObject
4557
4584
  this.randomness = randomness;
4558
4585
  /** @property {Boolean} - Do particles collide against tiles */
4559
4586
  this.collideTiles = collideTiles;
4560
- /** @property {Boolean} - Should particles use addtive blend */
4587
+ /** @property {Boolean} - Should particles use additive blend */
4561
4588
  this.additive = additive;
4562
4589
  /** @property {Boolean} - Should it be in local space of emitter */
4563
4590
  this.localSpace = localSpace;
4564
- /** @property {Number} - If non zero the partile is drawn as a trail, stretched in the drection of velocity */
4591
+ /** @property {Number} - If non zero the particle is drawn as a trail, stretched in the direction of velocity */
4565
4592
  this.trailScale = 0;
4566
4593
  /** @property {Function} - Callback when particle is destroyed */
4567
4594
  this.particleDestroyCallback = undefined;
@@ -4610,7 +4637,7 @@ class ParticleEmitter extends EngineObject
4610
4637
  angle += this.angle;
4611
4638
  }
4612
4639
 
4613
- // randomness scales each paremeter by a percentage
4640
+ // randomness scales each parameter by a percentage
4614
4641
  const randomness = this.randomness;
4615
4642
  const randomizeScale = (v)=> v + v*rand(randomness, -randomness);
4616
4643
 
@@ -4639,7 +4666,7 @@ class ParticleEmitter extends EngineObject
4639
4666
  particle.renderOrder = this.renderOrder;
4640
4667
  particle.mirror = !!randInt(2);
4641
4668
 
4642
- // call particle create callaback
4669
+ // call particle create callback
4643
4670
  this.particleCreateCallback && this.particleCreateCallback(particle);
4644
4671
 
4645
4672
  // return the newly created particle
@@ -4708,7 +4735,7 @@ class Particle extends EngineObject
4708
4735
  render()
4709
4736
  {
4710
4737
  // modulate size and color
4711
- const p = min((time - this.spawnTime) / this.lifeTime, 1);
4738
+ const p = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1;
4712
4739
  const radius = this.sizeStart + p * this.sizeEndDelta;
4713
4740
  const size = vec2(radius);
4714
4741
  const fadeRate = this.fadeRate/2;
@@ -4959,7 +4986,7 @@ let glCanvas;
4959
4986
  * @memberof WebGL */
4960
4987
  let glContext;
4961
4988
 
4962
- /** Shoule webgl be setup with antialiasing, must be set before calling engineInit
4989
+ /** Should webgl be setup with anti-aliasing? must be set before calling engineInit
4963
4990
  * @type {Boolean}
4964
4991
  * @memberof WebGL */
4965
4992
  let glAntialias = true;
@@ -4967,9 +4994,15 @@ let glAntialias = true;
4967
4994
  // WebGL internal variables not exposed to documentation
4968
4995
  let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glInstanceCount, glAdditive, glBatchAdditive;
4969
4996
 
4997
+ // WebGL internal constants
4998
+ const gl_MAX_INSTANCES = 1e4;
4999
+ const gl_INDICES_PER_INSTANCE = 11;
5000
+ const gl_INSTANCE_BYTE_STRIDE = gl_INDICES_PER_INSTANCE * 4;
5001
+ const gl_INSTANCE_BUFFER_SIZE = gl_MAX_INSTANCES * gl_INSTANCE_BYTE_STRIDE;
5002
+
4970
5003
  ///////////////////////////////////////////////////////////////////////////////
4971
5004
 
4972
- // Initalize WebGL, called automatically by the engine
5005
+ // Initialize WebGL, called automatically by the engine
4973
5006
  function glInit()
4974
5007
  {
4975
5008
  if (!glEnable || headlessMode) return;
@@ -5019,8 +5052,8 @@ function glInit()
5019
5052
 
5020
5053
  // create the geometry buffer, triangle strip square
5021
5054
  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);
5055
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
5056
+ glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
5024
5057
  }
5025
5058
 
5026
5059
  // Setup render each frame, called automatically by engine
@@ -5028,15 +5061,12 @@ function glPreRender()
5028
5061
  {
5029
5062
  if (!glEnable || headlessMode) return;
5030
5063
 
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
5064
+ // set up the shader and canvas
5065
+ glClearCanvas();
5036
5066
  glContext.useProgram(glShader);
5037
- glContext.activeTexture(gl_TEXTURE0);
5067
+ glContext.activeTexture(glContext.TEXTURE0);
5038
5068
  if (textureInfos[0])
5039
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
5069
+ glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
5040
5070
 
5041
5071
  // set vertex attributes
5042
5072
  let offset = glAdditive = glBatchAdditive = 0;
@@ -5051,15 +5081,15 @@ function glPreRender()
5051
5081
  glContext.vertexAttribDivisor(location, divisor);
5052
5082
  offset += size*typeSize;
5053
5083
  }
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
5084
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
5085
+ initVertexAttribArray('g', glContext.FLOAT, 0, 2); // geometry
5086
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
5087
+ glContext.bufferData(glContext.ARRAY_BUFFER, gl_INSTANCE_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
5088
+ initVertexAttribArray('p', glContext.FLOAT, 4, 4); // position & size
5089
+ initVertexAttribArray('u', glContext.FLOAT, 4, 4); // texture coords
5090
+ initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
5091
+ initVertexAttribArray('a', glContext.UNSIGNED_BYTE, 1, 4); // additiveColor
5092
+ initVertexAttribArray('r', glContext.FLOAT, 4, 1); // rotation
5063
5093
 
5064
5094
  // build the transform matrix
5065
5095
  const s = vec2(2*cameraScale).divide(mainCanvasSize);
@@ -5074,6 +5104,15 @@ function glPreRender()
5074
5104
  );
5075
5105
  }
5076
5106
 
5107
+ /** Clear the canvas and setup the viewport
5108
+ * @memberof WebGL */
5109
+ function glClearCanvas()
5110
+ {
5111
+ // clear and set to same size as main canvas
5112
+ glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
5113
+ glContext.clear(glContext.COLOR_BUFFER_BIT);
5114
+ }
5115
+
5077
5116
  /** Set the WebGl texture, called automatically if using multiple textures
5078
5117
  * - This may also flush the gl buffer resulting in more draw calls and worse performance
5079
5118
  * @param {WebGLTexture} texture
@@ -5085,7 +5124,7 @@ function glSetTexture(texture)
5085
5124
  return;
5086
5125
 
5087
5126
  glFlush();
5088
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = texture);
5127
+ glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture = texture);
5089
5128
  }
5090
5129
 
5091
5130
  /** Compile WebGL shader of the given type, will throw errors if in debug mode
@@ -5101,7 +5140,7 @@ function glCompileShader(source, type)
5101
5140
  glContext.compileShader(shader);
5102
5141
 
5103
5142
  // check for errors
5104
- if (debug && !glContext.getShaderParameter(shader, gl_COMPILE_STATUS))
5143
+ if (debug && !glContext.getShaderParameter(shader, glContext.COMPILE_STATUS))
5105
5144
  throw glContext.getShaderInfoLog(shader);
5106
5145
  return shader;
5107
5146
  }
@@ -5115,12 +5154,12 @@ function glCreateProgram(vsSource, fsSource)
5115
5154
  {
5116
5155
  // build the program
5117
5156
  const program = glContext.createProgram();
5118
- glContext.attachShader(program, glCompileShader(vsSource, gl_VERTEX_SHADER));
5119
- glContext.attachShader(program, glCompileShader(fsSource, gl_FRAGMENT_SHADER));
5157
+ glContext.attachShader(program, glCompileShader(vsSource, glContext.VERTEX_SHADER));
5158
+ glContext.attachShader(program, glCompileShader(fsSource, glContext.FRAGMENT_SHADER));
5120
5159
  glContext.linkProgram(program);
5121
5160
 
5122
5161
  // check for errors
5123
- if (debug && !glContext.getProgramParameter(program, gl_LINK_STATUS))
5162
+ if (debug && !glContext.getProgramParameter(program, glContext.LINK_STATUS))
5124
5163
  throw glContext.getProgramInfoLog(program);
5125
5164
  return program;
5126
5165
  }
@@ -5133,20 +5172,20 @@ function glCreateTexture(image)
5133
5172
  {
5134
5173
  // build the texture
5135
5174
  const texture = glContext.createTexture();
5136
- glContext.bindTexture(gl_TEXTURE_2D, texture);
5175
+ glContext.bindTexture(glContext.TEXTURE_2D, texture);
5137
5176
  if (image && image.width)
5138
- glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
5177
+ glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
5139
5178
  else
5140
5179
  {
5141
5180
  // create a white texture
5142
5181
  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);
5182
+ glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
5144
5183
  }
5145
5184
 
5146
5185
  // 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);
5186
+ const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
5187
+ glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
5188
+ glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, filter);
5150
5189
  return texture;
5151
5190
  }
5152
5191
 
@@ -5156,20 +5195,20 @@ function glFlush()
5156
5195
  {
5157
5196
  if (!glInstanceCount) return;
5158
5197
 
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);
5198
+ const destBlend = glBatchAdditive ? glContext.ONE : glContext.ONE_MINUS_SRC_ALPHA;
5199
+ glContext.blendFuncSeparate(glContext.SRC_ALPHA, destBlend, glContext.ONE, destBlend);
5200
+ glContext.enable(glContext.BLEND);
5162
5201
 
5163
5202
  // 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);
5203
+ glContext.bufferSubData(glContext.ARRAY_BUFFER, 0, glPositionData);
5204
+ glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP, 0, 4, glInstanceCount);
5166
5205
  if (showWatermark)
5167
5206
  drawCount += glInstanceCount;
5168
5207
  glInstanceCount = 0;
5169
5208
  glBatchAdditive = glAdditive;
5170
5209
  }
5171
5210
 
5172
- /** Draw any sprites still in the buffer, copy to main canvas and clear
5211
+ /** Draw any sprites still in the buffer and copy to main canvas
5173
5212
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
5174
5213
  * @param {Boolean} [forceDraw]
5175
5214
  * @memberof WebGL */
@@ -5184,7 +5223,7 @@ function glCopyToContext(context, forceDraw=false)
5184
5223
  context.drawImage(glCanvas, 0, 0);
5185
5224
  }
5186
5225
 
5187
- /** Set antialiasing for webgl canvas
5226
+ /** Set anti-aliasing for webgl canvas
5188
5227
  * @param {Boolean} [antialias]
5189
5228
  * @memberof WebGL */
5190
5229
  function glSetAntialias(antialias=true)
@@ -5214,7 +5253,7 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
5214
5253
  if (glInstanceCount >= gl_MAX_INSTANCES || glBatchAdditive != glAdditive)
5215
5254
  glFlush();
5216
5255
 
5217
- let offset = glInstanceCount * gl_INDICIES_PER_INSTANCE;
5256
+ let offset = glInstanceCount++ * gl_INDICES_PER_INSTANCE;
5218
5257
  glPositionData[offset++] = x;
5219
5258
  glPositionData[offset++] = y;
5220
5259
  glPositionData[offset++] = sizeX;
@@ -5226,41 +5265,7 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
5226
5265
  glColorData[offset++] = rgba;
5227
5266
  glColorData[offset++] = rgbaAdditive;
5228
5267
  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;
5268
+ }
5264
5269
  /**
5265
5270
  * LittleJS - The Tiny Fast JavaScript Game Engine
5266
5271
  * MIT License - Copyright 2021 Frank Force
@@ -5293,7 +5298,7 @@ const engineName = 'LittleJS';
5293
5298
  * @type {String}
5294
5299
  * @default
5295
5300
  * @memberof Engine */
5296
- const engineVersion = '1.11.2';
5301
+ const engineVersion = '1.11.5';
5297
5302
 
5298
5303
  /** Frames per second to update
5299
5304
  * @type {Number}
@@ -5376,8 +5381,21 @@ function engineAddPlugin(updateFunction, renderFunction)
5376
5381
  * @memberof Engine */
5377
5382
  function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
5378
5383
  {
5384
+ ASSERT(!mainContext, 'engine already initialized');
5379
5385
  ASSERT(Array.isArray(imageSources), 'pass in images as array');
5380
5386
 
5387
+ // allow passing in empty functions
5388
+ if (!gameInit)
5389
+ gameInit = ()=>{};
5390
+ if (!gameUpdate)
5391
+ gameUpdate = ()=>{};
5392
+ if (!gameUpdatePost)
5393
+ gameUpdatePost = ()=>{};
5394
+ if (!gameRender)
5395
+ gameRender = ()=>{};
5396
+ if (!gameRenderPost)
5397
+ gameRenderPost = ()=>{};
5398
+
5381
5399
  // Called automatically by engine to setup render system
5382
5400
  function enginePreRender()
5383
5401
  {
@@ -5542,7 +5560,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5542
5560
  (canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
5543
5561
  'user-select:none;' + // prevent hold to select
5544
5562
  '-webkit-user-select:none;' + // compatibility for ios
5545
- (!touchInputEnable ? '' : // no touch css setttings
5563
+ (!touchInputEnable ? '' : // no touch css settings
5546
5564
  'touch-action:none;' + // prevent mobile pinch to resize
5547
5565
  '-webkit-touch-callout:none');// compatibility for ios
5548
5566
  rootElement.style.cssText = styleRoot;
@@ -5571,6 +5589,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5571
5589
  new Promise(resolve =>
5572
5590
  {
5573
5591
  const image = new Image;
5592
+ image.crossOrigin = 'anonymous';
5574
5593
  image.onerror = image.onload = ()=>
5575
5594
  {
5576
5595
  textureInfos[textureIndex] = new TextureInfo(image);
@@ -5615,7 +5634,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5615
5634
  * @memberof Engine */
5616
5635
  function engineObjectsUpdate()
5617
5636
  {
5618
- // get list of solid objects for physics optimzation
5637
+ // get list of solid objects for physics optimization
5619
5638
  engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
5620
5639
 
5621
5640
  // recursive object update