littlejsengine 1.11.2 → 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 (56) 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 +1 -1
  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/examples/electron/build.js +0 -107
  40. package/examples/electron/electron.js +0 -43
  41. package/examples/electron/game.js +0 -131
  42. package/examples/electron/index.html +0 -13
  43. package/examples/electron/package.json +0 -22
  44. package/examples/electron/tiles.png +0 -0
  45. package/examples/js13k/build.bat +0 -2
  46. package/examples/js13k/build.js +0 -131
  47. package/examples/js13k/game.js +0 -118
  48. package/examples/js13k/index.html +0 -21
  49. package/examples/js13k/tiles.png +0 -0
  50. package/examples/typescript/build.bat +0 -5
  51. package/examples/typescript/build.js +0 -33
  52. package/examples/typescript/game.js +0 -102
  53. package/examples/typescript/game.ts +0 -134
  54. package/examples/typescript/index.html +0 -10
  55. package/examples/typescript/tiles.png +0 -0
  56. package/examples/typescript/tsconfig.json +0 -8
@@ -54,7 +54,7 @@ function debugSaveDataURL(){}
54
54
  * @memberof Utilities */
55
55
  const PI = Math.PI;
56
56
 
57
- /** Returns absoulte value of value passed in
57
+ /** Returns absolute value of value passed in
58
58
  * @param {Number} value
59
59
  * @return {Number}
60
60
  * @memberof Utilities */
@@ -87,7 +87,7 @@ function sign(value) { return Math.sign(value); }
87
87
  * @memberof Utilities */
88
88
  function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
89
89
 
90
- /** Clamps the value beween max and min
90
+ /** Clamps the value between max and min
91
91
  * @param {Number} value
92
92
  * @param {Number} [min]
93
93
  * @param {Number} [max]
@@ -523,7 +523,7 @@ class Vector2
523
523
  return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
524
524
  }
525
525
 
526
- /** Set the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
526
+ /** Set the integer direction of this vector, corresponding to multiples of 90 degree rotation (0-3)
527
527
  * @param {Number} [direction]
528
528
  * @param {Number} [length] */
529
529
  setDirection(direction, length=1)
@@ -534,7 +534,7 @@ class Vector2
534
534
  direction%2 ? 0 : direction ? -length : length);
535
535
  }
536
536
 
537
- /** Returns the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
537
+ /** Returns the integer direction of this vector, corresponding to multiples of 90 degree rotation (0-3)
538
538
  * @return {Number} */
539
539
  direction()
540
540
  { return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
@@ -919,7 +919,7 @@ const MAGENTA = rgb(1,0,1);
919
919
  * a.set(3); // sets the timer to 3 seconds
920
920
  *
921
921
  * let b = new Timer(1); // creates a timer with 1 second left
922
- * b.unset(); // unsets the timer
922
+ * b.unset(); // unset the timer
923
923
  */
924
924
  class Timer
925
925
  {
@@ -1070,7 +1070,7 @@ let tileFixBleedScale = 0;
1070
1070
  * @memberof Settings */
1071
1071
  let enablePhysicsSolver = true;
1072
1072
 
1073
- /** Default object mass for collision calcuations (how heavy objects are)
1073
+ /** Default object mass for collision calculations (how heavy objects are)
1074
1074
  * @type {Number}
1075
1075
  * @default
1076
1076
  * @memberof Settings */
@@ -1281,7 +1281,7 @@ function setFontDefault(font) { fontDefault = font; }
1281
1281
  * @memberof Settings */
1282
1282
  function setShowSplashScreen(show) { showSplashScreen = show; }
1283
1283
 
1284
- /** Set to disalbe rendering, audio, and input for servers
1284
+ /** Set to disable rendering, audio, and input for servers
1285
1285
  * @param {Boolean} headless
1286
1286
  * @memberof Settings */
1287
1287
  function setHeadlessMode(headless) { headlessMode = headless; }
@@ -1311,7 +1311,7 @@ function setTileFixBleedScale(scale) { tileFixBleedScale = scale; }
1311
1311
  * @memberof Settings */
1312
1312
  function setEnablePhysicsSolver(enable) { enablePhysicsSolver = enable; }
1313
1313
 
1314
- /** Set default object mass for collison calcuations
1314
+ /** Set default object mass for collision calculations
1315
1315
  * @param {Number} mass
1316
1316
  * @memberof Settings */
1317
1317
  function setObjectDefaultMass(mass) { objectDefaultMass = mass; }
@@ -1381,7 +1381,7 @@ function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
1381
1381
  * @memberof Settings */
1382
1382
  function setTouchGamepadAnalog(analog) { touchGamepadAnalog = analog; }
1383
1383
 
1384
- /** Set size of virutal gamepad for touch devices in pixels
1384
+ /** Set size of virtual gamepad for touch devices in pixels
1385
1385
  * @param {Number} size
1386
1386
  * @memberof Settings */
1387
1387
  function setTouchGamepadSize(size) { touchGamepadSize = size; }
@@ -1408,7 +1408,7 @@ function setSoundVolume(volume)
1408
1408
  {
1409
1409
  soundVolume = volume;
1410
1410
  if (soundEnable && !headlessMode && audioGainNode)
1411
- audioGainNode.gain.value = volume; // update gain immediatly
1411
+ audioGainNode.gain.value = volume; // update gain immediately
1412
1412
  }
1413
1413
 
1414
1414
  /** Set default range where sound no longer plays
@@ -1612,7 +1612,7 @@ class EngineObject
1612
1612
  const oldPos = this.pos.copy();
1613
1613
  this.velocity.x *= this.damping;
1614
1614
  this.velocity.y *= this.damping;
1615
- if (this.mass) // dont apply gravity to static objects
1615
+ if (this.mass) // don't apply gravity to static objects
1616
1616
  this.velocity.y += gravity * this.gravityScale;
1617
1617
  this.pos.x += this.velocity.x;
1618
1618
  this.pos.y += this.velocity.y;
@@ -1621,7 +1621,7 @@ class EngineObject
1621
1621
  // physics sanity checks
1622
1622
  ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
1623
1623
  ASSERT(this.damping >= 0 && this.damping <= 1);
1624
- if (!enablePhysicsSolver || !this.mass) // dont do collision for static objects
1624
+ if (!enablePhysicsSolver || !this.mass) // don't do collision for static objects
1625
1625
  return;
1626
1626
 
1627
1627
  const wasMovingDown = this.velocity.y < 0;
@@ -1640,7 +1640,7 @@ class EngineObject
1640
1640
  const epsilon = .001; // necessary to push slightly outside of the collision
1641
1641
  for (const o of engineObjectsCollide)
1642
1642
  {
1643
- // non solid objects don't collide with eachother
1643
+ // non solid objects don't collide with each other
1644
1644
  if (!this.isSolid && !o.isSolid || o.destroyed || o.parent || o == this)
1645
1645
  continue;
1646
1646
 
@@ -1700,7 +1700,7 @@ class EngineObject
1700
1700
  const elastic1 = o.velocity.y * (o.mass - this.mass) / (this.mass + o.mass)
1701
1701
  + this.velocity.y * 2 * this.mass / (this.mass + o.mass);
1702
1702
 
1703
- // lerp betwen elastic or inelastic based on elasticity
1703
+ // lerp between elastic or inelastic based on elasticity
1704
1704
  this.velocity.y = lerp(elasticity, inelastic, elastic0);
1705
1705
  o.velocity.y = lerp(elasticity, inelastic, elastic1);
1706
1706
  }
@@ -1720,7 +1720,7 @@ class EngineObject
1720
1720
  const elastic1 = o.velocity.x * (o.mass - this.mass) / (this.mass + o.mass)
1721
1721
  + this.velocity.x * 2 * this.mass / (this.mass + o.mass);
1722
1722
 
1723
- // lerp betwen elastic or inelastic based on elasticity
1723
+ // lerp between elastic or inelastic based on elasticity
1724
1724
  this.velocity.x = lerp(elasticity, inelastic, elastic0);
1725
1725
  o.velocity.x = lerp(elasticity, inelastic, elastic1);
1726
1726
  }
@@ -1786,7 +1786,7 @@ class EngineObject
1786
1786
  if (this.destroyed)
1787
1787
  return;
1788
1788
 
1789
- // disconnect from parent and destroy chidren
1789
+ // disconnect from parent and destroy children
1790
1790
  this.destroyed = 1;
1791
1791
  this.parent && this.parent.removeChild(this);
1792
1792
  for (const child of this.children)
@@ -1811,7 +1811,7 @@ class EngineObject
1811
1811
 
1812
1812
  /** Called to check if a tile collision should be resolved
1813
1813
  * @param {Number} tileData - the value of the tile at the position
1814
- * @param {Vector2} pos - tile where the collision occured
1814
+ * @param {Vector2} pos - tile where the collision occurred
1815
1815
  * @return {Boolean} - true if the collision should be resolved */
1816
1816
  collideWithTile(tileData, pos) { return tileData > 0; }
1817
1817
 
@@ -1874,7 +1874,7 @@ class EngineObject
1874
1874
  this.collideRaycast = collideRaycast;
1875
1875
  }
1876
1876
 
1877
- /** Returns string containg info about this object for debugging
1877
+ /** Returns string containing info about this object for debugging
1878
1878
  * @return {String} */
1879
1879
  toString()
1880
1880
  {
@@ -1960,7 +1960,7 @@ let overlayContext;
1960
1960
  let mainCanvasSize = vec2();
1961
1961
 
1962
1962
  /** Array containing texture info for batch rendering system
1963
- * @type {Array}
1963
+ * @type {Array<TextureInfo>}
1964
1964
  * @memberof Draw */
1965
1965
  let textureInfos = [];
1966
1966
 
@@ -1999,11 +1999,13 @@ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
1999
1999
 
2000
2000
  // use pos as a tile index
2001
2001
  const textureInfo = textureInfos[textureIndex];
2002
- ASSERT(textureInfo, 'Texture not loaded');
2002
+ ASSERT(!!textureInfo, 'Texture not loaded');
2003
2003
  const sizePadded = size.add(vec2(padding*2));
2004
- const cols = textureInfo.size.x / sizePadded.x |0;
2005
2004
  if (typeof pos === 'number')
2006
- pos = vec2(pos%cols, pos/cols|0);
2005
+ {
2006
+ const cols = textureInfo.size.x / sizePadded.x |0;
2007
+ pos = cols>0 ? vec2(pos%cols, pos/cols|0) : vec2();
2008
+ }
2007
2009
  pos = vec2(pos.x*sizePadded.x+padding, pos.y*sizePadded.y+padding);
2008
2010
 
2009
2011
  // return a tile info object
@@ -2309,24 +2311,6 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
2309
2311
  context.restore();
2310
2312
  }
2311
2313
 
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
2314
  /** Draw text on main canvas in world space
2331
2315
  * Automatically splits new lines into rows
2332
2316
  * @param {String} text
@@ -2394,6 +2378,38 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
2394
2378
  });
2395
2379
  }
2396
2380
 
2381
+ /** Enable normal or additive blend mode
2382
+ * @param {Boolean} [additive]
2383
+ * @param {Boolean} [useWebGL=glEnable]
2384
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
2385
+ * @memberof Draw */
2386
+ function setBlendMode(additive, useWebGL=glEnable, context)
2387
+ {
2388
+ ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
2389
+ if (useWebGL)
2390
+ glAdditive = additive;
2391
+ else
2392
+ {
2393
+ if (!context)
2394
+ context = mainContext;
2395
+ context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
2396
+ }
2397
+ }
2398
+
2399
+ /** Combines all LittleJS canvases onto the main canvas and clears them
2400
+ * This is necessary for things like saving a screenshot
2401
+ * @memberof Draw */
2402
+ function combineCanvases()
2403
+ {
2404
+ // combine canvases
2405
+ glCopyToContext(mainContext, true);
2406
+ mainContext.drawImage(overlayCanvas, 0, 0);
2407
+
2408
+ // clear canvases
2409
+ glClearCanvas();
2410
+ overlayCanvas.width |= 0;
2411
+ }
2412
+
2397
2413
  ///////////////////////////////////////////////////////////////////////////////
2398
2414
 
2399
2415
  let engineFontImage;
@@ -2405,7 +2421,7 @@ let engineFontImage;
2405
2421
  * - You can also use fonts from the main tile sheet
2406
2422
  * @example
2407
2423
  * // use built in font
2408
- * const font = new ImageFont;
2424
+ * const font = new FontImage;
2409
2425
  *
2410
2426
  * // draw text
2411
2427
  * font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
@@ -2480,14 +2496,14 @@ class FontImage
2480
2496
  }
2481
2497
 
2482
2498
  ///////////////////////////////////////////////////////////////////////////////
2483
- // Fullscreen mode
2499
+ // Display functions
2484
2500
 
2485
2501
  /** Returns true if fullscreen mode is active
2486
2502
  * @return {Boolean}
2487
2503
  * @memberof Draw */
2488
2504
  function isFullscreen() { return !!document.fullscreenElement; }
2489
2505
 
2490
- /** Toggle fullsceen mode
2506
+ /** Toggle fullscreen mode
2491
2507
  * @memberof Draw */
2492
2508
  function toggleFullscreen()
2493
2509
  {
@@ -2499,6 +2515,15 @@ function toggleFullscreen()
2499
2515
  }
2500
2516
  else if (rootElement.requestFullscreen)
2501
2517
  rootElement.requestFullscreen();
2518
+ }
2519
+
2520
+ /** Set the cursor style
2521
+ * @param {String} cursorStyle - CSS cursor style (auto, none, crosshair, etc)
2522
+ * @memberof Draw */
2523
+ function setCursor(cursorStyle = 'auto')
2524
+ {
2525
+ const rootElement = mainCanvas.parentElement;
2526
+ rootElement.style.cursor = cursorStyle;
2502
2527
  }
2503
2528
  /**
2504
2529
  * LittleJS Input System
@@ -2843,7 +2868,7 @@ function vibrateStop() { vibrate(0); }
2843
2868
 
2844
2869
  /** True if a touch device has been detected
2845
2870
  * @memberof Input */
2846
- const isTouchDevice = window.ontouchstart !== undefined;
2871
+ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
2847
2872
 
2848
2873
  // touch gamepad internal variables
2849
2874
  let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
@@ -3041,7 +3066,7 @@ function audioInit()
3041
3066
  {
3042
3067
  if (!soundEnable || headlessMode) return;
3043
3068
 
3044
- // (createGain is more widely spported then GainNode construtor)
3069
+ // (createGain is more widely supported then GainNode constructor)
3045
3070
  audioGainNode = audioContext.createGain();
3046
3071
  audioGainNode.connect(audioContext.destination);
3047
3072
  audioGainNode.gain.value = soundVolume; // set starting value
@@ -3391,7 +3416,7 @@ const zzfxR = 44100;
3391
3416
  * @param {Number} [sustain] - Sustain time, how long sound holds (seconds)
3392
3417
  * @param {Number} [release] - Release time, how fast sound fades out (seconds)
3393
3418
  * @param {Number} [shape] - Shape of the sound wave
3394
- * @param {Number} [shapeCurve] - Squarenes of wave (0=square, 1=normal, 2=pointy)
3419
+ * @param {Number} [shapeCurve] - Squareness of wave (0=square, 1=normal, 2=pointy)
3395
3420
  * @param {Number} [slide] - How much to slide frequency (kHz/s)
3396
3421
  * @param {Number} [deltaSlide] - How much to change slide (kHz/s/s)
3397
3422
  * @param {Number} [pitchJump] - Frequency of pitch jump (Hz)
@@ -3403,7 +3428,7 @@ const zzfxR = 44100;
3403
3428
  * @param {Number} [delay] - Overlap sound with itself for reverb and flanger effects (seconds)
3404
3429
  * @param {Number} [sustainVolume] - Volume level for sustain (percent)
3405
3430
  * @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)
3431
+ * @param {Number} [tremolo] - Trembling effect, rate controlled by repeat time (percent)
3407
3432
  * @param {Number} [filter] - Filter cutoff frequency, positive for HPF, negative for LPF (Hz)
3408
3433
  * @return {Array} - Array of audio samples
3409
3434
  * @memberof Audio
@@ -3507,7 +3532,7 @@ function zzfxG
3507
3532
  // ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
3508
3533
 
3509
3534
  /** Generate samples for a ZzFM song with given parameters
3510
- * @param {Array} instruments - Array of ZzFX sound paramaters
3535
+ * @param {Array} instruments - Array of ZzFX sound parameters
3511
3536
  * @param {Array} patterns - Array of pattern data
3512
3537
  * @param {Array} sequence - Array of pattern indexes
3513
3538
  * @param {Number} [BPM] - Playback speed of the song in BPM
@@ -3910,7 +3935,7 @@ class TileLayer extends EngineObject
3910
3935
 
3911
3936
  /** Draw the tile at a given position in the tile grid
3912
3937
  * This can be used to clear out tiles when they are destroyed
3913
- * Tiles can also be redrawn if isinde a redrawStart/End block
3938
+ * Tiles can also be redrawn if inside a redrawStart/End block
3914
3939
  * @param {Vector2} layerPos
3915
3940
  * @param {Boolean} [clear] - should the old tile be cleared out
3916
3941
  */
@@ -3935,7 +3960,7 @@ class TileLayer extends EngineObject
3935
3960
  }
3936
3961
  }
3937
3962
 
3938
- /** Draw directly to the 2D canvas in world space (bipass webgl)
3963
+ /** Draw directly to the 2D canvas in world space (bypass webgl)
3939
3964
  * @param {Vector2} pos
3940
3965
  * @param {Vector2} size
3941
3966
  * @param {Number} angle
@@ -4005,7 +4030,7 @@ class TileLayer extends EngineObject
4005
4030
  * let pos = vec2(2,3);
4006
4031
  * let particleEmitter = new ParticleEmitter
4007
4032
  * (
4008
- * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
4033
+ * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emitCone
4009
4034
  * tile(0, 16), // tileInfo
4010
4035
  * rgb(1,1,1), rgb(0,0,0), // colorStartA, colorStartB
4011
4036
  * rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
@@ -4040,7 +4065,7 @@ class ParticleEmitter extends EngineObject
4040
4065
  * @param {Number} [fadeRate] - How quick to fade particles at start/end in percent of life
4041
4066
  * @param {Number} [randomness] - Apply extra randomness percent
4042
4067
  * @param {Boolean} [collideTiles] - Do particles collide against tiles
4043
- * @param {Boolean} [additive] - Should particles use addtive blend
4068
+ * @param {Boolean} [additive] - Should particles use additive blend
4044
4069
  * @param {Boolean} [randomColorLinear] - Should color be randomized linearly or across each component
4045
4070
  * @param {Number} [renderOrder] - Render order for particles (additive is above other stuff by default)
4046
4071
  * @param {Boolean} [localSpace] - Should it be in local space of emitter (world space is default)
@@ -4125,11 +4150,11 @@ class ParticleEmitter extends EngineObject
4125
4150
  this.randomness = randomness;
4126
4151
  /** @property {Boolean} - Do particles collide against tiles */
4127
4152
  this.collideTiles = collideTiles;
4128
- /** @property {Boolean} - Should particles use addtive blend */
4153
+ /** @property {Boolean} - Should particles use additive blend */
4129
4154
  this.additive = additive;
4130
4155
  /** @property {Boolean} - Should it be in local space of emitter */
4131
4156
  this.localSpace = localSpace;
4132
- /** @property {Number} - If non zero the partile is drawn as a trail, stretched in the drection of velocity */
4157
+ /** @property {Number} - If non zero the particle is drawn as a trail, stretched in the direction of velocity */
4133
4158
  this.trailScale = 0;
4134
4159
  /** @property {Function} - Callback when particle is destroyed */
4135
4160
  this.particleDestroyCallback = undefined;
@@ -4178,7 +4203,7 @@ class ParticleEmitter extends EngineObject
4178
4203
  angle += this.angle;
4179
4204
  }
4180
4205
 
4181
- // randomness scales each paremeter by a percentage
4206
+ // randomness scales each parameter by a percentage
4182
4207
  const randomness = this.randomness;
4183
4208
  const randomizeScale = (v)=> v + v*rand(randomness, -randomness);
4184
4209
 
@@ -4207,7 +4232,7 @@ class ParticleEmitter extends EngineObject
4207
4232
  particle.renderOrder = this.renderOrder;
4208
4233
  particle.mirror = !!randInt(2);
4209
4234
 
4210
- // call particle create callaback
4235
+ // call particle create callback
4211
4236
  this.particleCreateCallback && this.particleCreateCallback(particle);
4212
4237
 
4213
4238
  // return the newly created particle
@@ -4527,7 +4552,7 @@ let glCanvas;
4527
4552
  * @memberof WebGL */
4528
4553
  let glContext;
4529
4554
 
4530
- /** Shoule webgl be setup with antialiasing, must be set before calling engineInit
4555
+ /** Should webgl be setup with anti-aliasing? must be set before calling engineInit
4531
4556
  * @type {Boolean}
4532
4557
  * @memberof WebGL */
4533
4558
  let glAntialias = true;
@@ -4535,9 +4560,15 @@ let glAntialias = true;
4535
4560
  // WebGL internal variables not exposed to documentation
4536
4561
  let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glInstanceCount, glAdditive, glBatchAdditive;
4537
4562
 
4563
+ // WebGL internal constants
4564
+ const gl_MAX_INSTANCES = 1e4;
4565
+ const gl_INDICES_PER_INSTANCE = 11;
4566
+ const gl_INSTANCE_BYTE_STRIDE = gl_INDICES_PER_INSTANCE * 4;
4567
+ const gl_INSTANCE_BUFFER_SIZE = gl_MAX_INSTANCES * gl_INSTANCE_BYTE_STRIDE;
4568
+
4538
4569
  ///////////////////////////////////////////////////////////////////////////////
4539
4570
 
4540
- // Initalize WebGL, called automatically by the engine
4571
+ // Initialize WebGL, called automatically by the engine
4541
4572
  function glInit()
4542
4573
  {
4543
4574
  if (!glEnable || headlessMode) return;
@@ -4587,8 +4618,8 @@ function glInit()
4587
4618
 
4588
4619
  // create the geometry buffer, triangle strip square
4589
4620
  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);
4621
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
4622
+ glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
4592
4623
  }
4593
4624
 
4594
4625
  // Setup render each frame, called automatically by engine
@@ -4596,15 +4627,12 @@ function glPreRender()
4596
4627
  {
4597
4628
  if (!glEnable || headlessMode) return;
4598
4629
 
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
4630
+ // set up the shader and canvas
4631
+ glClearCanvas();
4604
4632
  glContext.useProgram(glShader);
4605
- glContext.activeTexture(gl_TEXTURE0);
4633
+ glContext.activeTexture(glContext.TEXTURE0);
4606
4634
  if (textureInfos[0])
4607
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
4635
+ glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
4608
4636
 
4609
4637
  // set vertex attributes
4610
4638
  let offset = glAdditive = glBatchAdditive = 0;
@@ -4619,15 +4647,15 @@ function glPreRender()
4619
4647
  glContext.vertexAttribDivisor(location, divisor);
4620
4648
  offset += size*typeSize;
4621
4649
  }
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
4650
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
4651
+ initVertexAttribArray('g', glContext.FLOAT, 0, 2); // geometry
4652
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
4653
+ glContext.bufferData(glContext.ARRAY_BUFFER, gl_INSTANCE_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
4654
+ initVertexAttribArray('p', glContext.FLOAT, 4, 4); // position & size
4655
+ initVertexAttribArray('u', glContext.FLOAT, 4, 4); // texture coords
4656
+ initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
4657
+ initVertexAttribArray('a', glContext.UNSIGNED_BYTE, 1, 4); // additiveColor
4658
+ initVertexAttribArray('r', glContext.FLOAT, 4, 1); // rotation
4631
4659
 
4632
4660
  // build the transform matrix
4633
4661
  const s = vec2(2*cameraScale).divide(mainCanvasSize);
@@ -4642,6 +4670,15 @@ function glPreRender()
4642
4670
  );
4643
4671
  }
4644
4672
 
4673
+ /** Clear the canvas and setup the viewport
4674
+ * @memberof WebGL */
4675
+ function glClearCanvas()
4676
+ {
4677
+ // clear and set to same size as main canvas
4678
+ glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
4679
+ glContext.clear(glContext.COLOR_BUFFER_BIT);
4680
+ }
4681
+
4645
4682
  /** Set the WebGl texture, called automatically if using multiple textures
4646
4683
  * - This may also flush the gl buffer resulting in more draw calls and worse performance
4647
4684
  * @param {WebGLTexture} texture
@@ -4653,7 +4690,7 @@ function glSetTexture(texture)
4653
4690
  return;
4654
4691
 
4655
4692
  glFlush();
4656
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = texture);
4693
+ glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture = texture);
4657
4694
  }
4658
4695
 
4659
4696
  /** Compile WebGL shader of the given type, will throw errors if in debug mode
@@ -4669,7 +4706,7 @@ function glCompileShader(source, type)
4669
4706
  glContext.compileShader(shader);
4670
4707
 
4671
4708
  // check for errors
4672
- if (debug && !glContext.getShaderParameter(shader, gl_COMPILE_STATUS))
4709
+ if (debug && !glContext.getShaderParameter(shader, glContext.COMPILE_STATUS))
4673
4710
  throw glContext.getShaderInfoLog(shader);
4674
4711
  return shader;
4675
4712
  }
@@ -4683,12 +4720,12 @@ function glCreateProgram(vsSource, fsSource)
4683
4720
  {
4684
4721
  // build the program
4685
4722
  const program = glContext.createProgram();
4686
- glContext.attachShader(program, glCompileShader(vsSource, gl_VERTEX_SHADER));
4687
- glContext.attachShader(program, glCompileShader(fsSource, gl_FRAGMENT_SHADER));
4723
+ glContext.attachShader(program, glCompileShader(vsSource, glContext.VERTEX_SHADER));
4724
+ glContext.attachShader(program, glCompileShader(fsSource, glContext.FRAGMENT_SHADER));
4688
4725
  glContext.linkProgram(program);
4689
4726
 
4690
4727
  // check for errors
4691
- if (debug && !glContext.getProgramParameter(program, gl_LINK_STATUS))
4728
+ if (debug && !glContext.getProgramParameter(program, glContext.LINK_STATUS))
4692
4729
  throw glContext.getProgramInfoLog(program);
4693
4730
  return program;
4694
4731
  }
@@ -4701,20 +4738,20 @@ function glCreateTexture(image)
4701
4738
  {
4702
4739
  // build the texture
4703
4740
  const texture = glContext.createTexture();
4704
- glContext.bindTexture(gl_TEXTURE_2D, texture);
4741
+ glContext.bindTexture(glContext.TEXTURE_2D, texture);
4705
4742
  if (image && image.width)
4706
- glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
4743
+ glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
4707
4744
  else
4708
4745
  {
4709
4746
  // create a white texture
4710
4747
  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);
4748
+ glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
4712
4749
  }
4713
4750
 
4714
4751
  // 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);
4752
+ const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
4753
+ glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
4754
+ glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, filter);
4718
4755
  return texture;
4719
4756
  }
4720
4757
 
@@ -4724,20 +4761,20 @@ function glFlush()
4724
4761
  {
4725
4762
  if (!glInstanceCount) return;
4726
4763
 
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);
4764
+ const destBlend = glBatchAdditive ? glContext.ONE : glContext.ONE_MINUS_SRC_ALPHA;
4765
+ glContext.blendFuncSeparate(glContext.SRC_ALPHA, destBlend, glContext.ONE, destBlend);
4766
+ glContext.enable(glContext.BLEND);
4730
4767
 
4731
4768
  // 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);
4769
+ glContext.bufferSubData(glContext.ARRAY_BUFFER, 0, glPositionData);
4770
+ glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP, 0, 4, glInstanceCount);
4734
4771
  if (showWatermark)
4735
4772
  drawCount += glInstanceCount;
4736
4773
  glInstanceCount = 0;
4737
4774
  glBatchAdditive = glAdditive;
4738
4775
  }
4739
4776
 
4740
- /** Draw any sprites still in the buffer, copy to main canvas and clear
4777
+ /** Draw any sprites still in the buffer and copy to main canvas
4741
4778
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
4742
4779
  * @param {Boolean} [forceDraw]
4743
4780
  * @memberof WebGL */
@@ -4752,7 +4789,7 @@ function glCopyToContext(context, forceDraw=false)
4752
4789
  context.drawImage(glCanvas, 0, 0);
4753
4790
  }
4754
4791
 
4755
- /** Set antialiasing for webgl canvas
4792
+ /** Set anti-aliasing for webgl canvas
4756
4793
  * @param {Boolean} [antialias]
4757
4794
  * @memberof WebGL */
4758
4795
  function glSetAntialias(antialias=true)
@@ -4782,7 +4819,7 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
4782
4819
  if (glInstanceCount >= gl_MAX_INSTANCES || glBatchAdditive != glAdditive)
4783
4820
  glFlush();
4784
4821
 
4785
- let offset = glInstanceCount * gl_INDICIES_PER_INSTANCE;
4822
+ let offset = glInstanceCount++ * gl_INDICES_PER_INSTANCE;
4786
4823
  glPositionData[offset++] = x;
4787
4824
  glPositionData[offset++] = y;
4788
4825
  glPositionData[offset++] = sizeX;
@@ -4794,41 +4831,7 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
4794
4831
  glColorData[offset++] = rgba;
4795
4832
  glColorData[offset++] = rgbaAdditive;
4796
4833
  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;
4834
+ }
4832
4835
  /**
4833
4836
  * LittleJS - The Tiny Fast JavaScript Game Engine
4834
4837
  * MIT License - Copyright 2021 Frank Force
@@ -4861,7 +4864,7 @@ const engineName = 'LittleJS';
4861
4864
  * @type {String}
4862
4865
  * @default
4863
4866
  * @memberof Engine */
4864
- const engineVersion = '1.11.2';
4867
+ const engineVersion = '1.11.4';
4865
4868
 
4866
4869
  /** Frames per second to update
4867
4870
  * @type {Number}
@@ -4944,6 +4947,7 @@ function engineAddPlugin(updateFunction, renderFunction)
4944
4947
  * @memberof Engine */
4945
4948
  function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
4946
4949
  {
4950
+ ASSERT(!mainContext, 'engine already initialized');
4947
4951
  ASSERT(Array.isArray(imageSources), 'pass in images as array');
4948
4952
 
4949
4953
  // Called automatically by engine to setup render system
@@ -5110,7 +5114,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5110
5114
  (canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
5111
5115
  'user-select:none;' + // prevent hold to select
5112
5116
  '-webkit-user-select:none;' + // compatibility for ios
5113
- (!touchInputEnable ? '' : // no touch css setttings
5117
+ (!touchInputEnable ? '' : // no touch css settings
5114
5118
  'touch-action:none;' + // prevent mobile pinch to resize
5115
5119
  '-webkit-touch-callout:none');// compatibility for ios
5116
5120
  rootElement.style.cssText = styleRoot;
@@ -5139,6 +5143,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5139
5143
  new Promise(resolve =>
5140
5144
  {
5141
5145
  const image = new Image;
5146
+ image.crossOrigin = 'anonymous';
5142
5147
  image.onerror = image.onload = ()=>
5143
5148
  {
5144
5149
  textureInfos[textureIndex] = new TextureInfo(image);
@@ -5183,7 +5188,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5183
5188
  * @memberof Engine */
5184
5189
  function engineObjectsUpdate()
5185
5190
  {
5186
- // get list of solid objects for physics optimzation
5191
+ // get list of solid objects for physics optimization
5187
5192
  engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
5188
5193
 
5189
5194
  // recursive object update