littlejsengine 1.7.13 → 1.8.1

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 (58) hide show
  1. package/README.md +27 -55
  2. package/build/littlejs.d.ts +253 -76
  3. package/build/littlejs.esm.js +644 -523
  4. package/build/littlejs.esm.min.js +1 -1
  5. package/build/littlejs.js +640 -332
  6. package/build/littlejs.min.js +1 -1
  7. package/build/littlejs.release.js +640 -332
  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 +2 -2
  12. package/examples/breakoutTutorial/game.js +4 -4
  13. package/examples/breakoutTutorial/index.html +2 -2
  14. package/examples/electron/build.js +2 -0
  15. package/examples/electron/game.js +3 -2
  16. package/examples/electron/index.html +2 -2
  17. package/examples/empty/game.js +1 -1
  18. package/examples/favicon.png +0 -0
  19. package/examples/js13k/build.js +2 -0
  20. package/examples/js13k/game.js +20 -15
  21. package/examples/js13k/index.html +14 -14
  22. package/examples/module/game.js +4 -3
  23. package/examples/module/index.html +1 -1
  24. package/examples/particles/index.html +19 -22
  25. package/examples/platformer/game.js +3 -3
  26. package/examples/platformer/gameEffects.js +21 -19
  27. package/examples/platformer/gameLevel.js +6 -6
  28. package/examples/platformer/gameObjects.js +18 -18
  29. package/examples/platformer/gamePlayer.js +4 -3
  30. package/examples/platformer/index.html +6 -6
  31. package/examples/platformer/tiles.png +0 -0
  32. package/examples/platformer/tilesLevel.png +0 -0
  33. package/examples/puzzle/game.js +10 -8
  34. package/examples/puzzle/index.html +2 -2
  35. package/examples/screenshot.jpg +0 -0
  36. package/examples/starter/build.js +2 -0
  37. package/examples/starter/game.js +32 -21
  38. package/examples/starter/index.html +13 -13
  39. package/examples/starter/tiles.png +0 -0
  40. package/examples/stress/index.html +2 -2
  41. package/examples/typescript/build.js +2 -0
  42. package/examples/typescript/game.js +4 -3
  43. package/examples/typescript/game.ts +4 -3
  44. package/examples/typescript/index.html +1 -1
  45. package/package.json +1 -1
  46. package/src/engine.js +59 -49
  47. package/src/engineAudio.js +67 -41
  48. package/src/engineBuild.js +23 -7
  49. package/src/engineDraw.js +139 -40
  50. package/src/engineExport.js +4 -191
  51. package/src/engineInput.js +22 -10
  52. package/src/engineMedals.js +13 -45
  53. package/src/engineObject.js +12 -13
  54. package/src/engineParticles.js +17 -17
  55. package/src/engineSettings.js +195 -2
  56. package/src/engineTileLayer.js +23 -22
  57. package/src/engineUtilities.js +6 -6
  58. package/src/engineWebGL.js +73 -74
@@ -1,5 +1,7 @@
1
1
  // LittleJS - MIT License - Copyright 2021 Frank Force
2
2
 
3
+ 'use strict';
4
+
3
5
  /**
4
6
  * LittleJS Debug System
5
7
  * - Press Esc to show debug overlay with mouse pick
@@ -10,7 +12,7 @@
10
12
  * @namespace Debug
11
13
  */
12
14
 
13
- 'use strict';
15
+
14
16
 
15
17
  /** True if debug is enabled
16
18
  * @type {Boolean}
@@ -418,7 +420,7 @@ function debugRender()
418
420
  * @namespace Utilities
419
421
  */
420
422
 
421
- 'use strict';
423
+
422
424
 
423
425
  /** A shortcut to get Math.PI
424
426
  * @type {Number}
@@ -615,10 +617,10 @@ function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear)
615
617
  * - Can be used to create a deterministic random number sequence
616
618
  * @example
617
619
  * let r = new RandomGenerator(123); // random number generator with seed 123
618
- * let a = r.rand(); // random value between 0 and 1
619
- * 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
620
622
  * r.seed = 123; // reset the seed
621
- * let c = r.rand(); // the same value as a
623
+ * let c = r.float(); // the same value as a
622
624
  */
623
625
  class RandomGenerator
624
626
  {
@@ -647,18 +649,18 @@ class RandomGenerator
647
649
  * @param {Number} valueA
648
650
  * @param {Number} [valueB=0]
649
651
  * @return {Number} */
650
- int(valueA, valueB=0) { return Math.floor(this.rand(valueA, valueB)); }
652
+ int(valueA, valueB=0) { return Math.floor(this.float(valueA, valueB)); }
651
653
 
652
654
  /** Randomly returns either -1 or 1 deterministically
653
655
  * @return {Number} */
654
- sign() { return this.randInt(2) * 2 - 1; }
656
+ sign() { return this.int(2) * 2 - 1; }
655
657
  }
656
658
 
657
659
  ///////////////////////////////////////////////////////////////////////////////
658
660
 
659
661
  /**
660
662
  * Create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
661
- * @param {Number} [x=0]
663
+ * @param {(Number|Vector2)} [x=0]
662
664
  * @param {Number} [y=0]
663
665
  * @return {Vector2}
664
666
  * @example
@@ -1073,7 +1075,7 @@ class Timer
1073
1075
  * @namespace Settings
1074
1076
  */
1075
1077
 
1076
- 'use strict';
1078
+
1077
1079
 
1078
1080
  ///////////////////////////////////////////////////////////////////////////////
1079
1081
  // Camera settings
@@ -1142,7 +1144,7 @@ let glOverlay = 1;
1142
1144
  * @memberof Settings */
1143
1145
  let tileSizeDefault = vec2(16);
1144
1146
 
1145
- /** Prevent tile bleeding from neighbors in pixels
1147
+ /** How many pixels smaller to draw tiles to prevent bleeding from neighbors
1146
1148
  * @type {Number}
1147
1149
  * @default
1148
1150
  * @memberof Settings */
@@ -1316,12 +1318,205 @@ let medalDisplayIconSize = 50;
1316
1318
  * @type {Boolean}
1317
1319
  * @default 0
1318
1320
  * @memberof Settings */
1319
- 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; }
1320
1515
  /**
1321
1516
  * LittleJS Object System
1322
1517
  */
1323
1518
 
1324
- 'use strict';
1519
+
1325
1520
 
1326
1521
  /**
1327
1522
  * LittleJS Object Base Object Class
@@ -1351,18 +1546,19 @@ let medalsPreventUnlock;
1351
1546
  class EngineObject
1352
1547
  {
1353
1548
  /** Create an engine object and adds it to the list of objects
1354
- * @param {Vector2} [position=Vector2()] - World space position of the object
1355
- * @param {Vector2} [size=Vector2(1,1)] - World space size of the object
1356
- * @param {Number} [tileIndex=-1] - Tile to use to render object (-1 is untextured)
1357
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
1358
- * @param {Number} [angle=0] - Angle the object is rotated by
1359
- * @param {Color} [color=Color()] - Color to apply to tile when rendered
1360
- * @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
1361
1555
  */
1362
- 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)
1363
1557
  {
1364
1558
  // set passed in params
1365
1559
  ASSERT(isVector2(pos) && isVector2(size)); // ensure pos and size are vec2s
1560
+ ASSERT(typeof tileInfo !== 'number' || !tileInfo); // prevent old style calls
1561
+ // to fix old calls, replace with tile(tileIndex, tileSize)
1366
1562
 
1367
1563
  /** @property {Vector2} - World space position of the object */
1368
1564
  this.pos = pos.copy();
@@ -1370,10 +1566,8 @@ class EngineObject
1370
1566
  this.size = size;
1371
1567
  /** @property {Vector2} - Size of object used for drawing, uses size if not set */
1372
1568
  this.drawSize;
1373
- /** @property {Number} - Tile to use to render object (-1 is untextured) */
1374
- this.tileIndex = tileIndex;
1375
- /** @property {Vector2} - Size of tile in source pixels */
1376
- this.tileSize = tileSize;
1569
+ /** @property {TileInfo} - Tile info to render object (undefined is untextured) */
1570
+ this.tileInfo = tileInfo;
1377
1571
  /** @property {Number} - Angle to rotate the object */
1378
1572
  this.angle = angle;
1379
1573
  /** @property {Color} - Color to apply when rendered */
@@ -1589,7 +1783,7 @@ class EngineObject
1589
1783
  render()
1590
1784
  {
1591
1785
  // default object render
1592
- drawTile(this.pos, this.drawSize || this.size, this.tileIndex, this.tileSize, this.color, this.angle, this.mirror, this.additiveColor);
1786
+ drawTile(this.pos, this.drawSize || this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
1593
1787
  }
1594
1788
 
1595
1789
  /** Destroy this object, destroy it's children, detach it's parent, and mark it for removal */
@@ -1717,7 +1911,7 @@ class EngineObject
1717
1911
  * @namespace Draw
1718
1912
  */
1719
1913
 
1720
- 'use strict';
1914
+
1721
1915
 
1722
1916
  /** The primary 2D canvas visible to the user
1723
1917
  * @type {HTMLCanvasElement}
@@ -1744,13 +1938,109 @@ let overlayContext;
1744
1938
  * @memberof Draw */
1745
1939
  let mainCanvasSize = vec2();
1746
1940
 
1747
- /** Tile sheet for batch rendering system
1748
- * @type {CanvasImageSource}
1941
+ /** Array containing texture info for batch rendering system
1942
+ * @type {Array}
1749
1943
  * @memberof Draw */
1750
- const tileImage = new Image;
1944
+ let textureInfos = [];
1751
1945
 
1752
1946
  // Engine internal variables not exposed to documentation
1753
- let tileImageSize, tileImageFixBleed, drawCount;
1947
+ let drawCount;
1948
+
1949
+ ///////////////////////////////////////////////////////////////////////////////
1950
+
1951
+ /**
1952
+ * Create a tile info object
1953
+ * - This can take vecs or floats for easier use and conversion
1954
+ * - If an index is passed in, the tile size and index will determine the position
1955
+ * @param {(Number|Vector2)} [pos=Vector2()] - Top left corner of tile in pixels or index
1956
+ * @param {(Number|Vector2)} [size=tileSizeDefault] - Size of tile in pixels
1957
+ * @param {Number} [textureIndex=0] - Texture index to use
1958
+ * @return {TileInfo}
1959
+ * @example
1960
+ * tile(2) // a tile at index 2 using the default tile size of 16
1961
+ * tile(5, 8) // a tile at index 5 using a tile size of 8
1962
+ * tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
1963
+ * tile(vec2(4,8), vec2(30,10)) // a tile at pixel location (4,8) with a size of (30,10)
1964
+ * @memberof Draw
1965
+ */
1966
+ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
1967
+ {
1968
+ // if size is a number, make it a vector
1969
+ if (size.x == undefined)
1970
+ {
1971
+ ASSERT(size > 0);
1972
+ size = vec2(size);
1973
+ }
1974
+
1975
+ // if pos is a number, use it as a tile index
1976
+ if (pos.x == undefined)
1977
+ {
1978
+ const textureInfo = textureInfos[textureIndex];
1979
+ if (textureInfo)
1980
+ {
1981
+ const cols = textureInfo.size.x / size.x |0;
1982
+ pos = vec2((pos%cols)*size.x, (pos/cols|0)*size.y);
1983
+ }
1984
+ else
1985
+ pos = vec2();
1986
+ }
1987
+
1988
+ // return a tile info object
1989
+ return new TileInfo(pos, size, textureIndex);
1990
+ }
1991
+
1992
+ /**
1993
+ * Tile Info - Stores info about how to draw a tile
1994
+ */
1995
+ class TileInfo
1996
+ {
1997
+ /** Create a tile info object
1998
+ * @param {Vector2} [pos=Vector2()] - Top left corner of tile in pixels
1999
+ * @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
2000
+ * @param {Number} [textureIndex=0] - Texture index to use
2001
+ */
2002
+ constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
2003
+ {
2004
+ /** @property {Vector2} - Top left corner of tile in pixels */
2005
+ this.pos = pos;
2006
+ /** @property {Vector2} - Size of tile in pixels */
2007
+ this.size = size;
2008
+ /** @property {Number} - Texture index to use */
2009
+ this.textureIndex = textureIndex;
2010
+ }
2011
+
2012
+ /** Returns an offset copy of this tile, useful for animation
2013
+ * @param {Vector2} offset - Offset to apply in pixels
2014
+ * @return {TileInfo}
2015
+ */
2016
+ offset(offset)
2017
+ { return new TileInfo(this.pos.add(offset), this.size, this.textureIndex); }
2018
+
2019
+ /** Returns the texture info for this tile
2020
+ * @return {TextureInfo}
2021
+ */
2022
+ getTextureInfo()
2023
+ { return textureInfos[this.textureIndex]; }
2024
+ }
2025
+
2026
+ /** Texture Info - Stores info about each texture */
2027
+ class TextureInfo
2028
+ {
2029
+ // create a TextureInfo, called automatically by the engine
2030
+ constructor(image)
2031
+ {
2032
+ /** @property {CanvasImageSource} - image source */
2033
+ this.image = image;
2034
+ /** @property {Vector2} - size of the image */
2035
+ this.size = vec2(image.width, image.height);
2036
+ /** @property {WebGLTexture} - webgl texture */
2037
+ this.glTexture = glEnable && glCreateTexture(image);
2038
+ /** @property {Vector2} - size to adjust tile to fix bleeding */
2039
+ this.fixBleedSize = vec2(tileFixBleedScale).divide(this.size);
2040
+ }
2041
+ }
2042
+
2043
+ ///////////////////////////////////////////////////////////////////////////////
1754
2044
 
1755
2045
  /** Convert from screen to world space coordinates
1756
2046
  * @param {Vector2} screenPos
@@ -1781,7 +2071,7 @@ function worldToScreen(worldPos)
1781
2071
  /** Draw textured tile centered in world space, with color applied if using WebGL
1782
2072
  * @param {Vector2} pos - Center of the tile in world space
1783
2073
  * @param {Vector2} [size=Vector2(1,1)] - Size of the tile in world space
1784
- * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
2074
+ * @param {TileInfo}[tileInfo] - Tile info to use, untextured if undefined
1785
2075
  * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
1786
2076
  * @param {Color} [color=Color()] - Color to modulate with
1787
2077
  * @param {Number} [angle=0] - Angle to rotate by
@@ -1790,11 +2080,14 @@ function worldToScreen(worldPos)
1790
2080
  * @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
1791
2081
  * @param {Boolean} [screenSpace=0] - If true the pos and size are in screen space
1792
2082
  * @memberof Draw */
1793
- function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color,
2083
+ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
1794
2084
  angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace)
1795
2085
  {
2086
+ ASSERT(typeof tileInfo !== 'number' || !tileInfo); // prevent old style calls
2087
+ // to fix old calls, replace with tile(tileIndex, tileSize)
2088
+
1796
2089
  showWatermark && ++drawCount;
1797
-
2090
+ const textureInfo = tileInfo && tileInfo.getTextureInfo();
1798
2091
  if (glEnable && useWebGL)
1799
2092
  {
1800
2093
  if (screenSpace)
@@ -1803,46 +2096,48 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
1803
2096
  pos = screenToWorld(pos);
1804
2097
  size = size.scale(1/cameraScale);
1805
2098
  }
1806
- if (tileIndex < 0 || !tileImage.width)
1807
- {
1808
- // if negative tile index or image not found, force untextured
1809
- glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
1810
- }
1811
- else
2099
+
2100
+ if (textureInfo)
1812
2101
  {
1813
2102
  // calculate uvs and render
1814
- const cols = tileImageSize.x / tileSize.x |0;
1815
- const uvSizeX = tileSize.x / tileImageSize.x;
1816
- const uvSizeY = tileSize.y / tileImageSize.y;
1817
- const uvX = (tileIndex%cols)*uvSizeX, uvY = (tileIndex/cols|0)*uvSizeY;
1818
-
2103
+ const x = tileInfo.pos.x / textureInfo.size.x;
2104
+ const y = tileInfo.pos.y / textureInfo.size.y;
2105
+ const w = tileInfo.size.x / textureInfo.size.x;
2106
+ const h = tileInfo.size.y / textureInfo.size.y;
2107
+ const tileImageFixBleed = textureInfo.fixBleedSize;
2108
+ glSetTexture(textureInfo.glTexture);
1819
2109
  glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
1820
- uvX + tileImageFixBleed.x, uvY + tileImageFixBleed.y,
1821
- uvX - tileImageFixBleed.x + uvSizeX, uvY - tileImageFixBleed.y + uvSizeY,
2110
+ x + tileImageFixBleed.x, y + tileImageFixBleed.y,
2111
+ x - tileImageFixBleed.x + w, y - tileImageFixBleed.y + h,
1822
2112
  color.rgbaInt(), additiveColor.rgbaInt());
1823
2113
  }
2114
+ else
2115
+ {
2116
+ // if no tile info, force untextured
2117
+ glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
2118
+ }
1824
2119
  }
1825
2120
  else
1826
2121
  {
1827
2122
  // normal canvas 2D rendering method (slower)
1828
2123
  drawCanvas2D(pos, size, angle, mirror, (context)=>
1829
2124
  {
1830
- if (tileIndex < 0)
2125
+ if (textureInfo)
1831
2126
  {
1832
- // if negative tile index, force untextured
1833
- context.fillStyle = color;
1834
- context.fillRect(-.5, -.5, 1, 1);
2127
+ // calculate uvs and render
2128
+ const x = tileInfo.pos.x + tileFixBleedScale;
2129
+ const y = tileInfo.pos.y + tileFixBleedScale;
2130
+ const w = tileInfo.size.x - 2*tileFixBleedScale;
2131
+ const h = tileInfo.size.y - 2*tileFixBleedScale;
2132
+ context.globalAlpha = color.a; // only alpha is supported
2133
+ context.drawImage(textureInfo.image, x, y, w, h, -.5, -.5, 1, 1);
2134
+ context.globalAlpha = 1; // set back to full alpha
1835
2135
  }
1836
2136
  else
1837
2137
  {
1838
- // calculate uvs and render
1839
- const cols = tileImageSize.x / tileSize.x |0;
1840
- const sX = (tileIndex%cols)*tileSize.x + tileFixBleedScale;
1841
- const sY = (tileIndex/cols|0)*tileSize.y + tileFixBleedScale;
1842
- const sWidth = tileSize.x - 2*tileFixBleedScale;
1843
- const sHeight = tileSize.y - 2*tileFixBleedScale;
1844
- context.globalAlpha = color.a; // only alpha is supported
1845
- context.drawImage(tileImage, sX, sY, sWidth, sHeight, -.5, -.5, 1, 1);
2138
+ // if no tile info, force untextured
2139
+ context.fillStyle = color;
2140
+ context.fillRect(-.5, -.5, 1, 1);
1846
2141
  }
1847
2142
  }, undefined, screenSpace);
1848
2143
  }
@@ -1857,7 +2152,7 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
1857
2152
  * @param {Boolean} [screenSpace=0]
1858
2153
  * @memberof Draw */
1859
2154
  function drawRect(pos, size, color, angle, useWebGL, screenSpace)
1860
- { drawTile(pos, size, -1, tileSizeDefault, color, angle, 0, undefined, useWebGL, screenSpace); }
2155
+ { drawTile(pos, size, undefined, color, angle, 0, undefined, useWebGL, screenSpace); }
1861
2156
 
1862
2157
  /** Draw colored polygon using passed in points
1863
2158
  * @param {Array} points - Array of Vector2 points
@@ -1908,12 +2203,12 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
1908
2203
  {
1909
2204
  if (!screenSpace)
1910
2205
  {
1911
- // create canvas transform from world space to screen space
2206
+ // transform from world space to screen space
1912
2207
  pos = worldToScreen(pos);
1913
2208
  size = size.scale(cameraScale);
1914
2209
  }
1915
2210
  context.save();
1916
- context.translate(pos.x+.5|0, pos.y+.5|0);
2211
+ context.translate(pos.x+.5, pos.y+.5);
1917
2212
  context.rotate(angle);
1918
2213
  context.scale(mirror ? -size.x : size.x, size.y);
1919
2214
  drawFunction(context);
@@ -2002,10 +2297,9 @@ class FontImage
2002
2297
  * @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
2003
2298
  * @param {Vector2} [tileSize=vec2(8)] - Size of the font source tiles
2004
2299
  * @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
2005
- * @param {Number} [startTileIndex=0] - Tile index in image where font starts
2006
2300
  * @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
2007
2301
  */
2008
- constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
2302
+ constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), context=overlayContext)
2009
2303
  {
2010
2304
  // load default font image
2011
2305
  if (!engineFontImage)
@@ -2014,7 +2308,6 @@ class FontImage
2014
2308
  this.image = image || engineFontImage;
2015
2309
  this.tileSize = tileSize;
2016
2310
  this.paddingSize = paddingSize;
2017
- this.startTileIndex = startTileIndex;
2018
2311
  this.context = context;
2019
2312
  }
2020
2313
 
@@ -2055,7 +2348,7 @@ class FontImage
2055
2348
  charCode = 127; // unknown character
2056
2349
 
2057
2350
  // get the character source location and draw it
2058
- const tile = this.startTileIndex + charCode - 32;
2351
+ const tile = charCode - 32;
2059
2352
  const x = tile % cols;
2060
2353
  const y = tile / cols |0;
2061
2354
  const drawPos = pos.add(vec2(j,i).multiply(drawSize));
@@ -2087,8 +2380,7 @@ function toggleFullscreen()
2087
2380
  }
2088
2381
  else if (document.body.requestFullscreen)
2089
2382
  document.body.requestFullscreen();
2090
- }
2091
-
2383
+ }
2092
2384
  /**
2093
2385
  * LittleJS Input System
2094
2386
  * - Tracks keyboard down, pressed, and released
@@ -2098,7 +2390,7 @@ function toggleFullscreen()
2098
2390
  * @namespace Input
2099
2391
  */
2100
2392
 
2101
- 'use strict';
2393
+
2102
2394
 
2103
2395
  /** Returns true if device key is down
2104
2396
  * @param {Number} key
@@ -2282,18 +2574,26 @@ function mouseToScreen(mousePos)
2282
2574
  const stickData = [];
2283
2575
  function gamepadsUpdate()
2284
2576
  {
2285
- if (touchGamepadEnable && touchGamepadTimer.isSet())
2577
+ // update touch gamepad if enabled
2578
+ if (touchGamepadEnable && isTouchDevice)
2286
2579
  {
2287
- // read virtual analog stick
2288
- const sticks = stickData[0] || (stickData[0] = []);
2289
- sticks[0] = vec2(touchGamepadStick.x, -touchGamepadStick.y); // flip vertical
2580
+ // create the touch gamepad if it doesn't exist
2581
+ if (!touchGamepadButtons)
2582
+ createTouchGamepad();
2290
2583
 
2291
- // read virtual gamepad buttons
2292
- const data = inputData[1] || (inputData[1] = []);
2293
- for (let i=10; i--;)
2584
+ if (touchGamepadTimer.isSet())
2294
2585
  {
2295
- const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
2296
- data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
2586
+ // read virtual analog stick
2587
+ const sticks = stickData[0] || (stickData[0] = []);
2588
+ sticks[0] = vec2(touchGamepadStick.x, -touchGamepadStick.y); // flip vertical
2589
+
2590
+ // read virtual gamepad buttons
2591
+ const data = inputData[1] || (inputData[1] = []);
2592
+ for (let i=10; i--;)
2593
+ {
2594
+ const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
2595
+ data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
2596
+ }
2297
2597
  }
2298
2598
  }
2299
2599
 
@@ -2390,6 +2690,10 @@ if (isTouchDevice)
2390
2690
  // set was touching
2391
2691
  wasTouching = touching;
2392
2692
 
2693
+ // prevent default handling like copy and magnifier lens
2694
+ if (document.hasFocus()) // allow document to get focus
2695
+ e.preventDefault();
2696
+
2393
2697
  // must return true so the document will get focus
2394
2698
  return true;
2395
2699
  }
@@ -2402,7 +2706,7 @@ if (isTouchDevice)
2402
2706
  let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
2403
2707
 
2404
2708
  // create the touch gamepad, called automatically by the engine
2405
- if (touchGamepadEnable)
2709
+ function createTouchGamepad()
2406
2710
  {
2407
2711
  // touch input internal variables
2408
2712
  touchGamepadButtons = [];
@@ -2536,7 +2840,7 @@ function touchGamepadRender()
2536
2840
  * @namespace Audio
2537
2841
  */
2538
2842
 
2539
- 'use strict';
2843
+
2540
2844
 
2541
2845
  /**
2542
2846
  * Sound Object - Stores a zzfx sound for later use and can be played positionally
@@ -2572,8 +2876,9 @@ class Sound
2572
2876
  if (zzfxSound)
2573
2877
  {
2574
2878
  // generate zzfx sound now for fast playback
2575
- this.randomness = zzfxSound[1] || (zzfxSound[1] = 0);
2576
- this.cachedSamples = zzfxSound && zzfxG(...zzfxSound);
2879
+ this.randomness = zzfxSound[1] || (zzfxSound[1] = 0);
2880
+ this.sampleChannels = [zzfxG(...zzfxSound)];
2881
+ this.sampleRate = zzfxR;
2577
2882
  }
2578
2883
  }
2579
2884
 
@@ -2582,11 +2887,12 @@ class Sound
2582
2887
  * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
2583
2888
  * @param {Number} [pitch=1] - How much to scale pitch by (also adjusted by this.randomness)
2584
2889
  * @param {Number} [randomnessScale=1] - How much to scale randomness
2585
- * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
2890
+ * @param {Boolean} [loop=0] - Should the sound loop
2891
+ * @return {AudioBufferSourceNode} - The audio source node
2586
2892
  */
2587
- play(pos, volume=1, pitch=1, randomnessScale=1)
2893
+ play(pos, volume=1, pitch=1, randomnessScale=1, loop=0)
2588
2894
  {
2589
- if (!soundEnable || !this.cachedSamples) return;
2895
+ if (!soundEnable || !this.sampleChannels) return;
2590
2896
 
2591
2897
  let pan;
2592
2898
  if (pos)
@@ -2609,43 +2915,80 @@ class Sound
2609
2915
 
2610
2916
  // play the sound
2611
2917
  const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
2612
- return playSamples([this.cachedSamples], volume, playbackRate, pan);
2918
+ return this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate);
2919
+ }
2920
+
2921
+ /** Stop the last instance of this sound that was played */
2922
+ stop()
2923
+ {
2924
+ if (this.source)
2925
+ this.source.stop();
2926
+ this.source = 0;
2613
2927
  }
2614
2928
 
2615
2929
  /** Play the sound as a note with a semitone offset
2616
2930
  * @param {Number} semitoneOffset - How many semitones to offset pitch
2617
2931
  * @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null
2618
2932
  * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
2619
- * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
2933
+ * @return {AudioBufferSourceNode} - The audio source node
2620
2934
  */
2621
2935
  playNote(semitoneOffset, pos, volume)
2622
2936
  { return this.play(pos, volume, 2**(semitoneOffset/12), 0); }
2937
+
2938
+ /** Get how long this sound is in seconds
2939
+ * @return {Number} - How long the sound is in seconds (undefined if loading)
2940
+ */
2941
+ getDuration()
2942
+ { return this.sampleChannels && this.sampleChannels[0].length / this.sampleRate; }
2943
+
2944
+ /** Check if the last instance of this sound is playing
2945
+ * @return {Boolean} - True if the sound is playing
2946
+ */
2947
+ isPlaying() { return this.source && !this.source.ended; }
2948
+
2949
+ /** Check if sound is loading, for sounds fetched from a url
2950
+ * @return {Boolean} - True if sound is loading and not ready to play
2951
+ */
2952
+ isLoading() { return !this.sampleChannels; }
2623
2953
  }
2624
2954
 
2625
2955
  /**
2626
2956
  * Sound Wave Object - Stores a wave sound for later use and can be played positionally
2957
+ * - this can be used to play wave, mp3, and ogg files
2958
+ * @example
2959
+ * // create a sound
2960
+ * const sound_example = new SoundWave('sound.mp3');
2961
+ *
2962
+ * // play the sound
2963
+ * sound_example.play();
2627
2964
  */
2628
2965
  class SoundWave extends Sound
2629
2966
  {
2630
2967
  /** Create a sound object and cache the wave file for later use
2631
- * @param {String} waveFilename - Filename of wave file to load
2632
- * @param {Number} [randomness=.05] - How much to randomize frequency each time sound plays
2968
+ * @param {String} filename - Filename of audio file to load
2969
+ * @param {Number} [randomness=0] - How much to randomize frequency each time sound plays
2633
2970
  * @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
2634
2971
  * @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
2635
2972
  */
2636
- constructor(waveFilename, randomness=.05, range, taper)
2973
+ constructor(filename, randomness=0, range, taper)
2637
2974
  {
2638
2975
  super(0, range, taper);
2639
2976
  this.randomness = randomness;
2640
2977
 
2641
2978
  if (!soundEnable) return;
2642
- if (!soundWaveDecoderContext)
2979
+ if (!soundDecoderContext)
2643
2980
  soundDecoderContext = new AudioContext;
2644
2981
 
2645
- fetch(waveFilename)
2982
+ fetch(filename)
2646
2983
  .then(response => response.arrayBuffer())
2647
- .then(arrayBuffer => soundWaveDecoderContext.decodeAudioData(arrayBuffer))
2648
- .then(audioBuffer => this.cachedSamples = audioBuffer.getChannelData(0));
2984
+ .then(arrayBuffer => soundDecoderContext.decodeAudioData(arrayBuffer))
2985
+ .then(audioBuffer =>
2986
+ {
2987
+ this.sampleChannels = [];
2988
+ for (let i = audioBuffer.numberOfChannels; i--;)
2989
+ this.sampleChannels[i] = audioBuffer.getChannelData(i);
2990
+ this.sampleRate = audioBuffer.sampleRate;
2991
+ });
2649
2992
  }
2650
2993
  }
2651
2994
  let soundDecoderContext; // audio context used only to decode audio files
@@ -2680,48 +3023,34 @@ let soundDecoderContext; // audio context used only to decode audio files
2680
3023
  * // play the music
2681
3024
  * music_example.play();
2682
3025
  */
2683
- class Music
3026
+ class Music extends Sound
2684
3027
  {
2685
3028
  /** Create a music object and cache the zzfx music samples for later use
2686
3029
  * @param {Array} zzfxMusic - Array of zzfx music parameters
2687
3030
  */
2688
3031
  constructor(zzfxMusic)
2689
3032
  {
2690
- if (!soundEnable) return;
3033
+ super();
2691
3034
 
2692
- this.cachedSamples = zzfxM(...zzfxMusic);
3035
+ if (!soundEnable) return;
3036
+ this.randomness = 0;
3037
+ this.sampleChannels = zzfxM(...zzfxMusic);
3038
+ this.sampleRate = zzfxR;
2693
3039
  }
2694
3040
 
2695
3041
  /** Play the music
2696
3042
  * @param {Number} [volume=1] - How much to scale volume by
2697
- * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
2698
- * @return {AudioBufferSourceNode} - The audio node, can be used to stop sound later
3043
+ * @param {Boolean} [loop=1] - True if the music should loop
3044
+ * @return {AudioBufferSourceNode} - The audio source node
2699
3045
  */
2700
3046
  play(volume, loop = 1)
2701
- {
2702
- if (!soundEnable) return;
2703
-
2704
- return this.source = playSamples(this.cachedSamples, volume, 1, 0, loop);
2705
- }
2706
-
2707
- /** Stop the music */
2708
- stop()
2709
- {
2710
- if (this.source)
2711
- this.source.stop();
2712
- this.source = 0;
2713
- }
2714
-
2715
- /** Check if music is playing
2716
- * @return {Boolean}
2717
- */
2718
- isPlaying() { return this.source; }
3047
+ { return super.play(0, volume, 1, 1, loop); }
2719
3048
  }
2720
3049
 
2721
- /** Play an mp3 or wav audio from a local file or url
3050
+ /** Play an mp3, ogg, or wav audio from a local file or url
2722
3051
  * @param {String} url - Location of sound file to play
2723
3052
  * @param {Number} [volume=1] - How much to scale volume by
2724
- * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
3053
+ * @param {Boolean} [loop=1] - True if the music should loop
2725
3054
  * @return {HTMLAudioElement} - The audio element for this sound
2726
3055
  * @memberof Audio */
2727
3056
  function playAudioFile(url, volume=1, loop=1)
@@ -2785,9 +3114,10 @@ let audioContext;
2785
3114
  * @param {Number} [rate=1] - The playback rate to use
2786
3115
  * @param {Number} [pan=0] - How much to apply stereo panning
2787
3116
  * @param {Boolean} [loop=0] - True if the sound should loop when it reaches the end
3117
+ * @param {Number} [sampleRate=44100] - Sample rate for the sound
2788
3118
  * @return {AudioBufferSourceNode} - The audio node of the sound played
2789
3119
  * @memberof Audio */
2790
- function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
3120
+ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0, sampleRate=zzfxR)
2791
3121
  {
2792
3122
  if (!soundEnable) return;
2793
3123
 
@@ -2804,7 +3134,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
2804
3134
  }
2805
3135
 
2806
3136
  // create buffer and source
2807
- const buffer = audioContext.createBuffer(sampleChannels.length, sampleChannels[0].length, zzfxR),
3137
+ const buffer = audioContext.createBuffer(sampleChannels.length, sampleChannels[0].length, sampleRate),
2808
3138
  source = audioContext.createBufferSource();
2809
3139
 
2810
3140
  // copy samples to buffer and setup source
@@ -3063,7 +3393,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
3063
3393
  * @namespace TileCollision
3064
3394
  */
3065
3395
 
3066
- 'use strict';
3396
+
3067
3397
 
3068
3398
  /** The tile collision layer array, use setTileCollisionData and getTileCollisionData to access
3069
3399
  * @type {Array}
@@ -3208,7 +3538,7 @@ class TileLayerData
3208
3538
  }
3209
3539
 
3210
3540
  /**
3211
- * Tile layer object - cached rendering system for tile layers
3541
+ * Tile Layer - cached rendering system for tile layers
3212
3542
  * - Each Tile layer is rendered to an off screen canvas
3213
3543
  * - To allow dynamic modifications, layers are rendered using canvas 2d
3214
3544
  * - Some devices like mobile phones are limited to 4k texture resolution
@@ -3224,13 +3554,13 @@ class TileLayer extends EngineObject
3224
3554
  /** Create a tile layer object
3225
3555
  * @param {Vector2} [position=Vector2()] - World space position
3226
3556
  * @param {Vector2} [size=tileCollisionSize] - World space size
3227
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
3557
+ * @param {TileInfo} [tileInfo] - Tile info for layer
3228
3558
  * @param {Vector2} [scale=Vector2(1,1)] - How much to scale this layer when rendered
3229
3559
  * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
3230
3560
  */
3231
- constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
3561
+ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderOrder=0)
3232
3562
  {
3233
- super(pos, size, -1, tileSize, 0, undefined, renderOrder);
3563
+ super(pos, size, tileInfo, 0, undefined, renderOrder);
3234
3564
 
3235
3565
  /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
3236
3566
  this.canvas = document.createElement('canvas');
@@ -3307,13 +3637,13 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3307
3637
  mainCanvas = this.canvas;
3308
3638
  mainContext = this.context;
3309
3639
  cameraPos = this.size.scale(.5);
3310
- cameraScale = this.tileSize.x;
3640
+ cameraScale = this.tileInfo.size.x;
3311
3641
 
3312
3642
  if (clear)
3313
3643
  {
3314
3644
  // clear and set size
3315
- mainCanvas.width = this.size.x * this.tileSize.x;
3316
- mainCanvas.height = this.size.y * this.tileSize.y;
3645
+ mainCanvas.width = this.size.x * this.tileInfo.size.x;
3646
+ mainCanvas.height = this.size.y * this.tileInfo.size.y;
3317
3647
  }
3318
3648
 
3319
3649
  // begin a new render for the tile canvas
@@ -3344,7 +3674,8 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3344
3674
  if (d.tile != undefined)
3345
3675
  {
3346
3676
  ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
3347
- drawTile(pos, vec2(1), d.tile, this.tileSize, d.color, d.direction*PI/2, d.mirror);
3677
+ const tileInfo = tile(d.tile, this.tileInfo.size, this.tileInfo.textureIndex);
3678
+ drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
3348
3679
  }
3349
3680
  }
3350
3681
 
@@ -3366,8 +3697,8 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3366
3697
  {
3367
3698
  const context = this.context;
3368
3699
  context.save();
3369
- pos = pos.subtract(this.pos).multiply(this.tileSize);
3370
- size = size.multiply(this.tileSize);
3700
+ pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
3701
+ size = size.multiply(this.tileInfo.size);
3371
3702
  context.translate(pos.x, this.canvas.height - pos.y);
3372
3703
  context.rotate(angle);
3373
3704
  context.scale(mirror ? -size.x : size.x, size.y);
@@ -3378,28 +3709,28 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3378
3709
  /** Draw a tile directly onto the layer canvas
3379
3710
  * @param {Vector2} pos
3380
3711
  * @param {Vector2} [size=Vector2(1,1)]
3381
- * @param {Number} [tileIndex=-1]
3382
- * @param {Vector2} [tileSize=tileSizeDefault]
3712
+ * @param {TileInfo} [tileInfo]
3383
3713
  * @param {Color} [color=Color()]
3384
3714
  * @param {Number} [angle=0]
3385
3715
  * @param {Boolean} [mirror=0] */
3386
- drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle, mirror)
3716
+ drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle, mirror)
3387
3717
  {
3388
3718
  this.drawCanvas2D(pos, size, angle, mirror, (context)=>
3389
3719
  {
3390
- if (tileIndex < 0)
3720
+ const textureInfo = tileInfo && tileInfo.getTextureInfo();
3721
+ if (textureInfo)
3391
3722
  {
3392
- // untextured
3393
- context.fillStyle = color;
3394
- context.fillRect(-.5, -.5, 1, 1);
3723
+ context.globalAlpha = color.a; // only alpha is supported
3724
+ context.drawImage(textureInfo.image,
3725
+ tileInfo.pos.x, tileInfo.pos.y,
3726
+ tileInfo.size.x, tileInfo.size.y, -.5, -.5, 1, 1);
3727
+ context.globalAlpha = 1;
3395
3728
  }
3396
3729
  else
3397
3730
  {
3398
- const cols = tileImage.width/tileSize.x;
3399
- context.globalAlpha = color.a; // only alpha, no color, is supported in this mode
3400
- context.drawImage(tileImage,
3401
- (tileIndex%cols)*tileSize.x, (tileIndex/cols|0)*tileSize.y,
3402
- tileSize.x, tileSize.y, -.5, -.5, 1, 1);
3731
+ // untextured
3732
+ context.fillStyle = color;
3733
+ context.fillRect(-.5, -.5, 1, 1);
3403
3734
  }
3404
3735
  });
3405
3736
  }
@@ -3416,7 +3747,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3416
3747
  * LittleJS Particle System
3417
3748
  */
3418
3749
 
3419
- 'use strict';
3750
+
3420
3751
 
3421
3752
  /**
3422
3753
  * Particle Emitter - Spawns particles with the given settings
@@ -3427,7 +3758,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3427
3758
  * let particleEmiter = new ParticleEmitter
3428
3759
  * (
3429
3760
  * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
3430
- * 0, vec2(16), // tileIndex, tileSize
3761
+ * tile(0, 16), // tileInfo
3431
3762
  * new Color(1,1,1), new Color(0,0,0), // colorStartA, colorStartB
3432
3763
  * new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
3433
3764
  * 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
@@ -3444,8 +3775,7 @@ class ParticleEmitter extends EngineObject
3444
3775
  * @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
3445
3776
  * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
3446
3777
  * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
3447
- * @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied
3448
- * @param {Vector2} [tileSize=tileSizeDefault] - Tile size for particles
3778
+ * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
3449
3779
  * @param {Color} [colorStartA=Color()] - Color at start of life 1, randomized between start colors
3450
3780
  * @param {Color} [colorStartB=Color()] - Color at start of life 2, randomized between start colors
3451
3781
  * @param {Color} [colorEndA=Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
@@ -3475,8 +3805,7 @@ class ParticleEmitter extends EngineObject
3475
3805
  emitTime = 0,
3476
3806
  emitRate = 100,
3477
3807
  emitConeAngle = PI,
3478
- tileIndex = -1,
3479
- tileSize = tileSizeDefault,
3808
+ tileInfo,
3480
3809
  colorStartA = new Color,
3481
3810
  colorStartB = new Color,
3482
3811
  colorEndA = new Color(1,1,1,0),
@@ -3499,7 +3828,7 @@ class ParticleEmitter extends EngineObject
3499
3828
  localSpace
3500
3829
  )
3501
3830
  {
3502
- super(pos, vec2(), tileIndex, tileSize, angle, undefined, renderOrder);
3831
+ super(pos, vec2(), tileInfo, angle, undefined, renderOrder);
3503
3832
 
3504
3833
  // emitter settings
3505
3834
  /** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
@@ -3598,7 +3927,7 @@ class ParticleEmitter extends EngineObject
3598
3927
  angle += this.angle;
3599
3928
  }
3600
3929
 
3601
- const particle = new Particle(pos, this.tileIndex, this.tileSize, angle);
3930
+ const particle = new Particle(pos, this.tileInfo, this.tileSize, angle);
3602
3931
 
3603
3932
  // randomness scales each paremeter by a percentage
3604
3933
  const randomness = this.randomness;
@@ -3658,12 +3987,11 @@ class Particle extends EngineObject
3658
3987
  /**
3659
3988
  * Create a particle with the given settings
3660
3989
  * @param {Vector2} position - World space position of the particle
3661
- * @param {Number} [tileIndex=-1] - Tile to use to render, untextured if -1
3662
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
3990
+ * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
3663
3991
  * @param {Number} [angle=0] - Angle to rotate the particle
3664
3992
  */
3665
- constructor(pos, tileIndex, tileSize, angle)
3666
- { super(pos, vec2(), tileIndex, tileSize, angle); }
3993
+ constructor(pos, tileInfo, angle)
3994
+ { super(pos, vec2(), tileInfo, angle); }
3667
3995
 
3668
3996
  /** Render the particle, automatically called each frame, sorted by renderOrder */
3669
3997
  render()
@@ -3697,14 +4025,17 @@ class Particle extends EngineObject
3697
4025
  if (this.localSpaceEmitter)
3698
4026
  velocity = velocity.rotate(-this.localSpaceEmitter.angle);
3699
4027
  const speed = velocity.length();
3700
- const direction = velocity.scale(1/speed);
3701
- const trailLength = speed * this.trailScale;
3702
- size.y = max(size.x, trailLength);
3703
- angle = direction.angle();
3704
- drawTile(pos.add(direction.multiply(vec2(0,-trailLength/2))), size, this.tileIndex, this.tileSize, color, angle, this.mirror);
4028
+ if (speed)
4029
+ {
4030
+ const direction = velocity.scale(1/speed);
4031
+ const trailLength = speed * this.trailScale;
4032
+ size.y = max(size.x, trailLength);
4033
+ angle = direction.angle();
4034
+ drawTile(pos.add(direction.multiply(vec2(0,-trailLength/2))), size, this.tileInfo, color, angle, this.mirror);
4035
+ }
3705
4036
  }
3706
4037
  else
3707
- drawTile(pos, size, this.tileIndex, this.tileSize, color, angle, this.mirror);
4038
+ drawTile(pos, size, this.tileInfo, color, angle, this.mirror);
3708
4039
  this.additive && setBlendMode();
3709
4040
  debugParticles && debugRect(pos, size, '#f005', 0, angle);
3710
4041
 
@@ -3726,7 +4057,7 @@ class Particle extends EngineObject
3726
4057
  * @namespace Medals
3727
4058
  */
3728
4059
 
3729
- 'use strict';
4060
+
3730
4061
 
3731
4062
  /** List of all medals
3732
4063
  * @type {Array}
@@ -3751,7 +4082,7 @@ function medalsInit(saveName)
3751
4082
  }
3752
4083
 
3753
4084
  /**
3754
- * Medal Object - Tracks an unlockable medal
4085
+ * Medal - Tracks an unlockable medal
3755
4086
  * @example
3756
4087
  * // create a medal
3757
4088
  * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
@@ -3764,7 +4095,7 @@ function medalsInit(saveName)
3764
4095
  */
3765
4096
  class Medal
3766
4097
  {
3767
- /** Create an medal object and adds it to the list of medals
4098
+ /** Create a medal object and adds it to the list of medals
3768
4099
  * @param {Number} id - The unique identifier of the medal
3769
4100
  * @param {String} name - Name of the medal
3770
4101
  * @param {String} [description] - Description of the medal
@@ -3876,33 +4207,33 @@ let newgrounds;
3876
4207
  /** This can used to enable Newgrounds functionality
3877
4208
  * @param {Number} app_id - The newgrounds App ID
3878
4209
  * @param {String} [cipher] - The encryption Key (AES-128/Base64)
4210
+ * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher
3879
4211
  * @memberof Medals */
3880
- function newgroundsInit(app_id, cipher) { newgrounds = new Newgrounds(app_id, cipher); }
4212
+ function newgroundsInit(app_id, cipher, cryptoJS)
4213
+ { newgrounds = new Newgrounds(app_id, cipher, cryptoJS); }
3881
4214
 
3882
4215
  /**
3883
4216
  * Newgrounds API wrapper object
3884
4217
  * @example
3885
- * // create a newgrounds object, replace the app id and cipher with your own
4218
+ * // create a newgrounds object, replace the app id with your own
3886
4219
  * const app_id = '53123:1ZuSTQ9l';
3887
- * const cipher = 'enF0vGH@Mj/FRASKL23Q==';
3888
- * newgrounds = new Newgrounds(app_id, cipher);
4220
+ * newgrounds = new Newgrounds(app_id);
3889
4221
  */
3890
4222
  class Newgrounds
3891
4223
  {
3892
4224
  /** Create a newgrounds object
3893
4225
  * @param {Number} app_id - The newgrounds App ID
3894
- * @param {String} [cipher] - The encryption Key (AES-128/Base64) */
3895
- constructor(app_id, cipher)
4226
+ * @param {String} [cipher] - The encryption Key (AES-128/Base64)
4227
+ * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher */
4228
+ constructor(app_id, cipher, cryptoJS)
3896
4229
  {
3897
- ASSERT(!newgrounds && app_id);
4230
+ ASSERT(!newgrounds && app_id); // can only be one newgrounds object
4231
+ ASSERT(!cipher || cryptoJS); // must provide cryptojs if there is a cipher
3898
4232
 
3899
4233
  this.app_id = app_id;
3900
4234
  this.cipher = cipher;
4235
+ this.cryptoJS = cryptoJS;
3901
4236
  this.host = location ? location.hostname : '';
3902
-
3903
- // create an instance of CryptoJS for encrypted calls
3904
- if (cipher)
3905
- this.cryptoJS = this.CryptoJS();
3906
4237
 
3907
4238
  // get session id from url search params
3908
4239
  const url = new URL(location.href);
@@ -4006,38 +4337,6 @@ class Newgrounds
4006
4337
  debugMedals && console.log(xmlHttp.responseText);
4007
4338
  return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
4008
4339
  }
4009
-
4010
- CryptoJS()
4011
- {
4012
- ///////////////////////////////////////////////////////////////////////////////
4013
- // Crypto-JS - https://github.com/brix/crypto-js - MIT License
4014
- //
4015
- // [The MIT License (MIT)](http://opensource.org/licenses/MIT)
4016
- //
4017
- // Copyright (c) 2009-2013 Jeff Mott
4018
- // Copyright (c) 2013-2016 Evan Vosberg
4019
- //
4020
- // Permission is hereby granted, free of charge, to any person obtaining a copy
4021
- // of this software and associated documentation files (the "Software"), to deal
4022
- // in the Software without restriction, including without limitation the rights
4023
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
4024
- // copies of the Software, and to permit persons to whom the Software is
4025
- // furnished to do so, subject to the following conditions:
4026
- //
4027
- // The above copyright notice and this permission notice shall be included in
4028
- // all copies or substantial portions of the Software.
4029
- //
4030
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
4031
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
4032
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
4033
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
4034
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
4035
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
4036
- // THE SOFTWARE.
4037
- 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));
4038
- // end of Crypto-JS
4039
- ///////////////////////////////////////////////////////////////////////////////
4040
- }
4041
4340
  }
4042
4341
  /**
4043
4342
  * LittleJS WebGL Interface
@@ -4047,10 +4346,11 @@ class Newgrounds
4047
4346
  * - Can be disabled with glEnable to revert to 2D canvas rendering
4048
4347
  * - Batches sprite rendering on GPU for incredibly fast performance
4049
4348
  * - Sprite transform math is done in the shader where possible
4349
+ * - Supports shadertoy style post processing shaders
4050
4350
  * @namespace WebGL
4051
4351
  */
4052
4352
 
4053
- 'use strict';
4353
+
4054
4354
 
4055
4355
  /** The WebGL canvas which appears above the main canvas and below the overlay canvas
4056
4356
  * @type {HTMLCanvasElement}
@@ -4062,45 +4362,42 @@ let glCanvas;
4062
4362
  * @memberof WebGL */
4063
4363
  let glContext;
4064
4364
 
4065
- /** Main tile sheet texture automatically loaded by engine
4066
- * @type {WebGLTexture}
4067
- * @memberof WebGL */
4068
- let glTileTexture;
4069
-
4070
4365
  // WebGL internal variables not exposed to documentation
4071
4366
  let glActiveTexture, glShader, glArrayBuffer, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
4072
4367
 
4073
4368
  ///////////////////////////////////////////////////////////////////////////////
4074
4369
 
4075
- // Init WebGL, called automatically by the engine
4370
+ // Initalize WebGL, called automatically by the engine
4076
4371
  function glInit()
4077
4372
  {
4078
- // create the canvas and tile texture
4373
+ // create the canvas and textures
4079
4374
  glCanvas = document.createElement('canvas');
4080
- glContext = glCanvas.getContext('webgl', {antialias: false});
4081
- glTileTexture = glCreateTexture(tileImage);
4375
+ glContext = glCanvas.getContext('webgl2');
4082
4376
 
4083
4377
  // some browsers are much faster without copying the gl buffer so we just overlay it instead
4084
4378
  glOverlay && document.body.appendChild(glCanvas);
4085
4379
 
4086
4380
  // setup vertex and fragment shaders
4087
4381
  glShader = glCreateProgram(
4382
+ '#version 300 es\n' + // specify GLSL ES version
4088
4383
  'precision highp float;'+ // use highp for better accuracy
4089
4384
  'uniform mat4 m;'+ // transform matrix
4090
- 'attribute vec2 p,t;'+ // position, uv
4091
- 'attribute vec4 c,a;'+ // color, additiveColor
4092
- 'varying vec4 v,d,e;'+ // return uv, color, additiveColor
4385
+ 'in vec2 p,t;'+ // position, uv
4386
+ 'in vec4 c,a;'+ // color, additiveColor
4387
+ 'out vec4 v,d,e;'+ // return uv, color, additiveColor
4093
4388
  'void main(){'+ // shader entry point
4094
4389
  'gl_Position=m*vec4(p,1,1);'+ // transform position
4095
4390
  'v=vec4(t,p);d=c;e=a;'+ // pass stuff to fragment shader
4096
4391
  '}' // end of shader
4097
4392
  ,
4098
- 'precision highp float;'+ // use highp for better accuracy
4099
- 'varying vec4 v,d,e;'+ // uv, color, additiveColor
4100
- 'uniform sampler2D s;'+ // texture
4101
- 'void main(){'+ // shader entry point
4102
- 'gl_FragColor=texture2D(s,v.xy)*d+e;'+ // modulate texture by color plus additive
4103
- '}' // end of shader
4393
+ '#version 300 es\n' + // specify GLSL ES version
4394
+ 'precision highp float;'+ // use highp for better accuracy
4395
+ 'in vec4 v,d,e;'+ // uv, color, additiveColor
4396
+ 'uniform sampler2D s;'+ // texture
4397
+ 'out vec4 c;'+ // out color
4398
+ 'void main(){'+ // shader entry point
4399
+ 'c=texture(s,v.xy)*d+e;'+ // modulate texture by color plus additive
4400
+ '}' // end of shader
4104
4401
  );
4105
4402
 
4106
4403
  // init buffers
@@ -4111,25 +4408,68 @@ function glInit()
4111
4408
  glBatchCount = 0;
4112
4409
  }
4113
4410
 
4411
+ // Setup render each frame, called automatically by engine
4412
+ function glPreRender()
4413
+ {
4414
+ // clear and set to same size as main canvas
4415
+ glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
4416
+ glContext.clear(gl_COLOR_BUFFER_BIT);
4417
+
4418
+ // set up the shader
4419
+ glContext.useProgram(glShader);
4420
+ glContext.activeTexture(gl_TEXTURE0);
4421
+ glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
4422
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
4423
+ glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
4424
+ glSetBlendMode();
4425
+
4426
+ // set vertex attributes
4427
+ let offset = 0;
4428
+ const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
4429
+ {
4430
+ const location = glContext.getAttribLocation(glShader, name);
4431
+ glContext.enableVertexAttribArray(location);
4432
+ glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
4433
+ offset += size*typeSize;
4434
+ }
4435
+ initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
4436
+ initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
4437
+ initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
4438
+ initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
4439
+
4440
+ // build the transform matrix
4441
+ const sx = 2 * cameraScale / mainCanvas.width;
4442
+ const sy = 2 * cameraScale / mainCanvas.height;
4443
+ glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
4444
+ new Float32Array([
4445
+ sx, 0, 0, 0,
4446
+ 0, sy, 0, 0,
4447
+ 1, 1, -1, 1,
4448
+ -1-sx*cameraPos.x, -1-sy*cameraPos.y, 0, 0
4449
+ ])
4450
+ );
4451
+ }
4452
+
4114
4453
  /** Set the WebGl blend mode, normally you should call setBlendMode instead
4115
4454
  * @param {Boolean} [additive=0]
4116
4455
  * @memberof WebGL */
4117
- function glSetBlendMode(additive)
4456
+ function glSetBlendMode(additive=0)
4118
4457
  {
4119
4458
  // setup blending
4120
4459
  glAdditive = additive;
4121
4460
  }
4122
4461
 
4123
- /** Set the WebGl texture, not normally necessary unless multiple tile sheets are used
4462
+ /** Set the WebGl texture, called automatically if using multiple textures
4124
4463
  * - This may also flush the gl buffer resulting in more draw calls and worse performance
4125
- * @param {WebGLTexture} [texture=glTileTexture]
4464
+ * @param {WebGLTexture} texture
4126
4465
  * @memberof WebGL */
4127
- function glSetTexture(texture=glTileTexture)
4466
+ function glSetTexture(texture)
4128
4467
  {
4129
4468
  // must flush cache with the old texture to set a new one
4130
- if (texture != glActiveTexture)
4131
- glFlush();
4469
+ if (texture == glActiveTexture)
4470
+ return;
4132
4471
 
4472
+ glFlush();
4133
4473
  glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = texture);
4134
4474
  }
4135
4475
 
@@ -4190,48 +4530,6 @@ function glCreateTexture(image)
4190
4530
  return texture;
4191
4531
  }
4192
4532
 
4193
- // called automatically by engine before render
4194
- function glPreRender()
4195
- {
4196
- // clear and set to same size as main canvas
4197
- glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
4198
- glContext.clear(gl_COLOR_BUFFER_BIT);
4199
-
4200
- // set up the shader
4201
- glContext.useProgram(glShader);
4202
- glContext.activeTexture(gl_TEXTURE0);
4203
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
4204
- glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
4205
- glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
4206
- glSetBlendMode();
4207
-
4208
- // set vertex attributes
4209
- let offset = 0;
4210
- const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
4211
- {
4212
- const location = glContext.getAttribLocation(glShader, name);
4213
- glContext.enableVertexAttribArray(location);
4214
- glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
4215
- offset += size*typeSize;
4216
- }
4217
- initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
4218
- initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
4219
- initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
4220
- initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
4221
-
4222
- // build the transform matrix
4223
- const sx = 2 * cameraScale / mainCanvas.width;
4224
- const sy = 2 * cameraScale / mainCanvas.height;
4225
- glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
4226
- new Float32Array([
4227
- sx, 0, 0, 0,
4228
- 0, sy, 0, 0,
4229
- 1, 1, -1, 1,
4230
- -1-sx*cameraPos.x, -1-sy*cameraPos.y, 0, 0
4231
- ])
4232
- );
4233
- }
4234
-
4235
4533
  /** Draw all sprites and clear out the buffer, called automatically by the system whenever necessary
4236
4534
  * @memberof WebGL */
4237
4535
  function glFlush()
@@ -4350,25 +4648,28 @@ function glInitPostProcess(shaderCode, includeOverlay)
4350
4648
  {
4351
4649
  ASSERT(!glPostShader); // can only have 1 post effects shader
4352
4650
 
4353
- if (!shaderCode) // default shader
4354
- shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture2D(iChannel0,p/iResolution.xy);}';
4651
+ if (!shaderCode) // default shader pass through
4652
+ shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
4355
4653
 
4356
4654
  // create the shader
4357
4655
  glPostShader = glCreateProgram(
4656
+ '#version 300 es\n' + // specify GLSL ES version
4358
4657
  'precision highp float;'+ // use highp for better accuracy
4359
- 'attribute vec2 p;'+ // position
4658
+ 'in vec2 p;'+ // position
4360
4659
  'void main(){'+ // shader entry point
4361
4660
  'gl_Position=vec4(p,1,1);'+ // set position
4362
4661
  '}' // end of shader
4363
4662
  ,
4663
+ '#version 300 es\n' + // specify GLSL ES version
4364
4664
  'precision highp float;'+ // use highp for better accuracy
4365
4665
  'uniform sampler2D iChannel0;'+ // input texture
4366
4666
  'uniform vec3 iResolution;'+ // size of output texture
4367
4667
  'uniform float iTime;'+ // time passed
4668
+ 'out vec4 c;'+ // out color
4368
4669
  '\n' + shaderCode + '\n'+ // insert custom shader code
4369
4670
  'void main(){'+ // shader entry point
4370
- 'mainImage(gl_FragColor,gl_FragCoord.xy);'+ // call post process function
4371
- 'gl_FragColor.a=1.;'+ // always use full alpha
4671
+ 'mainImage(c,gl_FragCoord.xy);'+ // call post process function
4672
+ 'c.a=1.;'+ // always use full alpha
4372
4673
  '}' // end of shader
4373
4674
  );
4374
4675
 
@@ -4441,7 +4742,6 @@ gl_ONE_MINUS_SRC_ALPHA = 771,
4441
4742
  gl_BLEND = 3042,
4442
4743
  gl_TEXTURE_2D = 3553,
4443
4744
  gl_UNSIGNED_BYTE = 5121,
4444
- gl_BYTE = 5120,
4445
4745
  gl_FLOAT = 5126,
4446
4746
  gl_RGBA = 6408,
4447
4747
  gl_NEAREST = 9728,
@@ -4453,7 +4753,6 @@ gl_TEXTURE_WRAP_T = 10243,
4453
4753
  gl_COLOR_BUFFER_BIT = 16384,
4454
4754
  gl_CLAMP_TO_EDGE = 33071,
4455
4755
  gl_TEXTURE0 = 33984,
4456
- gl_TEXTURE1 = 33985,
4457
4756
  gl_ARRAY_BUFFER = 34962,
4458
4757
  gl_STATIC_DRAW = 35044,
4459
4758
  gl_DYNAMIC_DRAW = 35048,
@@ -4464,7 +4763,6 @@ gl_LINK_STATUS = 35714,
4464
4763
  gl_UNPACK_FLIP_Y_WEBGL = 37440,
4465
4764
 
4466
4765
  // constants for batch rendering
4467
- gl_VERTICES_PER_QUAD = 6,
4468
4766
  gl_INDICIES_PER_VERT = 6,
4469
4767
  gl_MAX_BATCH = 1e5,
4470
4768
  gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
@@ -4489,7 +4787,7 @@ gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTEX_BYTE_STRIDE;
4489
4787
  * @namespace Engine
4490
4788
  */
4491
4789
 
4492
- 'use strict';
4790
+
4493
4791
 
4494
4792
  /** Name of engine
4495
4793
  * @type {String}
@@ -4501,7 +4799,7 @@ const engineName = 'LittleJS';
4501
4799
  * @type {String}
4502
4800
  * @default
4503
4801
  * @memberof Engine */
4504
- const engineVersion = '1.7.13';
4802
+ const engineVersion = '1.8.1';
4505
4803
 
4506
4804
  /** Frames per second to update objects
4507
4805
  * @type {Number}
@@ -4551,6 +4849,9 @@ let paused = 0;
4551
4849
  * @memberof Engine */
4552
4850
  function setPaused(_paused) { paused = _paused; }
4553
4851
 
4852
+ // Frame time tracking
4853
+ let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
4854
+
4554
4855
  ///////////////////////////////////////////////////////////////////////////////
4555
4856
 
4556
4857
  /** Start up LittleJS engine with your callback functions
@@ -4559,49 +4860,13 @@ function setPaused(_paused) { paused = _paused; }
4559
4860
  * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
4560
4861
  * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
4561
4862
  * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
4562
- * @param {String} [tileImageSource] - Tile image to use, everything starts when the image is finished loading
4863
+ * @param {String} [imageSources='tiles.png'] - Image to load
4563
4864
  * @memberof Engine */
4564
- function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, tileImageSource)
4865
+ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=['tiles.png'])
4565
4866
  {
4566
- // init engine when tiles load or fail to load
4567
- tileImage.onerror = tileImage.onload = ()=>
4568
- {
4569
- // save tile image info
4570
- tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
4571
- debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
4572
-
4573
- // setup html
4574
- const styleBody = 'margin:0;overflow:hidden;' + // fill the window
4575
- 'background:#000;' + // set background color
4576
- 'touch-action:none;' + // prevent mobile pinch to resize
4577
- 'user-select:none;' + // prevent mobile hold to select
4578
- '-webkit-user-select:none'; // compatibility for ios
4579
- document.body.style = styleBody;
4580
- document.body.appendChild(mainCanvas = document.createElement('canvas'));
4581
- mainContext = mainCanvas.getContext('2d');
4582
-
4583
- // init stuff and start engine
4584
- debugInit();
4585
- glEnable && glInit();
4586
-
4587
- // create overlay canvas for hud to appear above gl canvas
4588
- document.body.appendChild(overlayCanvas = document.createElement('canvas'));
4589
- overlayContext = overlayCanvas.getContext('2d');
4590
-
4591
- // set canvas style
4592
- const styleCanvas = 'position:absolute;' +
4593
- 'top:50%;left:50%;transform:translate(-50%,-50%);' + // center the canvas
4594
- (canvasPixelated?'image-rendering:pixelated':''); // set pixelated rendering
4595
- (glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
4596
-
4597
- gameInit();
4598
- engineUpdate();
4599
- };
4867
+ ASSERT(Array.isArray(imageSources)); // pass in images as array
4600
4868
 
4601
- // frame time tracking
4602
- let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
4603
-
4604
- // main update loop
4869
+ // internal update loop for engine
4605
4870
  function engineUpdate(frameTimeMS=0)
4606
4871
  {
4607
4872
  // update time keeping
@@ -4632,9 +4897,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4632
4897
  }
4633
4898
  else
4634
4899
  {
4635
- // clear canvas and set size to same as window
4636
- mainCanvas.width = min(innerWidth, canvasMaxSize.x);
4637
- mainCanvas.height = min(innerHeight, canvasMaxSize.y);
4900
+ // clear canvas and set size to same as window
4901
+ mainCanvas.width = min(innerWidth, canvasMaxSize.x);
4902
+ mainCanvas.height = min(innerHeight, canvasMaxSize.y);
4638
4903
  }
4639
4904
 
4640
4905
  // clear overlay canvas and set size
@@ -4666,6 +4931,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4666
4931
  // update multiple frames if necessary in case of slow framerate
4667
4932
  for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / frameRate)
4668
4933
  {
4934
+ // increment frame and update time
4935
+ time = frame++ / frameRate;
4936
+
4669
4937
  // update game and objects
4670
4938
  inputUpdate();
4671
4939
  gameUpdate();
@@ -4713,8 +4981,51 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4713
4981
  requestAnimationFrame(engineUpdate);
4714
4982
  }
4715
4983
 
4716
- // set tile image source to load the image and start the engine
4717
- tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
4984
+ // setup html
4985
+ const styleBody =
4986
+ 'margin:0;overflow:hidden;' + // fill the window
4987
+ 'background:#000;' + // set background color
4988
+ 'touch-action:none;' + // prevent mobile pinch to resize
4989
+ 'user-select:none;' + // prevent mobile hold to select
4990
+ '-webkit-user-select:none;' + // compatibility for ios
4991
+ '-webkit-touch-callout:none'; // compatibility for ios
4992
+ document.body.style = styleBody;
4993
+ document.body.appendChild(mainCanvas = document.createElement('canvas'));
4994
+ mainContext = mainCanvas.getContext('2d');
4995
+
4996
+ // init stuff and start engine
4997
+ debugInit();
4998
+ glEnable && glInit();
4999
+
5000
+ // create overlay canvas for hud to appear above gl canvas
5001
+ document.body.appendChild(overlayCanvas = document.createElement('canvas'));
5002
+ overlayContext = overlayCanvas.getContext('2d');
5003
+
5004
+ // set canvas style
5005
+ const styleCanvas =
5006
+ 'position:absolute;' + // position
5007
+ 'top:50%;left:50%;transform:translate(-50%,-50%);' + // center
5008
+ (canvasPixelated?'image-rendering:pixelated':''); // pixelated rendering
5009
+ (glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
5010
+
5011
+ // load all of the images
5012
+ Promise.all(imageSources.map((src, textureIndex)=>
5013
+ new Promise((resolve, reject)=>
5014
+ {
5015
+ const image = new Image;
5016
+ image.onerror = image.onload = ()=>
5017
+ {
5018
+ textureInfos[textureIndex] = new TextureInfo(image);
5019
+ resolve();
5020
+ }
5021
+ image.src = src;
5022
+ })
5023
+ )).then(()=>
5024
+ {
5025
+ // start the engine
5026
+ gameInit();
5027
+ engineUpdate();
5028
+ });
4718
5029
  }
4719
5030
 
4720
5031
  // Called automatically by engine to setup render system
@@ -4752,9 +5063,6 @@ function engineObjectsUpdate()
4752
5063
 
4753
5064
  // remove destroyed objects
4754
5065
  engineObjects = engineObjects.filter(o=>!o.destroyed);
4755
-
4756
- // increment frame and update time
4757
- time = ++frame / frameRate;
4758
5066
  }
4759
5067
 
4760
5068
  /** Destroy and remove all objects
@@ -4797,196 +5105,6 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
4797
5105
  * - Export engine as a module with functions where necessary
4798
5106
  */
4799
5107
 
4800
- /** Set position of camera in world space
4801
- * @param {Vector2} pos
4802
- * @memberof Settings */
4803
- function setCameraPos(pos) { cameraPos = pos; }
4804
-
4805
- /** Set scale of camera in world space
4806
- * @param {Number} scale
4807
- * @memberof Settings */
4808
- function setCameraScale(scale) { cameraScale = scale; }
4809
-
4810
- /** Set max size of the canvas
4811
- * @param {Vector2} size
4812
- * @memberof Settings */
4813
- function setCanvasMaxSize(size) { canvasMaxSize = size; }
4814
-
4815
- /** Set fixed size of the canvas
4816
- * @param {Vector2} size
4817
- * @memberof Settings */
4818
- function setCanvasFixedSize(size) { canvasFixedSize = size; }
4819
-
4820
- /** Disables anti aliasing for pixel art if true
4821
- * @param {Boolean} pixelated
4822
- * @memberof Settings */
4823
- function setCanvasPixelated(pixelated) { canvasPixelated = pixelated; }
4824
-
4825
- /** Set default font used for text rendering
4826
- * @param {String} font
4827
- * @memberof Settings */
4828
- function setFontDefault(font) { fontDefault = font; }
4829
-
4830
- /** Set if webgl rendering is enabled
4831
- * @param {Boolean} enable
4832
- * @memberof Settings */
4833
- function setGlEnable(enable) { glEnable = enable; }
4834
-
4835
- /** Set to not composite the WebGL canvas
4836
- * @param {Boolean} overlay
4837
- * @memberof Settings */
4838
- function setGlOverlay(overlay) { glOverlay = overlay; }
4839
-
4840
- /** Set default size of tiles in pixels
4841
- * @param {Vector2} size
4842
- * @memberof Settings */
4843
- function setTileSizeDefault(size) { tileSizeDefault = size; }
4844
-
4845
- /** Set to prevent tile bleeding from neighbors in pixels
4846
- * @param {Number} scale
4847
- * @memberof Settings */
4848
- function setTileFixBleedScale(scale) { tileFixBleedScale = scale; }
4849
-
4850
- /** Set if collisions between objects are enabled
4851
- * @param {Boolean} enable
4852
- * @memberof Settings */
4853
- function setEnablePhysicsSolver(enable) { enablePhysicsSolver = enable; }
4854
-
4855
- /** Set default object mass for collison calcuations
4856
- * @param {Number} mass
4857
- * @memberof Settings */
4858
- function setObjectDefaultMass(mass) { objectDefaultMass = mass; }
4859
-
4860
- /** Set how much to slow velocity by each frame
4861
- * @param {Number} damping
4862
- * @memberof Settings */
4863
- function setObjectDefaultDamping(damp) { objectDefaultDamping = damp; }
4864
-
4865
- /** Set how much to slow angular velocity each frame
4866
- * @param {Number} damping
4867
- * @memberof Settings */
4868
- function setObjectDefaultAngleDamping(damp) { objectDefaultAngleDamping = damp; }
4869
-
4870
- /** Set how much to bounce when a collision occur
4871
- * @param {Number} elasticity
4872
- * @memberof Settings */
4873
- function setObjectDefaultElasticity(elasticity) { objectDefaultElasticity = elasticity; }
4874
-
4875
- /** Set how much to slow when touching
4876
- * @param {Number} friction
4877
- * @memberof Settings */
4878
- function setObjectDefaultFriction(friction) { objectDefaultFriction = friction; }
4879
-
4880
- /** Set max speed to avoid fast objects missing collisions
4881
- * @param {Number} speed
4882
- * @memberof Settings */
4883
- function setObjectMaxSpeed(speed) { objectMaxSpeed = speed; }
4884
-
4885
- /** Set how much gravity to apply to objects along the Y axis
4886
- * @param {Number} gravity
4887
- * @memberof Settings */
4888
- function setGravity(g) { gravity = g; }
4889
-
4890
- /** Set to scales emit rate of particles
4891
- * @param {Number} scale
4892
- * @memberof Settings */
4893
- function setParticleEmitRateScale(scale) { particleEmitRateScale = scale; }
4894
-
4895
- /** Set if gamepads are enabled
4896
- * @param {Boolean} enable
4897
- * @memberof Settings */
4898
- function setGamepadsEnable(enable) { gamepadsEnable = enable; }
4899
-
4900
- /** Set if the dpad input is also routed to the left analog stick
4901
- * @param {Boolean} enable
4902
- * @memberof Settings */
4903
- function setGamepadDirectionEmulateStick(enable) { gamepadDirectionEmulateStick = enable; }
4904
-
4905
- /** Set if true the WASD keys are also routed to the direction keys
4906
- * @param {Boolean} enable
4907
- * @memberof Settings */
4908
- function setInputWASDEmulateDirection(enable) { inputWASDEmulateDirection = enable; }
4909
-
4910
- /** Set if touch gamepad should appear on mobile devices
4911
- * @param {Boolean} enable
4912
- * @memberof Settings */
4913
- function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
4914
-
4915
- /** Set if touch gamepad should be analog stick or 8 way dpad
4916
- * @param {Boolean} analog
4917
- * @memberof Settings */
4918
- function setTouchGamepadAnalog(analog) { touchGamepadAnalog = analog; }
4919
-
4920
- /** Set size of virutal gamepad for touch devices in pixels
4921
- * @param {Number} size
4922
- * @memberof Settings */
4923
- function setTouchGamepadSize(size) { touchGamepadSize = size; }
4924
-
4925
- /** Set transparency of touch gamepad overlay
4926
- * @param {Number} alpha
4927
- * @memberof Settings */
4928
- function setTouchGamepadAlpha(alpha) { touchGamepadAlpha = alpha; }
4929
-
4930
- /** Set to allow vibration hardware if it exists
4931
- * @param {Boolean} enable
4932
- * @memberof Settings */
4933
- function setVibrateEnable(enable) { vibrateEnable = enable; }
4934
-
4935
- /** Set to disable all audio code
4936
- * @param {Boolean} enable
4937
- * @memberof Settings */
4938
- function setSoundEnable(enable) { soundEnable = enable; }
4939
-
4940
- /** Set volume scale to apply to all sound, music and speech
4941
- * @param {Number} volume
4942
- * @memberof Settings */
4943
- function setSoundVolume(volume) { soundVolume = volume; }
4944
-
4945
- /** Set default range where sound no longer plays
4946
- * @param {Number} range
4947
- * @memberof Settings */
4948
- function setSoundDefaultRange(range) { soundDefaultRange = range; }
4949
-
4950
- /** Set default range percent to start tapering off sound
4951
- * @param {Number} taper
4952
- * @memberof Settings */
4953
- function setSoundDefaultTaper(taper) { soundDefaultTaper = taper; }
4954
-
4955
- /** Set how long to show medals for in seconds
4956
- * @param {Number} time
4957
- * @memberof Settings */
4958
- function setMedalDisplayTime(time) { medalDisplayTime = time; }
4959
-
4960
- /** Set how quickly to slide on/off medals in seconds
4961
- * @param {Number} time
4962
- * @memberof Settings */
4963
- function setMedalDisplaySlideTime(time) { medalDisplaySlideTime = time; }
4964
-
4965
- /** Set size of medal display
4966
- * @param {Vector2} size
4967
- * @memberof Settings */
4968
- function setMedalDisplaySize(size) { medalDisplaySize = size; }
4969
-
4970
- /** Set size of icon in medal display
4971
- * @param {Number} size
4972
- * @memberof Settings */
4973
- function setMedalDisplayIconSize(size) { medalDisplayIconSize = size; }
4974
-
4975
- /** Set to stop medals from being unlockable
4976
- * @param {Boolean} preventUnlock
4977
- * @memberof Settings */
4978
- function setMedalsPreventUnlock(preventUnlock) { medalsPreventUnlock = preventUnlock; }
4979
-
4980
- /** Set if watermark with FPS should be shown
4981
- * @param {Boolean} show
4982
- * @memberof Debug */
4983
- function setShowWatermark(show) { showWatermark = show; }
4984
-
4985
- /** Set key code used to toggle debug mode, Esc by default
4986
- * @param {Number} key
4987
- * @memberof Debug */
4988
- function setDebugKey(key) { debugKey = key; }
4989
-
4990
5108
  export {
4991
5109
  // Setters for global variables
4992
5110
  setCameraPos,
@@ -5121,7 +5239,10 @@ export {
5121
5239
  EngineObject,
5122
5240
 
5123
5241
  // Draw
5124
- tileImage,
5242
+ textureInfos,
5243
+ tile,
5244
+ TileInfo,
5245
+ TextureInfo,
5125
5246
  mainCanvas,
5126
5247
  mainContext,
5127
5248
  overlayCanvas,