littlejsengine 1.7.21 → 1.8.3

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 (52) hide show
  1. package/README.md +28 -55
  2. package/build/littlejs.d.ts +595 -552
  3. package/build/littlejs.esm.js +626 -535
  4. package/build/littlejs.esm.min.js +1 -1
  5. package/build/littlejs.js +542 -264
  6. package/build/littlejs.min.js +1 -1
  7. package/build/littlejs.release.js +542 -264
  8. package/examples/breakout/game.js +17 -14
  9. package/examples/breakout/gameObjects.js +29 -8
  10. package/examples/breakout/index.html +3 -3
  11. package/examples/breakoutTutorial/README.md +3 -3
  12. package/examples/breakoutTutorial/game.js +4 -4
  13. package/examples/breakoutTutorial/index.html +2 -2
  14. package/examples/electron/game.js +3 -2
  15. package/examples/electron/index.html +2 -2
  16. package/examples/empty/game.js +1 -1
  17. package/examples/favicon.png +0 -0
  18. package/examples/js13k/game.js +20 -15
  19. package/examples/js13k/index.html +14 -14
  20. package/examples/module/game.js +5 -4
  21. package/examples/module/index.html +1 -1
  22. package/examples/particles/index.html +19 -22
  23. package/examples/platformer/game.js +3 -3
  24. package/examples/platformer/gameEffects.js +21 -19
  25. package/examples/platformer/gameLevel.js +6 -6
  26. package/examples/platformer/gameObjects.js +18 -18
  27. package/examples/platformer/gamePlayer.js +4 -3
  28. package/examples/platformer/index.html +6 -6
  29. package/examples/platformer/tiles.png +0 -0
  30. package/examples/platformer/tilesLevel.png +0 -0
  31. package/examples/puzzle/game.js +10 -8
  32. package/examples/puzzle/index.html +2 -2
  33. package/examples/screenshot.jpg +0 -0
  34. package/examples/starter/game.js +32 -21
  35. package/examples/starter/index.html +13 -13
  36. package/examples/starter/tiles.png +0 -0
  37. package/examples/stress/index.html +4 -4
  38. package/examples/typescript/game.js +4 -3
  39. package/examples/typescript/game.ts +5 -4
  40. package/examples/typescript/index.html +1 -1
  41. package/package.json +2 -1
  42. package/src/engine.js +59 -52
  43. package/src/engineAudio.js +2 -1
  44. package/src/engineDraw.js +171 -55
  45. package/src/engineExport.js +84 -271
  46. package/src/engineMedals.js +13 -45
  47. package/src/engineObject.js +13 -13
  48. package/src/engineParticles.js +17 -17
  49. package/src/engineSettings.js +195 -2
  50. package/src/engineTileLayer.js +23 -22
  51. package/src/engineUtilities.js +6 -6
  52. package/src/engineWebGL.js +43 -50
package/build/littlejs.js CHANGED
@@ -617,10 +617,10 @@ function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear)
617
617
  * - Can be used to create a deterministic random number sequence
618
618
  * @example
619
619
  * let r = new RandomGenerator(123); // random number generator with seed 123
620
- * let a = r.rand(); // random value between 0 and 1
621
- * let b = r.randInt(10); // random integer between 0 and 9
620
+ * let a = r.float(); // random value between 0 and 1
621
+ * let b = r.int(10); // random integer between 0 and 9
622
622
  * r.seed = 123; // reset the seed
623
- * let c = r.rand(); // the same value as a
623
+ * let c = r.float(); // the same value as a
624
624
  */
625
625
  class RandomGenerator
626
626
  {
@@ -649,18 +649,18 @@ class RandomGenerator
649
649
  * @param {Number} valueA
650
650
  * @param {Number} [valueB=0]
651
651
  * @return {Number} */
652
- int(valueA, valueB=0) { return Math.floor(this.rand(valueA, valueB)); }
652
+ int(valueA, valueB=0) { return Math.floor(this.float(valueA, valueB)); }
653
653
 
654
654
  /** Randomly returns either -1 or 1 deterministically
655
655
  * @return {Number} */
656
- sign() { return this.randInt(2) * 2 - 1; }
656
+ sign() { return this.int(2) * 2 - 1; }
657
657
  }
658
658
 
659
659
  ///////////////////////////////////////////////////////////////////////////////
660
660
 
661
661
  /**
662
662
  * Create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
663
- * @param {Number} [x=0]
663
+ * @param {(Number|Vector2)} [x=0]
664
664
  * @param {Number} [y=0]
665
665
  * @return {Vector2}
666
666
  * @example
@@ -1144,7 +1144,7 @@ let glOverlay = 1;
1144
1144
  * @memberof Settings */
1145
1145
  let tileSizeDefault = vec2(16);
1146
1146
 
1147
- /** Prevent tile bleeding from neighbors in pixels
1147
+ /** How many pixels smaller to draw tiles to prevent bleeding from neighbors
1148
1148
  * @type {Number}
1149
1149
  * @default
1150
1150
  * @memberof Settings */
@@ -1318,7 +1318,200 @@ let medalDisplayIconSize = 50;
1318
1318
  * @type {Boolean}
1319
1319
  * @default 0
1320
1320
  * @memberof Settings */
1321
- let medalsPreventUnlock;
1321
+ let medalsPreventUnlock;
1322
+
1323
+ ///////////////////////////////////////////////////////////////////////////////
1324
+ // Setters for global variables
1325
+
1326
+ /** Set position of camera in world space
1327
+ * @param {Vector2} pos
1328
+ * @memberof Settings */
1329
+ function setCameraPos(pos) { cameraPos = pos; }
1330
+
1331
+ /** Set scale of camera in world space
1332
+ * @param {Number} scale
1333
+ * @memberof Settings */
1334
+ function setCameraScale(scale) { cameraScale = scale; }
1335
+
1336
+ /** Set max size of the canvas
1337
+ * @param {Vector2} size
1338
+ * @memberof Settings */
1339
+ function setCanvasMaxSize(size) { canvasMaxSize = size; }
1340
+
1341
+ /** Set fixed size of the canvas
1342
+ * @param {Vector2} size
1343
+ * @memberof Settings */
1344
+ function setCanvasFixedSize(size) { canvasFixedSize = size; }
1345
+
1346
+ /** Disables anti aliasing for pixel art if true
1347
+ * @param {Boolean} pixelated
1348
+ * @memberof Settings */
1349
+ function setCanvasPixelated(pixelated) { canvasPixelated = pixelated; }
1350
+
1351
+ /** Set default font used for text rendering
1352
+ * @param {String} font
1353
+ * @memberof Settings */
1354
+ function setFontDefault(font) { fontDefault = font; }
1355
+
1356
+ /** Set if webgl rendering is enabled
1357
+ * @param {Boolean} enable
1358
+ * @memberof Settings */
1359
+ function setGlEnable(enable) { glEnable = enable; }
1360
+
1361
+ /** Set to not composite the WebGL canvas
1362
+ * @param {Boolean} overlay
1363
+ * @memberof Settings */
1364
+ function setGlOverlay(overlay) { glOverlay = overlay; }
1365
+
1366
+ /** Set default size of tiles in pixels
1367
+ * @param {Vector2} size
1368
+ * @memberof Settings */
1369
+ function setTileSizeDefault(size) { tileSizeDefault = size; }
1370
+
1371
+ /** Set to prevent tile bleeding from neighbors in pixels
1372
+ * @param {Number} scale
1373
+ * @memberof Settings */
1374
+ function setTileFixBleedScale(scale) { tileFixBleedScale = scale; }
1375
+
1376
+ /** Set if collisions between objects are enabled
1377
+ * @param {Boolean} enable
1378
+ * @memberof Settings */
1379
+ function setEnablePhysicsSolver(enable) { enablePhysicsSolver = enable; }
1380
+
1381
+ /** Set default object mass for collison calcuations
1382
+ * @param {Number} mass
1383
+ * @memberof Settings */
1384
+ function setObjectDefaultMass(mass) { objectDefaultMass = mass; }
1385
+
1386
+ /** Set how much to slow velocity by each frame
1387
+ * @param {Number} damping
1388
+ * @memberof Settings */
1389
+ function setObjectDefaultDamping(damp) { objectDefaultDamping = damp; }
1390
+
1391
+ /** Set how much to slow angular velocity each frame
1392
+ * @param {Number} damping
1393
+ * @memberof Settings */
1394
+ function setObjectDefaultAngleDamping(damp) { objectDefaultAngleDamping = damp; }
1395
+
1396
+ /** Set how much to bounce when a collision occur
1397
+ * @param {Number} elasticity
1398
+ * @memberof Settings */
1399
+ function setObjectDefaultElasticity(elasticity) { objectDefaultElasticity = elasticity; }
1400
+
1401
+ /** Set how much to slow when touching
1402
+ * @param {Number} friction
1403
+ * @memberof Settings */
1404
+ function setObjectDefaultFriction(friction) { objectDefaultFriction = friction; }
1405
+
1406
+ /** Set max speed to avoid fast objects missing collisions
1407
+ * @param {Number} speed
1408
+ * @memberof Settings */
1409
+ function setObjectMaxSpeed(speed) { objectMaxSpeed = speed; }
1410
+
1411
+ /** Set how much gravity to apply to objects along the Y axis
1412
+ * @param {Number} gravity
1413
+ * @memberof Settings */
1414
+ function setGravity(g) { gravity = g; }
1415
+
1416
+ /** Set to scales emit rate of particles
1417
+ * @param {Number} scale
1418
+ * @memberof Settings */
1419
+ function setParticleEmitRateScale(scale) { particleEmitRateScale = scale; }
1420
+
1421
+ /** Set if gamepads are enabled
1422
+ * @param {Boolean} enable
1423
+ * @memberof Settings */
1424
+ function setGamepadsEnable(enable) { gamepadsEnable = enable; }
1425
+
1426
+ /** Set if the dpad input is also routed to the left analog stick
1427
+ * @param {Boolean} enable
1428
+ * @memberof Settings */
1429
+ function setGamepadDirectionEmulateStick(enable) { gamepadDirectionEmulateStick = enable; }
1430
+
1431
+ /** Set if true the WASD keys are also routed to the direction keys
1432
+ * @param {Boolean} enable
1433
+ * @memberof Settings */
1434
+ function setInputWASDEmulateDirection(enable) { inputWASDEmulateDirection = enable; }
1435
+
1436
+ /** Set if touch gamepad should appear on mobile devices
1437
+ * @param {Boolean} enable
1438
+ * @memberof Settings */
1439
+ function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
1440
+
1441
+ /** Set if touch gamepad should be analog stick or 8 way dpad
1442
+ * @param {Boolean} analog
1443
+ * @memberof Settings */
1444
+ function setTouchGamepadAnalog(analog) { touchGamepadAnalog = analog; }
1445
+
1446
+ /** Set size of virutal gamepad for touch devices in pixels
1447
+ * @param {Number} size
1448
+ * @memberof Settings */
1449
+ function setTouchGamepadSize(size) { touchGamepadSize = size; }
1450
+
1451
+ /** Set transparency of touch gamepad overlay
1452
+ * @param {Number} alpha
1453
+ * @memberof Settings */
1454
+ function setTouchGamepadAlpha(alpha) { touchGamepadAlpha = alpha; }
1455
+
1456
+ /** Set to allow vibration hardware if it exists
1457
+ * @param {Boolean} enable
1458
+ * @memberof Settings */
1459
+ function setVibrateEnable(enable) { vibrateEnable = enable; }
1460
+
1461
+ /** Set to disable all audio code
1462
+ * @param {Boolean} enable
1463
+ * @memberof Settings */
1464
+ function setSoundEnable(enable) { soundEnable = enable; }
1465
+
1466
+ /** Set volume scale to apply to all sound, music and speech
1467
+ * @param {Number} volume
1468
+ * @memberof Settings */
1469
+ function setSoundVolume(volume) { soundVolume = volume; }
1470
+
1471
+ /** Set default range where sound no longer plays
1472
+ * @param {Number} range
1473
+ * @memberof Settings */
1474
+ function setSoundDefaultRange(range) { soundDefaultRange = range; }
1475
+
1476
+ /** Set default range percent to start tapering off sound
1477
+ * @param {Number} taper
1478
+ * @memberof Settings */
1479
+ function setSoundDefaultTaper(taper) { soundDefaultTaper = taper; }
1480
+
1481
+ /** Set how long to show medals for in seconds
1482
+ * @param {Number} time
1483
+ * @memberof Settings */
1484
+ function setMedalDisplayTime(time) { medalDisplayTime = time; }
1485
+
1486
+ /** Set how quickly to slide on/off medals in seconds
1487
+ * @param {Number} time
1488
+ * @memberof Settings */
1489
+ function setMedalDisplaySlideTime(time) { medalDisplaySlideTime = time; }
1490
+
1491
+ /** Set size of medal display
1492
+ * @param {Vector2} size
1493
+ * @memberof Settings */
1494
+ function setMedalDisplaySize(size) { medalDisplaySize = size; }
1495
+
1496
+ /** Set size of icon in medal display
1497
+ * @param {Number} size
1498
+ * @memberof Settings */
1499
+ function setMedalDisplayIconSize(size) { medalDisplayIconSize = size; }
1500
+
1501
+ /** Set to stop medals from being unlockable
1502
+ * @param {Boolean} preventUnlock
1503
+ * @memberof Settings */
1504
+ function setMedalsPreventUnlock(preventUnlock) { medalsPreventUnlock = preventUnlock; }
1505
+
1506
+ /** Set if watermark with FPS should be shown
1507
+ * @param {Boolean} show
1508
+ * @memberof Debug */
1509
+ function setShowWatermark(show) { showWatermark = show; }
1510
+
1511
+ /** Set key code used to toggle debug mode, Esc by default
1512
+ * @param {Number} key
1513
+ * @memberof Debug */
1514
+ function setDebugKey(key) { debugKey = key; }
1322
1515
  /**
1323
1516
  * LittleJS Object System
1324
1517
  */
@@ -1353,18 +1546,20 @@ let medalsPreventUnlock;
1353
1546
  class EngineObject
1354
1547
  {
1355
1548
  /** Create an engine object and adds it to the list of objects
1356
- * @param {Vector2} [position=Vector2()] - World space position of the object
1357
- * @param {Vector2} [size=Vector2(1,1)] - World space size of the object
1358
- * @param {Number} [tileIndex=-1] - Tile to use to render object (-1 is untextured)
1359
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
1360
- * @param {Number} [angle=0] - Angle the object is rotated by
1361
- * @param {Color} [color=Color()] - Color to apply to tile when rendered
1362
- * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
1549
+ * @param {Vector2} [pos=Vector2()] - World space position of the object
1550
+ * @param {Vector2} [size=Vector2(1,1)] - World space size of the object
1551
+ * @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
1552
+ * @param {Number} [angle=0] - Angle the object is rotated by
1553
+ * @param {Color} [color=Color()] - Color to apply to tile when rendered
1554
+ * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
1363
1555
  */
1364
- constructor(pos=vec2(), size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, angle=0, color, renderOrder=0)
1556
+ constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color, renderOrder=0)
1365
1557
  {
1366
1558
  // set passed in params
1367
1559
  ASSERT(isVector2(pos) && isVector2(size)); // ensure pos and size are vec2s
1560
+ ASSERT(typeof tileInfo !== 'number' || !tileInfo); // prevent old style calls
1561
+ ASSERT(!(renderOrder instanceof Color)); // prevent old style calls
1562
+ // to fix old calls, replace with tile(tileIndex, tileSize)
1368
1563
 
1369
1564
  /** @property {Vector2} - World space position of the object */
1370
1565
  this.pos = pos.copy();
@@ -1372,10 +1567,8 @@ class EngineObject
1372
1567
  this.size = size;
1373
1568
  /** @property {Vector2} - Size of object used for drawing, uses size if not set */
1374
1569
  this.drawSize;
1375
- /** @property {Number} - Tile to use to render object (-1 is untextured) */
1376
- this.tileIndex = tileIndex;
1377
- /** @property {Vector2} - Size of tile in source pixels */
1378
- this.tileSize = tileSize;
1570
+ /** @property {TileInfo} - Tile info to render object (undefined is untextured) */
1571
+ this.tileInfo = tileInfo;
1379
1572
  /** @property {Number} - Angle to rotate the object */
1380
1573
  this.angle = angle;
1381
1574
  /** @property {Color} - Color to apply when rendered */
@@ -1591,7 +1784,7 @@ class EngineObject
1591
1784
  render()
1592
1785
  {
1593
1786
  // default object render
1594
- drawTile(this.pos, this.drawSize || this.size, this.tileIndex, this.tileSize, this.color, this.angle, this.mirror, this.additiveColor);
1787
+ drawTile(this.pos, this.drawSize || this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
1595
1788
  }
1596
1789
 
1597
1790
  /** Destroy this object, destroy it's children, detach it's parent, and mark it for removal */
@@ -1746,13 +1939,109 @@ let overlayContext;
1746
1939
  * @memberof Draw */
1747
1940
  let mainCanvasSize = vec2();
1748
1941
 
1749
- /** Tile sheet for batch rendering system
1750
- * @type {CanvasImageSource}
1942
+ /** Array containing texture info for batch rendering system
1943
+ * @type {Array}
1751
1944
  * @memberof Draw */
1752
- const tileImage = new Image;
1945
+ let textureInfos = [];
1753
1946
 
1754
1947
  // Engine internal variables not exposed to documentation
1755
- let tileImageSize, tileImageFixBleed, drawCount;
1948
+ let drawCount;
1949
+
1950
+ ///////////////////////////////////////////////////////////////////////////////
1951
+
1952
+ /**
1953
+ * Create a tile info object
1954
+ * - This can take vecs or floats for easier use and conversion
1955
+ * - If an index is passed in, the tile size and index will determine the position
1956
+ * @param {(Number|Vector2)} [pos=Vector2()] - Top left corner of tile in pixels or index
1957
+ * @param {(Number|Vector2)} [size=tileSizeDefault] - Size of tile in pixels
1958
+ * @param {Number} [textureIndex=0] - Texture index to use
1959
+ * @return {TileInfo}
1960
+ * @example
1961
+ * tile(2) // a tile at index 2 using the default tile size of 16
1962
+ * tile(5, 8) // a tile at index 5 using a tile size of 8
1963
+ * tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
1964
+ * tile(vec2(4,8), vec2(30,10)) // a tile at pixel location (4,8) with a size of (30,10)
1965
+ * @memberof Draw
1966
+ */
1967
+ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
1968
+ {
1969
+ // if size is a number, make it a vector
1970
+ if (size.x == undefined)
1971
+ {
1972
+ ASSERT(size > 0);
1973
+ size = vec2(size);
1974
+ }
1975
+
1976
+ // if pos is a number, use it as a tile index
1977
+ if (pos.x == undefined)
1978
+ {
1979
+ const textureInfo = textureInfos[textureIndex];
1980
+ if (textureInfo)
1981
+ {
1982
+ const cols = textureInfo.size.x / size.x |0;
1983
+ pos = vec2((pos%cols)*size.x, (pos/cols|0)*size.y);
1984
+ }
1985
+ else
1986
+ pos = vec2();
1987
+ }
1988
+
1989
+ // return a tile info object
1990
+ return new TileInfo(pos, size, textureIndex);
1991
+ }
1992
+
1993
+ /**
1994
+ * Tile Info - Stores info about how to draw a tile
1995
+ */
1996
+ class TileInfo
1997
+ {
1998
+ /** Create a tile info object
1999
+ * @param {Vector2} [pos=Vector2()] - Top left corner of tile in pixels
2000
+ * @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
2001
+ * @param {Number} [textureIndex=0] - Texture index to use
2002
+ */
2003
+ constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
2004
+ {
2005
+ /** @property {Vector2} - Top left corner of tile in pixels */
2006
+ this.pos = pos;
2007
+ /** @property {Vector2} - Size of tile in pixels */
2008
+ this.size = size;
2009
+ /** @property {Number} - Texture index to use */
2010
+ this.textureIndex = textureIndex;
2011
+ }
2012
+
2013
+ /** Returns an offset copy of this tile, useful for animation
2014
+ * @param {Vector2} offset - Offset to apply in pixels
2015
+ * @return {TileInfo}
2016
+ */
2017
+ offset(offset)
2018
+ { return new TileInfo(this.pos.add(offset), this.size, this.textureIndex); }
2019
+
2020
+ /** Returns the texture info for this tile
2021
+ * @return {TextureInfo}
2022
+ */
2023
+ getTextureInfo()
2024
+ { return textureInfos[this.textureIndex]; }
2025
+ }
2026
+
2027
+ /** Texture Info - Stores info about each texture */
2028
+ class TextureInfo
2029
+ {
2030
+ // create a TextureInfo, called automatically by the engine
2031
+ constructor(image)
2032
+ {
2033
+ /** @property {CanvasImageSource} - image source */
2034
+ this.image = image;
2035
+ /** @property {Vector2} - size of the image */
2036
+ this.size = vec2(image.width, image.height);
2037
+ /** @property {WebGLTexture} - webgl texture */
2038
+ this.glTexture = glEnable && glCreateTexture(image);
2039
+ /** @property {Vector2} - size to adjust tile to fix bleeding */
2040
+ this.fixBleedSize = vec2(tileFixBleedScale).divide(this.size);
2041
+ }
2042
+ }
2043
+
2044
+ ///////////////////////////////////////////////////////////////////////////////
1756
2045
 
1757
2046
  /** Convert from screen to world space coordinates
1758
2047
  * @param {Vector2} screenPos
@@ -1783,7 +2072,7 @@ function worldToScreen(worldPos)
1783
2072
  /** Draw textured tile centered in world space, with color applied if using WebGL
1784
2073
  * @param {Vector2} pos - Center of the tile in world space
1785
2074
  * @param {Vector2} [size=Vector2(1,1)] - Size of the tile in world space
1786
- * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
2075
+ * @param {TileInfo}[tileInfo] - Tile info to use, untextured if undefined
1787
2076
  * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
1788
2077
  * @param {Color} [color=Color()] - Color to modulate with
1789
2078
  * @param {Number} [angle=0] - Angle to rotate by
@@ -1791,13 +2080,18 @@ function worldToScreen(worldPos)
1791
2080
  * @param {Color} [additiveColor=Color(0,0,0,0)] - Additive color to be applied
1792
2081
  * @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
1793
2082
  * @param {Boolean} [screenSpace=0] - If true the pos and size are in screen space
2083
+ * @param {CanvasRenderingContext2D} [context] - Canvas 2D context to draw to
1794
2084
  * @memberof Draw */
1795
- function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color,
1796
- angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace)
2085
+ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
2086
+ angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace, context)
1797
2087
  {
2088
+ ASSERT(!context || !useWebGL); // context only supported in canvas 2D mode
2089
+ ASSERT(typeof tileInfo !== 'number' || !tileInfo); // prevent old style calls
2090
+ // to fix old calls, replace with tile(tileIndex, tileSize)
2091
+
1798
2092
  showWatermark && ++drawCount;
1799
-
1800
- if (glEnable && useWebGL)
2093
+ const textureInfo = tileInfo && tileInfo.getTextureInfo();
2094
+ if (useWebGL)
1801
2095
  {
1802
2096
  if (screenSpace)
1803
2097
  {
@@ -1805,48 +2099,50 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
1805
2099
  pos = screenToWorld(pos);
1806
2100
  size = size.scale(1/cameraScale);
1807
2101
  }
1808
- if (tileIndex < 0 || !tileImage.width)
1809
- {
1810
- // if negative tile index or image not found, force untextured
1811
- glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
1812
- }
1813
- else
2102
+
2103
+ if (textureInfo)
1814
2104
  {
1815
2105
  // calculate uvs and render
1816
- const cols = tileImageSize.x / tileSize.x |0;
1817
- const uvSizeX = tileSize.x / tileImageSize.x;
1818
- const uvSizeY = tileSize.y / tileImageSize.y;
1819
- const uvX = (tileIndex%cols)*uvSizeX, uvY = (tileIndex/cols|0)*uvSizeY;
1820
-
2106
+ const x = tileInfo.pos.x / textureInfo.size.x;
2107
+ const y = tileInfo.pos.y / textureInfo.size.y;
2108
+ const w = tileInfo.size.x / textureInfo.size.x;
2109
+ const h = tileInfo.size.y / textureInfo.size.y;
2110
+ const tileImageFixBleed = textureInfo.fixBleedSize;
2111
+ glSetTexture(textureInfo.glTexture);
1821
2112
  glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
1822
- uvX + tileImageFixBleed.x, uvY + tileImageFixBleed.y,
1823
- uvX - tileImageFixBleed.x + uvSizeX, uvY - tileImageFixBleed.y + uvSizeY,
2113
+ x + tileImageFixBleed.x, y + tileImageFixBleed.y,
2114
+ x - tileImageFixBleed.x + w, y - tileImageFixBleed.y + h,
1824
2115
  color.rgbaInt(), additiveColor.rgbaInt());
1825
2116
  }
2117
+ else
2118
+ {
2119
+ // if no tile info, force untextured
2120
+ glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
2121
+ }
1826
2122
  }
1827
2123
  else
1828
2124
  {
1829
2125
  // normal canvas 2D rendering method (slower)
1830
2126
  drawCanvas2D(pos, size, angle, mirror, (context)=>
1831
2127
  {
1832
- if (tileIndex < 0)
2128
+ if (textureInfo)
1833
2129
  {
1834
- // if negative tile index, force untextured
1835
- context.fillStyle = color;
1836
- context.fillRect(-.5, -.5, 1, 1);
2130
+ // calculate uvs and render
2131
+ const x = tileInfo.pos.x + tileFixBleedScale;
2132
+ const y = tileInfo.pos.y + tileFixBleedScale;
2133
+ const w = tileInfo.size.x - 2*tileFixBleedScale;
2134
+ const h = tileInfo.size.y - 2*tileFixBleedScale;
2135
+ context.globalAlpha = color.a; // only alpha is supported
2136
+ context.drawImage(textureInfo.image, x, y, w, h, -.5, -.5, 1, 1);
2137
+ context.globalAlpha = 1; // set back to full alpha
1837
2138
  }
1838
2139
  else
1839
2140
  {
1840
- // calculate uvs and render
1841
- const cols = tileImageSize.x / tileSize.x |0;
1842
- const sX = (tileIndex%cols)*tileSize.x + tileFixBleedScale;
1843
- const sY = (tileIndex/cols|0)*tileSize.y + tileFixBleedScale;
1844
- const sWidth = tileSize.x - 2*tileFixBleedScale;
1845
- const sHeight = tileSize.y - 2*tileFixBleedScale;
1846
- context.globalAlpha = color.a; // only alpha is supported
1847
- context.drawImage(tileImage, sX, sY, sWidth, sHeight, -.5, -.5, 1, 1);
2141
+ // if no tile info, force untextured
2142
+ context.fillStyle = color;
2143
+ context.fillRect(-.5, -.5, 1, 1);
1848
2144
  }
1849
- }, undefined, screenSpace);
2145
+ }, screenSpace, context);
1850
2146
  }
1851
2147
  }
1852
2148
 
@@ -1857,28 +2153,36 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
1857
2153
  * @param {Number} [angle=0]
1858
2154
  * @param {Boolean} [useWebGL=glEnable]
1859
2155
  * @param {Boolean} [screenSpace=0]
2156
+ * @param {CanvasRenderingContext2D} [context]
1860
2157
  * @memberof Draw */
1861
- function drawRect(pos, size, color, angle, useWebGL, screenSpace)
1862
- { drawTile(pos, size, -1, tileSizeDefault, color, angle, 0, undefined, useWebGL, screenSpace); }
2158
+ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
2159
+ {
2160
+ drawTile(pos, size, undefined, color, angle, 0, undefined, useWebGL, screenSpace, context);
2161
+ }
1863
2162
 
1864
2163
  /** Draw colored polygon using passed in points
1865
2164
  * @param {Array} points - Array of Vector2 points
1866
2165
  * @param {Color} [color=Color()]
1867
2166
  * @param {Boolean} [useWebGL=glEnable]
1868
2167
  * @param {Boolean} [screenSpace=0]
2168
+ * @param {CanvasRenderingContext2D} [context]
1869
2169
  * @memberof Draw */
1870
- function drawPoly(points, color=new Color, useWebGL=glEnable, screenSpace)
2170
+ function drawPoly(points, color=new Color, useWebGL=glEnable, screenSpace, context)
1871
2171
  {
2172
+ ASSERT(!context || !useWebGL); // context only supported in canvas 2D mode
2173
+
1872
2174
  if (useWebGL)
1873
2175
  glDrawPoints(screenSpace ? points.map(screenToWorld) : points, color.rgbaInt());
1874
2176
  else
1875
2177
  {
1876
2178
  // draw using canvas
1877
- mainContext.fillStyle = color;
1878
- mainContext.beginPath();
2179
+ if (!context)
2180
+ context = mainContext;
2181
+ context.fillStyle = color;
2182
+ context.beginPath();
1879
2183
  for (const point of screenSpace ? points : points.map(worldToScreen))
1880
- mainContext.lineTo(point.x, point.y);
1881
- mainContext.fill();
2184
+ context.lineTo(point.x, point.y);
2185
+ context.fill();
1882
2186
  }
1883
2187
  }
1884
2188
 
@@ -1889,12 +2193,13 @@ function drawPoly(points, color=new Color, useWebGL=glEnable, screenSpace)
1889
2193
  * @param {Color} [color=Color()]
1890
2194
  * @param {Boolean} [useWebGL=glEnable]
1891
2195
  * @param {Boolean} [screenSpace=0]
2196
+ * @param {CanvasRenderingContext2D} [context]
1892
2197
  * @memberof Draw */
1893
- function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace)
2198
+ function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
1894
2199
  {
1895
2200
  const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
1896
2201
  const size = vec2(thickness, halfDelta.length()*2);
1897
- drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), useWebGL, screenSpace);
2202
+ drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), useWebGL, screenSpace, context);
1898
2203
  }
1899
2204
 
1900
2205
  /** Draw directly to a 2d canvas context in world space
@@ -1903,10 +2208,10 @@ function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace)
1903
2208
  * @param {Number} angle
1904
2209
  * @param {Boolean} mirror
1905
2210
  * @param {Function} drawFunction
1906
- * @param {CanvasRenderingContext2D} [context=mainContext]
1907
2211
  * @param {Boolean} [screenSpace=0]
2212
+ * @param {CanvasRenderingContext2D} [context=mainContext]
1908
2213
  * @memberof Draw */
1909
- function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainContext, screenSpace)
2214
+ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, context=mainContext)
1910
2215
  {
1911
2216
  if (!screenSpace)
1912
2217
  {
@@ -1925,13 +2230,19 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
1925
2230
  /** Enable normal or additive blend mode
1926
2231
  * @param {Boolean} [additive=0]
1927
2232
  * @param {Boolean} [useWebGL=glEnable]
2233
+ * @param {CanvasRenderingContext2D} [context=mainContext]
1928
2234
  * @memberof Draw */
1929
- function setBlendMode(additive, useWebGL=glEnable)
2235
+ function setBlendMode(additive, useWebGL=glEnable, context)
1930
2236
  {
1931
- if (glEnable && useWebGL)
1932
- glSetBlendMode(additive);
2237
+ ASSERT(!context || !useWebGL); // context only supported in canvas 2D mode
2238
+ if (useWebGL)
2239
+ glAdditive = additive;
1933
2240
  else
1934
- mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
2241
+ {
2242
+ if (!context)
2243
+ context = mainContext;
2244
+ context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
2245
+ }
1935
2246
  }
1936
2247
 
1937
2248
  /** Draw text on overlay canvas in world space
@@ -2004,10 +2315,9 @@ class FontImage
2004
2315
  * @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
2005
2316
  * @param {Vector2} [tileSize=vec2(8)] - Size of the font source tiles
2006
2317
  * @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
2007
- * @param {Number} [startTileIndex=0] - Tile index in image where font starts
2008
2318
  * @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
2009
2319
  */
2010
- constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
2320
+ constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), context=overlayContext)
2011
2321
  {
2012
2322
  // load default font image
2013
2323
  if (!engineFontImage)
@@ -2016,7 +2326,6 @@ class FontImage
2016
2326
  this.image = image || engineFontImage;
2017
2327
  this.tileSize = tileSize;
2018
2328
  this.paddingSize = paddingSize;
2019
- this.startTileIndex = startTileIndex;
2020
2329
  this.context = context;
2021
2330
  }
2022
2331
 
@@ -2057,7 +2366,7 @@ class FontImage
2057
2366
  charCode = 127; // unknown character
2058
2367
 
2059
2368
  // get the character source location and draw it
2060
- const tile = this.startTileIndex + charCode - 32;
2369
+ const tile = charCode - 32;
2061
2370
  const x = tile % cols;
2062
2371
  const y = tile / cols |0;
2063
2372
  const drawPos = pos.add(vec2(j,i).multiply(drawSize));
@@ -2089,8 +2398,7 @@ function toggleFullscreen()
2089
2398
  }
2090
2399
  else if (document.body.requestFullscreen)
2091
2400
  document.body.requestFullscreen();
2092
- }
2093
-
2401
+ }
2094
2402
  /**
2095
2403
  * LittleJS Input System
2096
2404
  * - Tracks keyboard down, pressed, and released
@@ -2586,7 +2894,8 @@ class Sound
2586
2894
  if (zzfxSound)
2587
2895
  {
2588
2896
  // generate zzfx sound now for fast playback
2589
- this.randomness = zzfxSound[1] || (zzfxSound[1] = 0);
2897
+ this.randomness = zzfxSound[1];
2898
+ zzfxSound[1] = 0; // generate without randomness
2590
2899
  this.sampleChannels = [zzfxG(...zzfxSound)];
2591
2900
  this.sampleRate = zzfxR;
2592
2901
  }
@@ -3248,7 +3557,7 @@ class TileLayerData
3248
3557
  }
3249
3558
 
3250
3559
  /**
3251
- * Tile layer object - cached rendering system for tile layers
3560
+ * Tile Layer - cached rendering system for tile layers
3252
3561
  * - Each Tile layer is rendered to an off screen canvas
3253
3562
  * - To allow dynamic modifications, layers are rendered using canvas 2d
3254
3563
  * - Some devices like mobile phones are limited to 4k texture resolution
@@ -3264,13 +3573,13 @@ class TileLayer extends EngineObject
3264
3573
  /** Create a tile layer object
3265
3574
  * @param {Vector2} [position=Vector2()] - World space position
3266
3575
  * @param {Vector2} [size=tileCollisionSize] - World space size
3267
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
3576
+ * @param {TileInfo} [tileInfo] - Tile info for layer
3268
3577
  * @param {Vector2} [scale=Vector2(1,1)] - How much to scale this layer when rendered
3269
3578
  * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
3270
3579
  */
3271
- constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
3580
+ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderOrder=0)
3272
3581
  {
3273
- super(pos, size, -1, tileSize, 0, undefined, renderOrder);
3582
+ super(pos, size, tileInfo, 0, undefined, renderOrder);
3274
3583
 
3275
3584
  /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
3276
3585
  this.canvas = document.createElement('canvas');
@@ -3347,13 +3656,13 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3347
3656
  mainCanvas = this.canvas;
3348
3657
  mainContext = this.context;
3349
3658
  cameraPos = this.size.scale(.5);
3350
- cameraScale = this.tileSize.x;
3659
+ cameraScale = this.tileInfo.size.x;
3351
3660
 
3352
3661
  if (clear)
3353
3662
  {
3354
3663
  // clear and set size
3355
- mainCanvas.width = this.size.x * this.tileSize.x;
3356
- mainCanvas.height = this.size.y * this.tileSize.y;
3664
+ mainCanvas.width = this.size.x * this.tileInfo.size.x;
3665
+ mainCanvas.height = this.size.y * this.tileInfo.size.y;
3357
3666
  }
3358
3667
 
3359
3668
  // begin a new render for the tile canvas
@@ -3384,7 +3693,8 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3384
3693
  if (d.tile != undefined)
3385
3694
  {
3386
3695
  ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
3387
- drawTile(pos, vec2(1), d.tile, this.tileSize, d.color, d.direction*PI/2, d.mirror);
3696
+ const tileInfo = tile(d.tile, this.tileInfo.size, this.tileInfo.textureIndex);
3697
+ drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
3388
3698
  }
3389
3699
  }
3390
3700
 
@@ -3406,8 +3716,8 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3406
3716
  {
3407
3717
  const context = this.context;
3408
3718
  context.save();
3409
- pos = pos.subtract(this.pos).multiply(this.tileSize);
3410
- size = size.multiply(this.tileSize);
3719
+ pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
3720
+ size = size.multiply(this.tileInfo.size);
3411
3721
  context.translate(pos.x, this.canvas.height - pos.y);
3412
3722
  context.rotate(angle);
3413
3723
  context.scale(mirror ? -size.x : size.x, size.y);
@@ -3418,28 +3728,28 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3418
3728
  /** Draw a tile directly onto the layer canvas
3419
3729
  * @param {Vector2} pos
3420
3730
  * @param {Vector2} [size=Vector2(1,1)]
3421
- * @param {Number} [tileIndex=-1]
3422
- * @param {Vector2} [tileSize=tileSizeDefault]
3731
+ * @param {TileInfo} [tileInfo]
3423
3732
  * @param {Color} [color=Color()]
3424
3733
  * @param {Number} [angle=0]
3425
3734
  * @param {Boolean} [mirror=0] */
3426
- drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle, mirror)
3735
+ drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle, mirror)
3427
3736
  {
3428
3737
  this.drawCanvas2D(pos, size, angle, mirror, (context)=>
3429
3738
  {
3430
- if (tileIndex < 0)
3739
+ const textureInfo = tileInfo && tileInfo.getTextureInfo();
3740
+ if (textureInfo)
3431
3741
  {
3432
- // untextured
3433
- context.fillStyle = color;
3434
- context.fillRect(-.5, -.5, 1, 1);
3742
+ context.globalAlpha = color.a; // only alpha is supported
3743
+ context.drawImage(textureInfo.image,
3744
+ tileInfo.pos.x, tileInfo.pos.y,
3745
+ tileInfo.size.x, tileInfo.size.y, -.5, -.5, 1, 1);
3746
+ context.globalAlpha = 1;
3435
3747
  }
3436
3748
  else
3437
3749
  {
3438
- const cols = tileImage.width/tileSize.x;
3439
- context.globalAlpha = color.a; // only alpha, no color, is supported in this mode
3440
- context.drawImage(tileImage,
3441
- (tileIndex%cols)*tileSize.x, (tileIndex/cols|0)*tileSize.y,
3442
- tileSize.x, tileSize.y, -.5, -.5, 1, 1);
3750
+ // untextured
3751
+ context.fillStyle = color;
3752
+ context.fillRect(-.5, -.5, 1, 1);
3443
3753
  }
3444
3754
  });
3445
3755
  }
@@ -3467,7 +3777,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3467
3777
  * let particleEmiter = new ParticleEmitter
3468
3778
  * (
3469
3779
  * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
3470
- * 0, vec2(16), // tileIndex, tileSize
3780
+ * tile(0, 16), // tileInfo
3471
3781
  * new Color(1,1,1), new Color(0,0,0), // colorStartA, colorStartB
3472
3782
  * new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
3473
3783
  * 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
@@ -3484,8 +3794,7 @@ class ParticleEmitter extends EngineObject
3484
3794
  * @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
3485
3795
  * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
3486
3796
  * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
3487
- * @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied
3488
- * @param {Vector2} [tileSize=tileSizeDefault] - Tile size for particles
3797
+ * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
3489
3798
  * @param {Color} [colorStartA=Color()] - Color at start of life 1, randomized between start colors
3490
3799
  * @param {Color} [colorStartB=Color()] - Color at start of life 2, randomized between start colors
3491
3800
  * @param {Color} [colorEndA=Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
@@ -3515,8 +3824,7 @@ class ParticleEmitter extends EngineObject
3515
3824
  emitTime = 0,
3516
3825
  emitRate = 100,
3517
3826
  emitConeAngle = PI,
3518
- tileIndex = -1,
3519
- tileSize = tileSizeDefault,
3827
+ tileInfo,
3520
3828
  colorStartA = new Color,
3521
3829
  colorStartB = new Color,
3522
3830
  colorEndA = new Color(1,1,1,0),
@@ -3539,7 +3847,7 @@ class ParticleEmitter extends EngineObject
3539
3847
  localSpace
3540
3848
  )
3541
3849
  {
3542
- super(pos, vec2(), tileIndex, tileSize, angle, undefined, renderOrder);
3850
+ super(pos, vec2(), tileInfo, angle, undefined, renderOrder);
3543
3851
 
3544
3852
  // emitter settings
3545
3853
  /** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
@@ -3638,7 +3946,7 @@ class ParticleEmitter extends EngineObject
3638
3946
  angle += this.angle;
3639
3947
  }
3640
3948
 
3641
- const particle = new Particle(pos, this.tileIndex, this.tileSize, angle);
3949
+ const particle = new Particle(pos, this.tileInfo, this.tileSize, angle);
3642
3950
 
3643
3951
  // randomness scales each paremeter by a percentage
3644
3952
  const randomness = this.randomness;
@@ -3698,12 +4006,11 @@ class Particle extends EngineObject
3698
4006
  /**
3699
4007
  * Create a particle with the given settings
3700
4008
  * @param {Vector2} position - World space position of the particle
3701
- * @param {Number} [tileIndex=-1] - Tile to use to render, untextured if -1
3702
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
4009
+ * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
3703
4010
  * @param {Number} [angle=0] - Angle to rotate the particle
3704
4011
  */
3705
- constructor(pos, tileIndex, tileSize, angle)
3706
- { super(pos, vec2(), tileIndex, tileSize, angle); }
4012
+ constructor(pos, tileInfo, angle)
4013
+ { super(pos, vec2(), tileInfo, angle); }
3707
4014
 
3708
4015
  /** Render the particle, automatically called each frame, sorted by renderOrder */
3709
4016
  render()
@@ -3737,14 +4044,17 @@ class Particle extends EngineObject
3737
4044
  if (this.localSpaceEmitter)
3738
4045
  velocity = velocity.rotate(-this.localSpaceEmitter.angle);
3739
4046
  const speed = velocity.length();
3740
- const direction = velocity.scale(1/speed);
3741
- const trailLength = speed * this.trailScale;
3742
- size.y = max(size.x, trailLength);
3743
- angle = direction.angle();
3744
- drawTile(pos.add(direction.multiply(vec2(0,-trailLength/2))), size, this.tileIndex, this.tileSize, color, angle, this.mirror);
4047
+ if (speed)
4048
+ {
4049
+ const direction = velocity.scale(1/speed);
4050
+ const trailLength = speed * this.trailScale;
4051
+ size.y = max(size.x, trailLength);
4052
+ angle = direction.angle();
4053
+ drawTile(pos.add(direction.multiply(vec2(0,-trailLength/2))), size, this.tileInfo, color, angle, this.mirror);
4054
+ }
3745
4055
  }
3746
4056
  else
3747
- drawTile(pos, size, this.tileIndex, this.tileSize, color, angle, this.mirror);
4057
+ drawTile(pos, size, this.tileInfo, color, angle, this.mirror);
3748
4058
  this.additive && setBlendMode();
3749
4059
  debugParticles && debugRect(pos, size, '#f005', 0, angle);
3750
4060
 
@@ -3791,7 +4101,7 @@ function medalsInit(saveName)
3791
4101
  }
3792
4102
 
3793
4103
  /**
3794
- * Medal Object - Tracks an unlockable medal
4104
+ * Medal - Tracks an unlockable medal
3795
4105
  * @example
3796
4106
  * // create a medal
3797
4107
  * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
@@ -3804,7 +4114,7 @@ function medalsInit(saveName)
3804
4114
  */
3805
4115
  class Medal
3806
4116
  {
3807
- /** Create an medal object and adds it to the list of medals
4117
+ /** Create a medal object and adds it to the list of medals
3808
4118
  * @param {Number} id - The unique identifier of the medal
3809
4119
  * @param {String} name - Name of the medal
3810
4120
  * @param {String} [description] - Description of the medal
@@ -3916,33 +4226,33 @@ let newgrounds;
3916
4226
  /** This can used to enable Newgrounds functionality
3917
4227
  * @param {Number} app_id - The newgrounds App ID
3918
4228
  * @param {String} [cipher] - The encryption Key (AES-128/Base64)
4229
+ * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher
3919
4230
  * @memberof Medals */
3920
- function newgroundsInit(app_id, cipher) { newgrounds = new Newgrounds(app_id, cipher); }
4231
+ function newgroundsInit(app_id, cipher, cryptoJS)
4232
+ { newgrounds = new Newgrounds(app_id, cipher, cryptoJS); }
3921
4233
 
3922
4234
  /**
3923
4235
  * Newgrounds API wrapper object
3924
4236
  * @example
3925
- * // create a newgrounds object, replace the app id and cipher with your own
4237
+ * // create a newgrounds object, replace the app id with your own
3926
4238
  * const app_id = '53123:1ZuSTQ9l';
3927
- * const cipher = 'enF0vGH@Mj/FRASKL23Q==';
3928
- * newgrounds = new Newgrounds(app_id, cipher);
4239
+ * newgrounds = new Newgrounds(app_id);
3929
4240
  */
3930
4241
  class Newgrounds
3931
4242
  {
3932
4243
  /** Create a newgrounds object
3933
4244
  * @param {Number} app_id - The newgrounds App ID
3934
- * @param {String} [cipher] - The encryption Key (AES-128/Base64) */
3935
- constructor(app_id, cipher)
4245
+ * @param {String} [cipher] - The encryption Key (AES-128/Base64)
4246
+ * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher */
4247
+ constructor(app_id, cipher, cryptoJS)
3936
4248
  {
3937
- ASSERT(!newgrounds && app_id);
4249
+ ASSERT(!newgrounds && app_id); // can only be one newgrounds object
4250
+ ASSERT(!cipher || cryptoJS); // must provide cryptojs if there is a cipher
3938
4251
 
3939
4252
  this.app_id = app_id;
3940
4253
  this.cipher = cipher;
4254
+ this.cryptoJS = cryptoJS;
3941
4255
  this.host = location ? location.hostname : '';
3942
-
3943
- // create an instance of CryptoJS for encrypted calls
3944
- if (cipher)
3945
- this.cryptoJS = this.CryptoJS();
3946
4256
 
3947
4257
  // get session id from url search params
3948
4258
  const url = new URL(location.href);
@@ -4046,38 +4356,6 @@ class Newgrounds
4046
4356
  debugMedals && console.log(xmlHttp.responseText);
4047
4357
  return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
4048
4358
  }
4049
-
4050
- CryptoJS()
4051
- {
4052
- ///////////////////////////////////////////////////////////////////////////////
4053
- // Crypto-JS - https://github.com/brix/crypto-js - MIT License
4054
- //
4055
- // [The MIT License (MIT)](http://opensource.org/licenses/MIT)
4056
- //
4057
- // Copyright (c) 2009-2013 Jeff Mott
4058
- // Copyright (c) 2013-2016 Evan Vosberg
4059
- //
4060
- // Permission is hereby granted, free of charge, to any person obtaining a copy
4061
- // of this software and associated documentation files (the "Software"), to deal
4062
- // in the Software without restriction, including without limitation the rights
4063
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
4064
- // copies of the Software, and to permit persons to whom the Software is
4065
- // furnished to do so, subject to the following conditions:
4066
- //
4067
- // The above copyright notice and this permission notice shall be included in
4068
- // all copies or substantial portions of the Software.
4069
- //
4070
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
4071
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
4072
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
4073
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
4074
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
4075
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
4076
- // THE SOFTWARE.
4077
- return eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",...']charCodeAtUinyxpf',"for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h<t,o=o%h+(i?t:h-t)*(o>>17)-!i*t,f.map((n,x)=>(U=r[n]+=(i*h/2-r[n]<<13)/((C[n]+=C[n]<5)+1/20)>>13,a[x]+=y[x]*(i-t/h))),p=p*2+i)for(f='010202103203210431053105410642065206541'.split(t=0).map((n,x)=>(U=0,[...n].map((n,x)=>(U=U*997+(c[e-n]|0)|0)),h*32-1&U*997+p+!!A*129)*12+x);o<h*32;o=o*64|M.charCodeAt(d++)&63);for(C=String.fromCharCode(...c);r=/[\0-#?@\\\\~]/.exec(C);)with(C.split(r))C=join(shift());return C")([],[],1<<17,[0,0,0,0,0,0,0,0,0,0,0,0],new Uint16Array(51e6).fill(1<<15),new Uint8Array(51e6),0,0,0,0));
4078
- // end of Crypto-JS
4079
- ///////////////////////////////////////////////////////////////////////////////
4080
- }
4081
4359
  }
4082
4360
  /**
4083
4361
  * LittleJS WebGL Interface
@@ -4087,6 +4365,7 @@ class Newgrounds
4087
4365
  * - Can be disabled with glEnable to revert to 2D canvas rendering
4088
4366
  * - Batches sprite rendering on GPU for incredibly fast performance
4089
4367
  * - Sprite transform math is done in the shader where possible
4368
+ * - Supports shadertoy style post processing shaders
4090
4369
  * @namespace WebGL
4091
4370
  */
4092
4371
 
@@ -4102,11 +4381,6 @@ let glCanvas;
4102
4381
  * @memberof WebGL */
4103
4382
  let glContext;
4104
4383
 
4105
- /** Main tile sheet texture automatically loaded by engine
4106
- * @type {WebGLTexture}
4107
- * @memberof WebGL */
4108
- let glTileTexture;
4109
-
4110
4384
  // WebGL internal variables not exposed to documentation
4111
4385
  let glActiveTexture, glShader, glArrayBuffer, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
4112
4386
 
@@ -4115,32 +4389,33 @@ let glActiveTexture, glShader, glArrayBuffer, glPositionData, glColorData, glBat
4115
4389
  // Initalize WebGL, called automatically by the engine
4116
4390
  function glInit()
4117
4391
  {
4118
- // create the canvas and tile texture
4392
+ // create the canvas and textures
4119
4393
  glCanvas = document.createElement('canvas');
4120
- glContext = glCanvas.getContext('webgl', {antialias: false});
4121
- glTileTexture = glCreateTexture(tileImage);
4394
+ glContext = glCanvas.getContext('webgl2');
4122
4395
 
4123
4396
  // some browsers are much faster without copying the gl buffer so we just overlay it instead
4124
4397
  glOverlay && document.body.appendChild(glCanvas);
4125
4398
 
4126
4399
  // setup vertex and fragment shaders
4127
4400
  glShader = glCreateProgram(
4401
+ '#version 300 es\n' + // specify GLSL ES version
4128
4402
  'precision highp float;'+ // use highp for better accuracy
4129
4403
  'uniform mat4 m;'+ // transform matrix
4130
- 'attribute vec2 p,t;'+ // position, uv
4131
- 'attribute vec4 c,a;'+ // color, additiveColor
4132
- 'varying vec4 v,d,e;'+ // return uv, color, additiveColor
4404
+ 'in vec4 p,c,a;'+ // position, uv, color, additiveColor
4405
+ 'out vec4 v,d,e;'+ // return uv, color, additiveColor
4133
4406
  'void main(){'+ // shader entry point
4134
- 'gl_Position=m*vec4(p,1,1);'+ // transform position
4135
- 'v=vec4(t,p);d=c;e=a;'+ // pass stuff to fragment shader
4407
+ 'gl_Position=m*vec4(p.xy,1,1);'+ // transform position
4408
+ 'v=p;d=c;e=a;'+ // pass stuff to fragment shader
4136
4409
  '}' // end of shader
4137
4410
  ,
4138
- 'precision highp float;'+ // use highp for better accuracy
4139
- 'varying vec4 v,d,e;'+ // uv, color, additiveColor
4140
- 'uniform sampler2D s;'+ // texture
4141
- 'void main(){'+ // shader entry point
4142
- 'gl_FragColor=texture2D(s,v.xy)*d+e;'+ // modulate texture by color plus additive
4143
- '}' // end of shader
4411
+ '#version 300 es\n' + // specify GLSL ES version
4412
+ 'precision highp float;'+ // use highp for better accuracy
4413
+ 'in vec4 v,d,e;'+ // position, uv, color, additiveColor
4414
+ 'uniform sampler2D s;'+ // texture
4415
+ 'out vec4 c;'+ // out color
4416
+ 'void main(){'+ // shader entry point
4417
+ 'c=texture(s,v.zw)*d+e;'+ // modulate texture by color plus additive
4418
+ '}' // end of shader
4144
4419
  );
4145
4420
 
4146
4421
  // init buffers
@@ -4161,10 +4436,10 @@ function glPreRender()
4161
4436
  // set up the shader
4162
4437
  glContext.useProgram(glShader);
4163
4438
  glContext.activeTexture(gl_TEXTURE0);
4164
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
4439
+ glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
4165
4440
  glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
4166
4441
  glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
4167
- glSetBlendMode();
4442
+ glAdditive = 0;
4168
4443
 
4169
4444
  // set vertex attributes
4170
4445
  let offset = 0;
@@ -4175,43 +4450,36 @@ function glPreRender()
4175
4450
  glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
4176
4451
  offset += size*typeSize;
4177
4452
  }
4178
- initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
4179
- initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
4453
+ initVertexAttribArray('p', gl_FLOAT, 4, 4); // position & texture coords
4180
4454
  initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
4181
4455
  initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
4182
4456
 
4183
4457
  // build the transform matrix
4184
4458
  const sx = 2 * cameraScale / mainCanvas.width;
4185
4459
  const sy = 2 * cameraScale / mainCanvas.height;
4460
+ const cx = -1 - sx*cameraPos.x;
4461
+ const cy = -1 - sy*cameraPos.y;
4186
4462
  glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
4187
4463
  new Float32Array([
4188
- sx, 0, 0, 0,
4189
- 0, sy, 0, 0,
4190
- 1, 1, -1, 1,
4191
- -1-sx*cameraPos.x, -1-sy*cameraPos.y, 0, 0
4464
+ sx, 0, 0, 0,
4465
+ 0, sy, 0, 0,
4466
+ 1, 1, -1, 1,
4467
+ cx, cy, 0, 0
4192
4468
  ])
4193
4469
  );
4194
4470
  }
4195
4471
 
4196
- /** Set the WebGl blend mode, normally you should call setBlendMode instead
4197
- * @param {Boolean} [additive=0]
4198
- * @memberof WebGL */
4199
- function glSetBlendMode(additive)
4200
- {
4201
- // setup blending
4202
- glAdditive = additive;
4203
- }
4204
-
4205
- /** Set the WebGl texture, not normally necessary unless multiple tile sheets are used
4472
+ /** Set the WebGl texture, called automatically if using multiple textures
4206
4473
  * - This may also flush the gl buffer resulting in more draw calls and worse performance
4207
- * @param {WebGLTexture} [texture=glTileTexture]
4474
+ * @param {WebGLTexture} texture
4208
4475
  * @memberof WebGL */
4209
- function glSetTexture(texture=glTileTexture)
4476
+ function glSetTexture(texture)
4210
4477
  {
4211
4478
  // must flush cache with the old texture to set a new one
4212
- if (texture != glActiveTexture)
4213
- glFlush();
4479
+ if (texture == glActiveTexture)
4480
+ return;
4214
4481
 
4482
+ glFlush();
4215
4483
  glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = texture);
4216
4484
  }
4217
4485
 
@@ -4390,25 +4658,28 @@ function glInitPostProcess(shaderCode, includeOverlay)
4390
4658
  {
4391
4659
  ASSERT(!glPostShader); // can only have 1 post effects shader
4392
4660
 
4393
- if (!shaderCode) // default shader
4394
- shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture2D(iChannel0,p/iResolution.xy);}';
4661
+ if (!shaderCode) // default shader pass through
4662
+ shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
4395
4663
 
4396
4664
  // create the shader
4397
4665
  glPostShader = glCreateProgram(
4666
+ '#version 300 es\n' + // specify GLSL ES version
4398
4667
  'precision highp float;'+ // use highp for better accuracy
4399
- 'attribute vec2 p;'+ // position
4668
+ 'in vec2 p;'+ // position
4400
4669
  'void main(){'+ // shader entry point
4401
4670
  'gl_Position=vec4(p,1,1);'+ // set position
4402
4671
  '}' // end of shader
4403
4672
  ,
4673
+ '#version 300 es\n' + // specify GLSL ES version
4404
4674
  'precision highp float;'+ // use highp for better accuracy
4405
4675
  'uniform sampler2D iChannel0;'+ // input texture
4406
4676
  'uniform vec3 iResolution;'+ // size of output texture
4407
4677
  'uniform float iTime;'+ // time passed
4678
+ 'out vec4 c;'+ // out color
4408
4679
  '\n' + shaderCode + '\n'+ // insert custom shader code
4409
4680
  'void main(){'+ // shader entry point
4410
- 'mainImage(gl_FragColor,gl_FragCoord.xy);'+ // call post process function
4411
- 'gl_FragColor.a=1.;'+ // always use full alpha
4681
+ 'mainImage(c,gl_FragCoord.xy);'+ // call post process function
4682
+ 'c.a=1.;'+ // always use full alpha
4412
4683
  '}' // end of shader
4413
4684
  );
4414
4685
 
@@ -4433,8 +4704,11 @@ function glRenderPostProcess()
4433
4704
  glFlush(); // clear out the buffer
4434
4705
  mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
4435
4706
  }
4436
- else // set viewport
4707
+ else
4708
+ {
4709
+ // set the viewport
4437
4710
  glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
4711
+ }
4438
4712
 
4439
4713
  if (glPostIncludeOverlay)
4440
4714
  {
@@ -4481,7 +4755,6 @@ gl_ONE_MINUS_SRC_ALPHA = 771,
4481
4755
  gl_BLEND = 3042,
4482
4756
  gl_TEXTURE_2D = 3553,
4483
4757
  gl_UNSIGNED_BYTE = 5121,
4484
- gl_BYTE = 5120,
4485
4758
  gl_FLOAT = 5126,
4486
4759
  gl_RGBA = 6408,
4487
4760
  gl_NEAREST = 9728,
@@ -4493,7 +4766,6 @@ gl_TEXTURE_WRAP_T = 10243,
4493
4766
  gl_COLOR_BUFFER_BIT = 16384,
4494
4767
  gl_CLAMP_TO_EDGE = 33071,
4495
4768
  gl_TEXTURE0 = 33984,
4496
- gl_TEXTURE1 = 33985,
4497
4769
  gl_ARRAY_BUFFER = 34962,
4498
4770
  gl_STATIC_DRAW = 35044,
4499
4771
  gl_DYNAMIC_DRAW = 35048,
@@ -4504,7 +4776,6 @@ gl_LINK_STATUS = 35714,
4504
4776
  gl_UNPACK_FLIP_Y_WEBGL = 37440,
4505
4777
 
4506
4778
  // constants for batch rendering
4507
- gl_VERTICES_PER_QUAD = 6,
4508
4779
  gl_INDICIES_PER_VERT = 6,
4509
4780
  gl_MAX_BATCH = 1e5,
4510
4781
  gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
@@ -4541,7 +4812,7 @@ const engineName = 'LittleJS';
4541
4812
  * @type {String}
4542
4813
  * @default
4543
4814
  * @memberof Engine */
4544
- const engineVersion = '1.7.21';
4815
+ const engineVersion = '1.8.3';
4545
4816
 
4546
4817
  /** Frames per second to update objects
4547
4818
  * @type {Number}
@@ -4591,6 +4862,9 @@ let paused = 0;
4591
4862
  * @memberof Engine */
4592
4863
  function setPaused(_paused) { paused = _paused; }
4593
4864
 
4865
+ // Frame time tracking
4866
+ let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
4867
+
4594
4868
  ///////////////////////////////////////////////////////////////////////////////
4595
4869
 
4596
4870
  /** Start up LittleJS engine with your callback functions
@@ -4599,52 +4873,13 @@ function setPaused(_paused) { paused = _paused; }
4599
4873
  * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
4600
4874
  * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
4601
4875
  * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
4602
- * @param {String} [tileImageSource] - Tile image to use, everything starts when the image is finished loading
4876
+ * @param {Array} [imageSources=['tiles.png']] - Image to load
4603
4877
  * @memberof Engine */
4604
- function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, tileImageSource)
4878
+ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=['tiles.png'])
4605
4879
  {
4606
- // init engine when tiles load or fail to load
4607
- tileImage.onerror = tileImage.onload = ()=>
4608
- {
4609
- // save tile image info
4610
- tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
4611
- debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
4612
-
4613
- // setup html
4614
- const styleBody =
4615
- 'margin:0;overflow:hidden;' + // fill the window
4616
- 'background:#000;' + // set background color
4617
- 'touch-action:none;' + // prevent mobile pinch to resize
4618
- 'user-select:none;' + // prevent mobile hold to select
4619
- '-webkit-user-select:none;' + // compatibility for ios
4620
- '-webkit-touch-callout:none'; // compatibility for ios
4621
- document.body.style = styleBody;
4622
- document.body.appendChild(mainCanvas = document.createElement('canvas'));
4623
- mainContext = mainCanvas.getContext('2d');
4624
-
4625
- // init stuff and start engine
4626
- debugInit();
4627
- glEnable && glInit();
4628
-
4629
- // create overlay canvas for hud to appear above gl canvas
4630
- document.body.appendChild(overlayCanvas = document.createElement('canvas'));
4631
- overlayContext = overlayCanvas.getContext('2d');
4632
-
4633
- // set canvas style
4634
- const styleCanvas =
4635
- 'position:absolute;' + // position canvas
4636
- 'top:50%;left:50%;transform:translate(-50%,-50%);' + // center the canvas
4637
- (canvasPixelated?'image-rendering:pixelated':''); // set pixelated rendering
4638
- (glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
4639
-
4640
- gameInit();
4641
- engineUpdate();
4642
- };
4643
-
4644
- // frame time tracking
4645
- let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
4880
+ ASSERT(Array.isArray(imageSources)); // pass in images as array
4646
4881
 
4647
- // main update loop
4882
+ // internal update loop for engine
4648
4883
  function engineUpdate(frameTimeMS=0)
4649
4884
  {
4650
4885
  // update time keeping
@@ -4675,9 +4910,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4675
4910
  }
4676
4911
  else
4677
4912
  {
4678
- // clear canvas and set size to same as window
4679
- mainCanvas.width = min(innerWidth, canvasMaxSize.x);
4680
- mainCanvas.height = min(innerHeight, canvasMaxSize.y);
4913
+ // clear canvas and set size to same as window
4914
+ mainCanvas.width = min(innerWidth, canvasMaxSize.x);
4915
+ mainCanvas.height = min(innerHeight, canvasMaxSize.y);
4681
4916
  }
4682
4917
 
4683
4918
  // clear overlay canvas and set size
@@ -4709,6 +4944,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4709
4944
  // update multiple frames if necessary in case of slow framerate
4710
4945
  for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / frameRate)
4711
4946
  {
4947
+ // increment frame and update time
4948
+ time = frame++ / frameRate;
4949
+
4712
4950
  // update game and objects
4713
4951
  inputUpdate();
4714
4952
  gameUpdate();
@@ -4756,8 +4994,51 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4756
4994
  requestAnimationFrame(engineUpdate);
4757
4995
  }
4758
4996
 
4759
- // set tile image source to load the image and start the engine
4760
- tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
4997
+ // setup html
4998
+ const styleBody =
4999
+ 'margin:0;overflow:hidden;' + // fill the window
5000
+ 'background:#000;' + // set background color
5001
+ 'touch-action:none;' + // prevent mobile pinch to resize
5002
+ 'user-select:none;' + // prevent mobile hold to select
5003
+ '-webkit-user-select:none;' + // compatibility for ios
5004
+ '-webkit-touch-callout:none'; // compatibility for ios
5005
+ document.body.style = styleBody;
5006
+ document.body.appendChild(mainCanvas = document.createElement('canvas'));
5007
+ mainContext = mainCanvas.getContext('2d');
5008
+
5009
+ // init stuff and start engine
5010
+ debugInit();
5011
+ glEnable && glInit();
5012
+
5013
+ // create overlay canvas for hud to appear above gl canvas
5014
+ document.body.appendChild(overlayCanvas = document.createElement('canvas'));
5015
+ overlayContext = overlayCanvas.getContext('2d');
5016
+
5017
+ // set canvas style
5018
+ const styleCanvas =
5019
+ 'position:absolute;' + // position
5020
+ 'top:50%;left:50%;transform:translate(-50%,-50%);' + // center
5021
+ (canvasPixelated?'image-rendering:pixelated':''); // pixelated rendering
5022
+ (glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
5023
+
5024
+ // load all of the images
5025
+ Promise.all(imageSources.map((src, textureIndex)=>
5026
+ new Promise((resolve, reject)=>
5027
+ {
5028
+ const image = new Image;
5029
+ image.onerror = image.onload = ()=>
5030
+ {
5031
+ textureInfos[textureIndex] = new TextureInfo(image);
5032
+ resolve();
5033
+ }
5034
+ image.src = src;
5035
+ })
5036
+ )).then(()=>
5037
+ {
5038
+ // start the engine
5039
+ gameInit();
5040
+ engineUpdate();
5041
+ });
4761
5042
  }
4762
5043
 
4763
5044
  // Called automatically by engine to setup render system
@@ -4795,9 +5076,6 @@ function engineObjectsUpdate()
4795
5076
 
4796
5077
  // remove destroyed objects
4797
5078
  engineObjects = engineObjects.filter(o=>!o.destroyed);
4798
-
4799
- // increment frame and update time
4800
- time = ++frame / frameRate;
4801
5079
  }
4802
5080
 
4803
5081
  /** Destroy and remove all objects