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
@@ -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
 
@@ -54,7 +55,7 @@ function debugSaveDataURL(){}
54
55
  * @memberof Utilities */
55
56
  const PI = Math.PI;
56
57
 
57
- /** Returns absoulte value of value passed in
58
+ /** Returns absolute value of value passed in
58
59
  * @param {Number} value
59
60
  * @return {Number}
60
61
  * @memberof Utilities */
@@ -87,7 +88,7 @@ function sign(value) { return Math.sign(value); }
87
88
  * @memberof Utilities */
88
89
  function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
89
90
 
90
- /** Clamps the value beween max and min
91
+ /** Clamps the value between max and min
91
92
  * @param {Number} value
92
93
  * @param {Number} [min]
93
94
  * @param {Number} [max]
@@ -523,7 +524,7 @@ class Vector2
523
524
  return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
524
525
  }
525
526
 
526
- /** Set the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
527
+ /** Set the integer direction of this vector, corresponding to multiples of 90 degree rotation (0-3)
527
528
  * @param {Number} [direction]
528
529
  * @param {Number} [length] */
529
530
  setDirection(direction, length=1)
@@ -534,7 +535,7 @@ class Vector2
534
535
  direction%2 ? 0 : direction ? -length : length);
535
536
  }
536
537
 
537
- /** Returns the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
538
+ /** Returns the integer direction of this vector, corresponding to multiples of 90 degree rotation (0-3)
538
539
  * @return {Number} */
539
540
  direction()
540
541
  { return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
@@ -919,7 +920,7 @@ const MAGENTA = rgb(1,0,1);
919
920
  * a.set(3); // sets the timer to 3 seconds
920
921
  *
921
922
  * let b = new Timer(1); // creates a timer with 1 second left
922
- * b.unset(); // unsets the timer
923
+ * b.unset(); // unset the timer
923
924
  */
924
925
  class Timer
925
926
  {
@@ -1070,7 +1071,7 @@ let tileFixBleedScale = 0;
1070
1071
  * @memberof Settings */
1071
1072
  let enablePhysicsSolver = true;
1072
1073
 
1073
- /** Default object mass for collision calcuations (how heavy objects are)
1074
+ /** Default object mass for collision calculations (how heavy objects are)
1074
1075
  * @type {Number}
1075
1076
  * @default
1076
1077
  * @memberof Settings */
@@ -1281,7 +1282,7 @@ function setFontDefault(font) { fontDefault = font; }
1281
1282
  * @memberof Settings */
1282
1283
  function setShowSplashScreen(show) { showSplashScreen = show; }
1283
1284
 
1284
- /** Set to disalbe rendering, audio, and input for servers
1285
+ /** Set to disable rendering, audio, and input for servers
1285
1286
  * @param {Boolean} headless
1286
1287
  * @memberof Settings */
1287
1288
  function setHeadlessMode(headless) { headlessMode = headless; }
@@ -1311,7 +1312,7 @@ function setTileFixBleedScale(scale) { tileFixBleedScale = scale; }
1311
1312
  * @memberof Settings */
1312
1313
  function setEnablePhysicsSolver(enable) { enablePhysicsSolver = enable; }
1313
1314
 
1314
- /** Set default object mass for collison calcuations
1315
+ /** Set default object mass for collision calculations
1315
1316
  * @param {Number} mass
1316
1317
  * @memberof Settings */
1317
1318
  function setObjectDefaultMass(mass) { objectDefaultMass = mass; }
@@ -1381,7 +1382,7 @@ function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
1381
1382
  * @memberof Settings */
1382
1383
  function setTouchGamepadAnalog(analog) { touchGamepadAnalog = analog; }
1383
1384
 
1384
- /** Set size of virutal gamepad for touch devices in pixels
1385
+ /** Set size of virtual gamepad for touch devices in pixels
1385
1386
  * @param {Number} size
1386
1387
  * @memberof Settings */
1387
1388
  function setTouchGamepadSize(size) { touchGamepadSize = size; }
@@ -1408,7 +1409,7 @@ function setSoundVolume(volume)
1408
1409
  {
1409
1410
  soundVolume = volume;
1410
1411
  if (soundEnable && !headlessMode && audioGainNode)
1411
- audioGainNode.gain.value = volume; // update gain immediatly
1412
+ audioGainNode.gain.value = volume; // update gain immediately
1412
1413
  }
1413
1414
 
1414
1415
  /** Set default range where sound no longer plays
@@ -1612,7 +1613,7 @@ class EngineObject
1612
1613
  const oldPos = this.pos.copy();
1613
1614
  this.velocity.x *= this.damping;
1614
1615
  this.velocity.y *= this.damping;
1615
- if (this.mass) // dont apply gravity to static objects
1616
+ if (this.mass) // don't apply gravity to static objects
1616
1617
  this.velocity.y += gravity * this.gravityScale;
1617
1618
  this.pos.x += this.velocity.x;
1618
1619
  this.pos.y += this.velocity.y;
@@ -1621,7 +1622,7 @@ class EngineObject
1621
1622
  // physics sanity checks
1622
1623
  ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
1623
1624
  ASSERT(this.damping >= 0 && this.damping <= 1);
1624
- if (!enablePhysicsSolver || !this.mass) // dont do collision for static objects
1625
+ if (!enablePhysicsSolver || !this.mass) // don't do collision for static objects
1625
1626
  return;
1626
1627
 
1627
1628
  const wasMovingDown = this.velocity.y < 0;
@@ -1640,7 +1641,7 @@ class EngineObject
1640
1641
  const epsilon = .001; // necessary to push slightly outside of the collision
1641
1642
  for (const o of engineObjectsCollide)
1642
1643
  {
1643
- // non solid objects don't collide with eachother
1644
+ // non solid objects don't collide with each other
1644
1645
  if (!this.isSolid && !o.isSolid || o.destroyed || o.parent || o == this)
1645
1646
  continue;
1646
1647
 
@@ -1700,7 +1701,7 @@ class EngineObject
1700
1701
  const elastic1 = o.velocity.y * (o.mass - this.mass) / (this.mass + o.mass)
1701
1702
  + this.velocity.y * 2 * this.mass / (this.mass + o.mass);
1702
1703
 
1703
- // lerp betwen elastic or inelastic based on elasticity
1704
+ // lerp between elastic or inelastic based on elasticity
1704
1705
  this.velocity.y = lerp(elasticity, inelastic, elastic0);
1705
1706
  o.velocity.y = lerp(elasticity, inelastic, elastic1);
1706
1707
  }
@@ -1720,7 +1721,7 @@ class EngineObject
1720
1721
  const elastic1 = o.velocity.x * (o.mass - this.mass) / (this.mass + o.mass)
1721
1722
  + this.velocity.x * 2 * this.mass / (this.mass + o.mass);
1722
1723
 
1723
- // lerp betwen elastic or inelastic based on elasticity
1724
+ // lerp between elastic or inelastic based on elasticity
1724
1725
  this.velocity.x = lerp(elasticity, inelastic, elastic0);
1725
1726
  o.velocity.x = lerp(elasticity, inelastic, elastic1);
1726
1727
  }
@@ -1786,7 +1787,7 @@ class EngineObject
1786
1787
  if (this.destroyed)
1787
1788
  return;
1788
1789
 
1789
- // disconnect from parent and destroy chidren
1790
+ // disconnect from parent and destroy children
1790
1791
  this.destroyed = 1;
1791
1792
  this.parent && this.parent.removeChild(this);
1792
1793
  for (const child of this.children)
@@ -1811,7 +1812,7 @@ class EngineObject
1811
1812
 
1812
1813
  /** Called to check if a tile collision should be resolved
1813
1814
  * @param {Number} tileData - the value of the tile at the position
1814
- * @param {Vector2} pos - tile where the collision occured
1815
+ * @param {Vector2} pos - tile where the collision occurred
1815
1816
  * @return {Boolean} - true if the collision should be resolved */
1816
1817
  collideWithTile(tileData, pos) { return tileData > 0; }
1817
1818
 
@@ -1874,7 +1875,7 @@ class EngineObject
1874
1875
  this.collideRaycast = collideRaycast;
1875
1876
  }
1876
1877
 
1877
- /** Returns string containg info about this object for debugging
1878
+ /** Returns string containing info about this object for debugging
1878
1879
  * @return {String} */
1879
1880
  toString()
1880
1881
  {
@@ -1960,7 +1961,7 @@ let overlayContext;
1960
1961
  let mainCanvasSize = vec2();
1961
1962
 
1962
1963
  /** Array containing texture info for batch rendering system
1963
- * @type {Array}
1964
+ * @type {Array<TextureInfo>}
1964
1965
  * @memberof Draw */
1965
1966
  let textureInfos = [];
1966
1967
 
@@ -1999,11 +2000,13 @@ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
1999
2000
 
2000
2001
  // use pos as a tile index
2001
2002
  const textureInfo = textureInfos[textureIndex];
2002
- ASSERT(textureInfo, 'Texture not loaded');
2003
+ ASSERT(!!textureInfo, 'Texture not loaded');
2003
2004
  const sizePadded = size.add(vec2(padding*2));
2004
- const cols = textureInfo.size.x / sizePadded.x |0;
2005
2005
  if (typeof pos === 'number')
2006
- pos = vec2(pos%cols, pos/cols|0);
2006
+ {
2007
+ const cols = textureInfo.size.x / sizePadded.x |0;
2008
+ pos = cols>0 ? vec2(pos%cols, pos/cols|0) : vec2();
2009
+ }
2007
2010
  pos = vec2(pos.x*sizePadded.x+padding, pos.y*sizePadded.y+padding);
2008
2011
 
2009
2012
  // return a tile info object
@@ -2309,24 +2312,6 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
2309
2312
  context.restore();
2310
2313
  }
2311
2314
 
2312
- /** Enable normal or additive blend mode
2313
- * @param {Boolean} [additive]
2314
- * @param {Boolean} [useWebGL=glEnable]
2315
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
2316
- * @memberof Draw */
2317
- function setBlendMode(additive, useWebGL=glEnable, context)
2318
- {
2319
- ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
2320
- if (useWebGL)
2321
- glAdditive = additive;
2322
- else
2323
- {
2324
- if (!context)
2325
- context = mainContext;
2326
- context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
2327
- }
2328
- }
2329
-
2330
2315
  /** Draw text on main canvas in world space
2331
2316
  * Automatically splits new lines into rows
2332
2317
  * @param {String} text
@@ -2394,6 +2379,38 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
2394
2379
  });
2395
2380
  }
2396
2381
 
2382
+ /** Enable normal or additive blend mode
2383
+ * @param {Boolean} [additive]
2384
+ * @param {Boolean} [useWebGL=glEnable]
2385
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
2386
+ * @memberof Draw */
2387
+ function setBlendMode(additive, useWebGL=glEnable, context)
2388
+ {
2389
+ ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
2390
+ if (useWebGL)
2391
+ glAdditive = additive;
2392
+ else
2393
+ {
2394
+ if (!context)
2395
+ context = mainContext;
2396
+ context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
2397
+ }
2398
+ }
2399
+
2400
+ /** Combines all LittleJS canvases onto the main canvas and clears them
2401
+ * This is necessary for things like saving a screenshot
2402
+ * @memberof Draw */
2403
+ function combineCanvases()
2404
+ {
2405
+ // combine canvases
2406
+ glCopyToContext(mainContext, true);
2407
+ mainContext.drawImage(overlayCanvas, 0, 0);
2408
+
2409
+ // clear canvases
2410
+ glClearCanvas();
2411
+ overlayCanvas.width |= 0;
2412
+ }
2413
+
2397
2414
  ///////////////////////////////////////////////////////////////////////////////
2398
2415
 
2399
2416
  let engineFontImage;
@@ -2405,7 +2422,7 @@ let engineFontImage;
2405
2422
  * - You can also use fonts from the main tile sheet
2406
2423
  * @example
2407
2424
  * // use built in font
2408
- * const font = new ImageFont;
2425
+ * const font = new FontImage;
2409
2426
  *
2410
2427
  * // draw text
2411
2428
  * font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
@@ -2480,14 +2497,14 @@ class FontImage
2480
2497
  }
2481
2498
 
2482
2499
  ///////////////////////////////////////////////////////////////////////////////
2483
- // Fullscreen mode
2500
+ // Display functions
2484
2501
 
2485
2502
  /** Returns true if fullscreen mode is active
2486
2503
  * @return {Boolean}
2487
2504
  * @memberof Draw */
2488
2505
  function isFullscreen() { return !!document.fullscreenElement; }
2489
2506
 
2490
- /** Toggle fullsceen mode
2507
+ /** Toggle fullscreen mode
2491
2508
  * @memberof Draw */
2492
2509
  function toggleFullscreen()
2493
2510
  {
@@ -2499,6 +2516,15 @@ function toggleFullscreen()
2499
2516
  }
2500
2517
  else if (rootElement.requestFullscreen)
2501
2518
  rootElement.requestFullscreen();
2519
+ }
2520
+
2521
+ /** Set the cursor style
2522
+ * @param {String} cursorStyle - CSS cursor style (auto, none, crosshair, etc)
2523
+ * @memberof Draw */
2524
+ function setCursor(cursorStyle = 'auto')
2525
+ {
2526
+ const rootElement = mainCanvas.parentElement;
2527
+ rootElement.style.cursor = cursorStyle;
2502
2528
  }
2503
2529
  /**
2504
2530
  * LittleJS Input System
@@ -2843,7 +2869,7 @@ function vibrateStop() { vibrate(0); }
2843
2869
 
2844
2870
  /** True if a touch device has been detected
2845
2871
  * @memberof Input */
2846
- const isTouchDevice = window.ontouchstart !== undefined;
2872
+ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
2847
2873
 
2848
2874
  // touch gamepad internal variables
2849
2875
  let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
@@ -3041,7 +3067,7 @@ function audioInit()
3041
3067
  {
3042
3068
  if (!soundEnable || headlessMode) return;
3043
3069
 
3044
- // (createGain is more widely spported then GainNode construtor)
3070
+ // (createGain is more widely supported then GainNode constructor)
3045
3071
  audioGainNode = audioContext.createGain();
3046
3072
  audioGainNode.connect(audioContext.destination);
3047
3073
  audioGainNode.gain.value = soundVolume; // set starting value
@@ -3391,7 +3417,7 @@ const zzfxR = 44100;
3391
3417
  * @param {Number} [sustain] - Sustain time, how long sound holds (seconds)
3392
3418
  * @param {Number} [release] - Release time, how fast sound fades out (seconds)
3393
3419
  * @param {Number} [shape] - Shape of the sound wave
3394
- * @param {Number} [shapeCurve] - Squarenes of wave (0=square, 1=normal, 2=pointy)
3420
+ * @param {Number} [shapeCurve] - Squareness of wave (0=square, 1=normal, 2=pointy)
3395
3421
  * @param {Number} [slide] - How much to slide frequency (kHz/s)
3396
3422
  * @param {Number} [deltaSlide] - How much to change slide (kHz/s/s)
3397
3423
  * @param {Number} [pitchJump] - Frequency of pitch jump (Hz)
@@ -3403,7 +3429,7 @@ const zzfxR = 44100;
3403
3429
  * @param {Number} [delay] - Overlap sound with itself for reverb and flanger effects (seconds)
3404
3430
  * @param {Number} [sustainVolume] - Volume level for sustain (percent)
3405
3431
  * @param {Number} [decay] - Decay time, how long to reach sustain after attack (seconds)
3406
- * @param {Number} [tremolo] - Trembling effect, rate controlled by repeat time (precent)
3432
+ * @param {Number} [tremolo] - Trembling effect, rate controlled by repeat time (percent)
3407
3433
  * @param {Number} [filter] - Filter cutoff frequency, positive for HPF, negative for LPF (Hz)
3408
3434
  * @return {Array} - Array of audio samples
3409
3435
  * @memberof Audio
@@ -3507,7 +3533,7 @@ function zzfxG
3507
3533
  // ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
3508
3534
 
3509
3535
  /** Generate samples for a ZzFM song with given parameters
3510
- * @param {Array} instruments - Array of ZzFX sound paramaters
3536
+ * @param {Array} instruments - Array of ZzFX sound parameters
3511
3537
  * @param {Array} patterns - Array of pattern data
3512
3538
  * @param {Array} sequence - Array of pattern indexes
3513
3539
  * @param {Number} [BPM] - Playback speed of the song in BPM
@@ -3848,6 +3874,10 @@ class TileLayer extends EngineObject
3848
3874
 
3849
3875
  // draw the entire cached level onto the canvas
3850
3876
  const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
3877
+
3878
+ // fix canvas jitter in some browsers if position is not an integer
3879
+ pos.x |= 0; pos.y |= 0;
3880
+
3851
3881
  (this.isOverlay ? overlayContext : mainContext).drawImage
3852
3882
  (
3853
3883
  this.canvas, pos.x, pos.y,
@@ -3910,7 +3940,7 @@ class TileLayer extends EngineObject
3910
3940
 
3911
3941
  /** Draw the tile at a given position in the tile grid
3912
3942
  * This can be used to clear out tiles when they are destroyed
3913
- * Tiles can also be redrawn if isinde a redrawStart/End block
3943
+ * Tiles can also be redrawn if inside a redrawStart/End block
3914
3944
  * @param {Vector2} layerPos
3915
3945
  * @param {Boolean} [clear] - should the old tile be cleared out
3916
3946
  */
@@ -3935,7 +3965,7 @@ class TileLayer extends EngineObject
3935
3965
  }
3936
3966
  }
3937
3967
 
3938
- /** Draw directly to the 2D canvas in world space (bipass webgl)
3968
+ /** Draw directly to the 2D canvas in world space (bypass webgl)
3939
3969
  * @param {Vector2} pos
3940
3970
  * @param {Vector2} size
3941
3971
  * @param {Number} angle
@@ -4005,7 +4035,7 @@ class TileLayer extends EngineObject
4005
4035
  * let pos = vec2(2,3);
4006
4036
  * let particleEmitter = new ParticleEmitter
4007
4037
  * (
4008
- * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
4038
+ * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emitCone
4009
4039
  * tile(0, 16), // tileInfo
4010
4040
  * rgb(1,1,1), rgb(0,0,0), // colorStartA, colorStartB
4011
4041
  * rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
@@ -4040,7 +4070,7 @@ class ParticleEmitter extends EngineObject
4040
4070
  * @param {Number} [fadeRate] - How quick to fade particles at start/end in percent of life
4041
4071
  * @param {Number} [randomness] - Apply extra randomness percent
4042
4072
  * @param {Boolean} [collideTiles] - Do particles collide against tiles
4043
- * @param {Boolean} [additive] - Should particles use addtive blend
4073
+ * @param {Boolean} [additive] - Should particles use additive blend
4044
4074
  * @param {Boolean} [randomColorLinear] - Should color be randomized linearly or across each component
4045
4075
  * @param {Number} [renderOrder] - Render order for particles (additive is above other stuff by default)
4046
4076
  * @param {Boolean} [localSpace] - Should it be in local space of emitter (world space is default)
@@ -4125,11 +4155,11 @@ class ParticleEmitter extends EngineObject
4125
4155
  this.randomness = randomness;
4126
4156
  /** @property {Boolean} - Do particles collide against tiles */
4127
4157
  this.collideTiles = collideTiles;
4128
- /** @property {Boolean} - Should particles use addtive blend */
4158
+ /** @property {Boolean} - Should particles use additive blend */
4129
4159
  this.additive = additive;
4130
4160
  /** @property {Boolean} - Should it be in local space of emitter */
4131
4161
  this.localSpace = localSpace;
4132
- /** @property {Number} - If non zero the partile is drawn as a trail, stretched in the drection of velocity */
4162
+ /** @property {Number} - If non zero the particle is drawn as a trail, stretched in the direction of velocity */
4133
4163
  this.trailScale = 0;
4134
4164
  /** @property {Function} - Callback when particle is destroyed */
4135
4165
  this.particleDestroyCallback = undefined;
@@ -4178,7 +4208,7 @@ class ParticleEmitter extends EngineObject
4178
4208
  angle += this.angle;
4179
4209
  }
4180
4210
 
4181
- // randomness scales each paremeter by a percentage
4211
+ // randomness scales each parameter by a percentage
4182
4212
  const randomness = this.randomness;
4183
4213
  const randomizeScale = (v)=> v + v*rand(randomness, -randomness);
4184
4214
 
@@ -4207,7 +4237,7 @@ class ParticleEmitter extends EngineObject
4207
4237
  particle.renderOrder = this.renderOrder;
4208
4238
  particle.mirror = !!randInt(2);
4209
4239
 
4210
- // call particle create callaback
4240
+ // call particle create callback
4211
4241
  this.particleCreateCallback && this.particleCreateCallback(particle);
4212
4242
 
4213
4243
  // return the newly created particle
@@ -4276,7 +4306,7 @@ class Particle extends EngineObject
4276
4306
  render()
4277
4307
  {
4278
4308
  // modulate size and color
4279
- const p = min((time - this.spawnTime) / this.lifeTime, 1);
4309
+ const p = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1;
4280
4310
  const radius = this.sizeStart + p * this.sizeEndDelta;
4281
4311
  const size = vec2(radius);
4282
4312
  const fadeRate = this.fadeRate/2;
@@ -4527,7 +4557,7 @@ let glCanvas;
4527
4557
  * @memberof WebGL */
4528
4558
  let glContext;
4529
4559
 
4530
- /** Shoule webgl be setup with antialiasing, must be set before calling engineInit
4560
+ /** Should webgl be setup with anti-aliasing? must be set before calling engineInit
4531
4561
  * @type {Boolean}
4532
4562
  * @memberof WebGL */
4533
4563
  let glAntialias = true;
@@ -4535,9 +4565,15 @@ let glAntialias = true;
4535
4565
  // WebGL internal variables not exposed to documentation
4536
4566
  let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glInstanceCount, glAdditive, glBatchAdditive;
4537
4567
 
4568
+ // WebGL internal constants
4569
+ const gl_MAX_INSTANCES = 1e4;
4570
+ const gl_INDICES_PER_INSTANCE = 11;
4571
+ const gl_INSTANCE_BYTE_STRIDE = gl_INDICES_PER_INSTANCE * 4;
4572
+ const gl_INSTANCE_BUFFER_SIZE = gl_MAX_INSTANCES * gl_INSTANCE_BYTE_STRIDE;
4573
+
4538
4574
  ///////////////////////////////////////////////////////////////////////////////
4539
4575
 
4540
- // Initalize WebGL, called automatically by the engine
4576
+ // Initialize WebGL, called automatically by the engine
4541
4577
  function glInit()
4542
4578
  {
4543
4579
  if (!glEnable || headlessMode) return;
@@ -4587,8 +4623,8 @@ function glInit()
4587
4623
 
4588
4624
  // create the geometry buffer, triangle strip square
4589
4625
  const geometry = new Float32Array([glInstanceCount=0,0,1,0,0,1,1,1]);
4590
- glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
4591
- glContext.bufferData(gl_ARRAY_BUFFER, geometry, gl_STATIC_DRAW);
4626
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
4627
+ glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
4592
4628
  }
4593
4629
 
4594
4630
  // Setup render each frame, called automatically by engine
@@ -4596,15 +4632,12 @@ function glPreRender()
4596
4632
  {
4597
4633
  if (!glEnable || headlessMode) return;
4598
4634
 
4599
- // clear and set to same size as main canvas
4600
- glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
4601
- glContext.clear(gl_COLOR_BUFFER_BIT);
4602
-
4603
- // set up the shader
4635
+ // set up the shader and canvas
4636
+ glClearCanvas();
4604
4637
  glContext.useProgram(glShader);
4605
- glContext.activeTexture(gl_TEXTURE0);
4638
+ glContext.activeTexture(glContext.TEXTURE0);
4606
4639
  if (textureInfos[0])
4607
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
4640
+ glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
4608
4641
 
4609
4642
  // set vertex attributes
4610
4643
  let offset = glAdditive = glBatchAdditive = 0;
@@ -4619,15 +4652,15 @@ function glPreRender()
4619
4652
  glContext.vertexAttribDivisor(location, divisor);
4620
4653
  offset += size*typeSize;
4621
4654
  }
4622
- glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
4623
- initVertexAttribArray('g', gl_FLOAT, 0, 2); // geometry
4624
- glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
4625
- glContext.bufferData(gl_ARRAY_BUFFER, gl_INSTANCE_BUFFER_SIZE, gl_DYNAMIC_DRAW);
4626
- initVertexAttribArray('p', gl_FLOAT, 4, 4); // position & size
4627
- initVertexAttribArray('u', gl_FLOAT, 4, 4); // texture coords
4628
- initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4); // color
4629
- initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4); // additiveColor
4630
- initVertexAttribArray('r', gl_FLOAT, 4, 1); // rotation
4655
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
4656
+ initVertexAttribArray('g', glContext.FLOAT, 0, 2); // geometry
4657
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
4658
+ glContext.bufferData(glContext.ARRAY_BUFFER, gl_INSTANCE_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
4659
+ initVertexAttribArray('p', glContext.FLOAT, 4, 4); // position & size
4660
+ initVertexAttribArray('u', glContext.FLOAT, 4, 4); // texture coords
4661
+ initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
4662
+ initVertexAttribArray('a', glContext.UNSIGNED_BYTE, 1, 4); // additiveColor
4663
+ initVertexAttribArray('r', glContext.FLOAT, 4, 1); // rotation
4631
4664
 
4632
4665
  // build the transform matrix
4633
4666
  const s = vec2(2*cameraScale).divide(mainCanvasSize);
@@ -4642,6 +4675,15 @@ function glPreRender()
4642
4675
  );
4643
4676
  }
4644
4677
 
4678
+ /** Clear the canvas and setup the viewport
4679
+ * @memberof WebGL */
4680
+ function glClearCanvas()
4681
+ {
4682
+ // clear and set to same size as main canvas
4683
+ glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
4684
+ glContext.clear(glContext.COLOR_BUFFER_BIT);
4685
+ }
4686
+
4645
4687
  /** Set the WebGl texture, called automatically if using multiple textures
4646
4688
  * - This may also flush the gl buffer resulting in more draw calls and worse performance
4647
4689
  * @param {WebGLTexture} texture
@@ -4653,7 +4695,7 @@ function glSetTexture(texture)
4653
4695
  return;
4654
4696
 
4655
4697
  glFlush();
4656
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = texture);
4698
+ glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture = texture);
4657
4699
  }
4658
4700
 
4659
4701
  /** Compile WebGL shader of the given type, will throw errors if in debug mode
@@ -4669,7 +4711,7 @@ function glCompileShader(source, type)
4669
4711
  glContext.compileShader(shader);
4670
4712
 
4671
4713
  // check for errors
4672
- if (debug && !glContext.getShaderParameter(shader, gl_COMPILE_STATUS))
4714
+ if (debug && !glContext.getShaderParameter(shader, glContext.COMPILE_STATUS))
4673
4715
  throw glContext.getShaderInfoLog(shader);
4674
4716
  return shader;
4675
4717
  }
@@ -4683,12 +4725,12 @@ function glCreateProgram(vsSource, fsSource)
4683
4725
  {
4684
4726
  // build the program
4685
4727
  const program = glContext.createProgram();
4686
- glContext.attachShader(program, glCompileShader(vsSource, gl_VERTEX_SHADER));
4687
- glContext.attachShader(program, glCompileShader(fsSource, gl_FRAGMENT_SHADER));
4728
+ glContext.attachShader(program, glCompileShader(vsSource, glContext.VERTEX_SHADER));
4729
+ glContext.attachShader(program, glCompileShader(fsSource, glContext.FRAGMENT_SHADER));
4688
4730
  glContext.linkProgram(program);
4689
4731
 
4690
4732
  // check for errors
4691
- if (debug && !glContext.getProgramParameter(program, gl_LINK_STATUS))
4733
+ if (debug && !glContext.getProgramParameter(program, glContext.LINK_STATUS))
4692
4734
  throw glContext.getProgramInfoLog(program);
4693
4735
  return program;
4694
4736
  }
@@ -4701,20 +4743,20 @@ function glCreateTexture(image)
4701
4743
  {
4702
4744
  // build the texture
4703
4745
  const texture = glContext.createTexture();
4704
- glContext.bindTexture(gl_TEXTURE_2D, texture);
4746
+ glContext.bindTexture(glContext.TEXTURE_2D, texture);
4705
4747
  if (image && image.width)
4706
- glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
4748
+ glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
4707
4749
  else
4708
4750
  {
4709
4751
  // create a white texture
4710
4752
  const whitePixel = new Uint8Array([255, 255, 255, 255]);
4711
- glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, 1, 1, 0, gl_RGBA, gl_UNSIGNED_BYTE, whitePixel);
4753
+ glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
4712
4754
  }
4713
4755
 
4714
4756
  // use point filtering for pixelated rendering
4715
- const filter = tilesPixelated ? gl_NEAREST : gl_LINEAR;
4716
- glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
4717
- glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
4757
+ const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
4758
+ glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
4759
+ glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, filter);
4718
4760
  return texture;
4719
4761
  }
4720
4762
 
@@ -4724,20 +4766,20 @@ function glFlush()
4724
4766
  {
4725
4767
  if (!glInstanceCount) return;
4726
4768
 
4727
- const destBlend = glBatchAdditive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
4728
- glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
4729
- glContext.enable(gl_BLEND);
4769
+ const destBlend = glBatchAdditive ? glContext.ONE : glContext.ONE_MINUS_SRC_ALPHA;
4770
+ glContext.blendFuncSeparate(glContext.SRC_ALPHA, destBlend, glContext.ONE, destBlend);
4771
+ glContext.enable(glContext.BLEND);
4730
4772
 
4731
4773
  // draw all the sprites in the batch and reset the buffer
4732
- glContext.bufferSubData(gl_ARRAY_BUFFER, 0, glPositionData);
4733
- glContext.drawArraysInstanced(gl_TRIANGLE_STRIP, 0, 4, glInstanceCount);
4774
+ glContext.bufferSubData(glContext.ARRAY_BUFFER, 0, glPositionData);
4775
+ glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP, 0, 4, glInstanceCount);
4734
4776
  if (showWatermark)
4735
4777
  drawCount += glInstanceCount;
4736
4778
  glInstanceCount = 0;
4737
4779
  glBatchAdditive = glAdditive;
4738
4780
  }
4739
4781
 
4740
- /** Draw any sprites still in the buffer, copy to main canvas and clear
4782
+ /** Draw any sprites still in the buffer and copy to main canvas
4741
4783
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
4742
4784
  * @param {Boolean} [forceDraw]
4743
4785
  * @memberof WebGL */
@@ -4752,7 +4794,7 @@ function glCopyToContext(context, forceDraw=false)
4752
4794
  context.drawImage(glCanvas, 0, 0);
4753
4795
  }
4754
4796
 
4755
- /** Set antialiasing for webgl canvas
4797
+ /** Set anti-aliasing for webgl canvas
4756
4798
  * @param {Boolean} [antialias]
4757
4799
  * @memberof WebGL */
4758
4800
  function glSetAntialias(antialias=true)
@@ -4782,7 +4824,7 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
4782
4824
  if (glInstanceCount >= gl_MAX_INSTANCES || glBatchAdditive != glAdditive)
4783
4825
  glFlush();
4784
4826
 
4785
- let offset = glInstanceCount * gl_INDICIES_PER_INSTANCE;
4827
+ let offset = glInstanceCount++ * gl_INDICES_PER_INSTANCE;
4786
4828
  glPositionData[offset++] = x;
4787
4829
  glPositionData[offset++] = y;
4788
4830
  glPositionData[offset++] = sizeX;
@@ -4794,41 +4836,7 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
4794
4836
  glColorData[offset++] = rgba;
4795
4837
  glColorData[offset++] = rgbaAdditive;
4796
4838
  glPositionData[offset++] = angle;
4797
- glInstanceCount++;
4798
- }
4799
-
4800
- ///////////////////////////////////////////////////////////////////////////////
4801
- // store gl constants as integers so their name doesn't use space in minifed
4802
- const
4803
- gl_ONE = 1,
4804
- gl_TRIANGLE_STRIP = 5,
4805
- gl_SRC_ALPHA = 770,
4806
- gl_ONE_MINUS_SRC_ALPHA = 771,
4807
- gl_BLEND = 3042,
4808
- gl_TEXTURE_2D = 3553,
4809
- gl_UNSIGNED_BYTE = 5121,
4810
- gl_FLOAT = 5126,
4811
- gl_RGBA = 6408,
4812
- gl_NEAREST = 9728,
4813
- gl_LINEAR = 9729,
4814
- gl_TEXTURE_MAG_FILTER = 10240,
4815
- gl_TEXTURE_MIN_FILTER = 10241,
4816
- gl_COLOR_BUFFER_BIT = 16384,
4817
- gl_TEXTURE0 = 33984,
4818
- gl_ARRAY_BUFFER = 34962,
4819
- gl_STATIC_DRAW = 35044,
4820
- gl_DYNAMIC_DRAW = 35048,
4821
- gl_FRAGMENT_SHADER = 35632,
4822
- gl_VERTEX_SHADER = 35633,
4823
- gl_COMPILE_STATUS = 35713,
4824
- gl_LINK_STATUS = 35714,
4825
- gl_UNPACK_FLIP_Y_WEBGL = 37440,
4826
-
4827
- // constants for batch rendering
4828
- gl_INDICIES_PER_INSTANCE = 11,
4829
- gl_MAX_INSTANCES = 1e4,
4830
- gl_INSTANCE_BYTE_STRIDE = gl_INDICIES_PER_INSTANCE * 4, // 11 * 4
4831
- gl_INSTANCE_BUFFER_SIZE = gl_MAX_INSTANCES * gl_INSTANCE_BYTE_STRIDE;
4839
+ }
4832
4840
  /**
4833
4841
  * LittleJS - The Tiny Fast JavaScript Game Engine
4834
4842
  * MIT License - Copyright 2021 Frank Force
@@ -4861,7 +4869,7 @@ const engineName = 'LittleJS';
4861
4869
  * @type {String}
4862
4870
  * @default
4863
4871
  * @memberof Engine */
4864
- const engineVersion = '1.11.2';
4872
+ const engineVersion = '1.11.5';
4865
4873
 
4866
4874
  /** Frames per second to update
4867
4875
  * @type {Number}
@@ -4944,8 +4952,21 @@ function engineAddPlugin(updateFunction, renderFunction)
4944
4952
  * @memberof Engine */
4945
4953
  function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
4946
4954
  {
4955
+ ASSERT(!mainContext, 'engine already initialized');
4947
4956
  ASSERT(Array.isArray(imageSources), 'pass in images as array');
4948
4957
 
4958
+ // allow passing in empty functions
4959
+ if (!gameInit)
4960
+ gameInit = ()=>{};
4961
+ if (!gameUpdate)
4962
+ gameUpdate = ()=>{};
4963
+ if (!gameUpdatePost)
4964
+ gameUpdatePost = ()=>{};
4965
+ if (!gameRender)
4966
+ gameRender = ()=>{};
4967
+ if (!gameRenderPost)
4968
+ gameRenderPost = ()=>{};
4969
+
4949
4970
  // Called automatically by engine to setup render system
4950
4971
  function enginePreRender()
4951
4972
  {
@@ -5110,7 +5131,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5110
5131
  (canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
5111
5132
  'user-select:none;' + // prevent hold to select
5112
5133
  '-webkit-user-select:none;' + // compatibility for ios
5113
- (!touchInputEnable ? '' : // no touch css setttings
5134
+ (!touchInputEnable ? '' : // no touch css settings
5114
5135
  'touch-action:none;' + // prevent mobile pinch to resize
5115
5136
  '-webkit-touch-callout:none');// compatibility for ios
5116
5137
  rootElement.style.cssText = styleRoot;
@@ -5139,6 +5160,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5139
5160
  new Promise(resolve =>
5140
5161
  {
5141
5162
  const image = new Image;
5163
+ image.crossOrigin = 'anonymous';
5142
5164
  image.onerror = image.onload = ()=>
5143
5165
  {
5144
5166
  textureInfos[textureIndex] = new TextureInfo(image);
@@ -5183,7 +5205,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5183
5205
  * @memberof Engine */
5184
5206
  function engineObjectsUpdate()
5185
5207
  {
5186
- // get list of solid objects for physics optimzation
5208
+ // get list of solid objects for physics optimization
5187
5209
  engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
5188
5210
 
5189
5211
  // recursive object update