littlejsengine 1.11.17 → 1.12.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/box2d.wasm.js +630 -0
  2. package/dist/box2d.wasm.wasm +0 -0
  3. package/dist/littlejs.d.ts +158 -81
  4. package/dist/littlejs.esm.js +882 -793
  5. package/dist/littlejs.esm.min.js +1 -1
  6. package/dist/littlejs.js +3048 -175
  7. package/dist/littlejs.min.js +1 -1
  8. package/dist/littlejs.release.js +3047 -172
  9. package/examples/box2d/game.js +82 -56
  10. package/examples/box2d/gameObjects.js +114 -107
  11. package/examples/box2d/index.html +2 -7
  12. package/examples/box2d/scenes.js +72 -78
  13. package/examples/box2d/tiles.png +0 -0
  14. package/examples/breakout/game.js +63 -28
  15. package/examples/breakout/gameObjects.js +45 -47
  16. package/examples/breakout/index.html +1 -5
  17. package/examples/breakoutTutorial/game.js +36 -29
  18. package/examples/breakoutTutorial/index.html +1 -3
  19. package/examples/empty/game.js +5 -2
  20. package/examples/empty/index.html +1 -3
  21. package/examples/htmlMenu/game.js +24 -15
  22. package/examples/htmlMenu/index.html +5 -7
  23. package/examples/index.html +24 -17
  24. package/examples/module/game.js +32 -34
  25. package/examples/module/index.html +1 -1
  26. package/examples/particles/index.html +27 -22
  27. package/examples/platformer/game.js +62 -45
  28. package/examples/platformer/gameCharacter.js +42 -34
  29. package/examples/platformer/gameEffects.js +82 -75
  30. package/examples/platformer/gameLevel.js +67 -38
  31. package/examples/platformer/{gameLevelData.js → gameLevelData.json} +1 -11
  32. package/examples/platformer/gameObjects.js +70 -64
  33. package/examples/platformer/gamePlayer.js +12 -7
  34. package/examples/platformer/index.html +1 -9
  35. package/examples/puzzle/game.js +59 -40
  36. package/examples/puzzle/index.html +1 -3
  37. package/examples/shorts/base.html +3 -2
  38. package/examples/shorts/box2d.js +46 -0
  39. package/examples/shorts/box2dCar.js +49 -0
  40. package/examples/shorts/particles.js +1 -1
  41. package/examples/shorts/platformer.js +1 -1
  42. package/examples/shorts/postProcess.js +45 -0
  43. package/examples/shorts/tileLayer.js +5 -6
  44. package/examples/shorts/tiles.png +0 -0
  45. package/examples/shorts/uiSystem.js +39 -0
  46. package/examples/starter/game.js +9 -10
  47. package/examples/starter/index.html +13 -13
  48. package/examples/stress/index.html +44 -44
  49. package/examples/typescript/build.js +17 -18
  50. package/examples/typescript/game.js +32 -34
  51. package/examples/typescript/game.ts +32 -34
  52. package/examples/typescript/index.html +1 -1
  53. package/examples/uiSystem/game.js +34 -31
  54. package/examples/uiSystem/index.html +1 -5
  55. package/package.json +1 -1
  56. package/plugins/box2d.js +28 -30
  57. package/plugins/pluginExport.js +2 -3
  58. package/plugins/uiSystem.js +9 -3
  59. package/reference.md +29 -27
  60. package/src/engine.js +10 -11
  61. package/src/engineAudio.js +24 -15
  62. package/src/engineBuild.js +25 -8
  63. package/src/engineDebug.js +1 -3
  64. package/src/engineExport.js +3 -6
  65. package/src/engineInput.js +6 -4
  66. package/src/engineObject.js +18 -14
  67. package/src/engineSettings.js +6 -6
  68. package/src/engineTileLayer.js +179 -96
  69. package/src/engineUtilities.js +16 -0
  70. package/src/engineWebGL.js +13 -1
  71. package/examples/typescript/build/dist/littlejs.esm.js +0 -4834
  72. package/examples/typescript/build/examples/typescript/build.js +0 -24
  73. package/examples/typescript/build/examples/typescript/game.js +0 -102
@@ -13,8 +13,6 @@
13
13
  * @namespace Debug
14
14
  */
15
15
 
16
-
17
-
18
16
  /** True if debug is enabled
19
17
  * @type {boolean}
20
18
  * @default
@@ -334,11 +332,9 @@ function debugRender()
334
332
  }
335
333
  }
336
334
 
337
- if (tileCollisionSize.x > 0 && tileCollisionSize.y > 0)
335
+ if (tileCollisionLayers.length) // show floored tile pick if there is tile collision
338
336
  drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(0,0,1,.5), 0, false);
339
337
  mainContext = saveContext;
340
-
341
- //glCopyToContext(mainContext = saveContext);
342
338
  }
343
339
 
344
340
  {
@@ -581,8 +577,6 @@ function debugVideoCaptureUpdate()
581
577
  * @namespace Utilities
582
578
  */
583
579
 
584
-
585
-
586
580
  /** A shortcut to get Math.PI
587
581
  * @type {number}
588
582
  * @default Math.PI
@@ -762,6 +756,16 @@ function wave(frequency=1, amplitude=1, t=time)
762
756
  * @memberof Utilities */
763
757
  function formatTime(t) { return (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0); }
764
758
 
759
+ /** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
760
+ * @param {string} url - URL of JSON file
761
+ * @return {Promise<object>}
762
+ * @memberof Utilities */
763
+ async function fetchJSON(url)
764
+ {
765
+ const response = await fetch(url);
766
+ return response.json();
767
+ }
768
+
765
769
  ///////////////////////////////////////////////////////////////////////////////
766
770
 
767
771
  /** Random global functions
@@ -857,6 +861,12 @@ class RandomGenerator
857
861
  /** Randomly returns either -1 or 1 deterministically
858
862
  * @return {number} */
859
863
  sign() { return this.float() > .5 ? 1 : -1; }
864
+
865
+ /** Returns a seeded random value between the two values passed in with a random sign
866
+ * @param {number} [valueA]
867
+ * @param {number} [valueB]
868
+ * @return {number} */
869
+ floatSign(valueA=1, valueB=0) { return this.float(valueA, valueB) * this.sign(); }
860
870
  }
861
871
 
862
872
  ///////////////////////////////////////////////////////////////////////////////
@@ -1497,8 +1507,6 @@ class Timer
1497
1507
  * @namespace Settings
1498
1508
  */
1499
1509
 
1500
-
1501
-
1502
1510
  ///////////////////////////////////////////////////////////////////////////////
1503
1511
  // Camera settings
1504
1512
 
@@ -1530,7 +1538,7 @@ let canvasMaxSize = vec2(1920, 1080);
1530
1538
  * @memberof Settings */
1531
1539
  let canvasFixedSize = vec2();
1532
1540
 
1533
- /** Use nearest neighbor scaling algorithm for canvas for more pixelated look
1541
+ /** Use nearest neighbor canvas scaling for more pixelated look
1534
1542
  * - Must be set before startup to take effect
1535
1543
  * - If enabled sets css image-rendering:pixelated
1536
1544
  * @type {boolean}
@@ -1640,11 +1648,11 @@ let objectDefaultFriction = .8;
1640
1648
  * @memberof Settings */
1641
1649
  let objectMaxSpeed = 1;
1642
1650
 
1643
- /** How much gravity to apply to objects along the Y axis, negative is down
1644
- * @type {number}
1651
+ /** How much gravity to apply to objects, negative Y is down
1652
+ * @type {Vector2}
1645
1653
  * @default
1646
1654
  * @memberof Settings */
1647
- let gravity = 0;
1655
+ let gravity = vec2();
1648
1656
 
1649
1657
  /** Scales emit rate of particles, useful for low graphics mode (0 disables particle emitters)
1650
1658
  * @type {number}
@@ -1870,8 +1878,8 @@ function setObjectDefaultFriction(friction) { objectDefaultFriction = friction;
1870
1878
  * @memberof Settings */
1871
1879
  function setObjectMaxSpeed(speed) { objectMaxSpeed = speed; }
1872
1880
 
1873
- /** Set how much gravity to apply to objects along the Y axis
1874
- * @param {number} newGravity
1881
+ /** Set how much gravity to apply to objects
1882
+ * @param {Vector2} newGravity
1875
1883
  * @memberof Settings */
1876
1884
  function setGravity(newGravity) { gravity = newGravity; }
1877
1885
 
@@ -1983,8 +1991,6 @@ function setDebugKey(key) { debugKey = key; }
1983
1991
  * LittleJS Object System
1984
1992
  */
1985
1993
 
1986
-
1987
-
1988
1994
  /**
1989
1995
  * LittleJS Object Base Object Class
1990
1996
  * - Top level object class used by the engine
@@ -2139,7 +2145,10 @@ class EngineObject
2139
2145
  this.velocity.x *= this.damping;
2140
2146
  this.velocity.y *= this.damping;
2141
2147
  if (this.mass) // don't apply gravity to static objects
2142
- this.velocity.y += gravity * this.gravityScale;
2148
+ {
2149
+ this.velocity.x += gravity.x * this.gravityScale;
2150
+ this.velocity.y += gravity.y * this.gravityScale;
2151
+ }
2143
2152
  this.pos.x += this.velocity.x;
2144
2153
  this.pos.y += this.velocity.y;
2145
2154
  this.angle += this.angleVelocity *= this.angleDamping;
@@ -2154,9 +2163,9 @@ class EngineObject
2154
2163
  if (this.groundObject)
2155
2164
  {
2156
2165
  // apply friction in local space of ground object
2157
- const groundSpeed = this.groundObject != this && this.groundObject.velocity ?
2158
- this.groundObject.velocity.x : 0;
2159
- this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * this.friction;
2166
+ const friction = max(this.friction, this.groundObject.friction);
2167
+ const groundSpeed = this.groundObject.velocity ? this.groundObject.velocity.x : 0;
2168
+ this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * friction;
2160
2169
  this.groundObject = undefined;
2161
2170
  //debugOverlay && debugPhysics && debugPoint(this.pos.subtract(vec2(0,this.size.y/2)), '#0f0');
2162
2171
  }
@@ -2198,7 +2207,7 @@ class EngineObject
2198
2207
 
2199
2208
  // check for collision
2200
2209
  const sizeBoth = this.size.add(o.size);
2201
- const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity; // prefer to push up if small delta
2210
+ const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity.y; // prefer to push up if small delta
2202
2211
  const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
2203
2212
  const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
2204
2213
  const elasticity = max(this.elasticity, o.elasticity);
@@ -2260,19 +2269,21 @@ class EngineObject
2260
2269
  if (this.collideTiles)
2261
2270
  {
2262
2271
  // check collision against tiles
2263
- if (tileCollisionTest(this.pos, this.size, this))
2272
+ const hitLayer = tileCollisionTest(this.pos, this.size, this)
2273
+ if (hitLayer)
2264
2274
  {
2265
2275
  // if already was stuck in collision, don't do anything
2266
2276
  // this should not happen unless something starts in collision
2267
2277
  if (!tileCollisionTest(oldPos, this.size, this))
2268
2278
  {
2269
2279
  // test which side we bounced off (or both if a corner)
2270
- const isBlockedY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
2271
- const isBlockedX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
2272
- if (isBlockedY || !isBlockedX)
2280
+ const blockedLayerY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
2281
+ const blockedLayerX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
2282
+ if (blockedLayerY || !blockedLayerX)
2273
2283
  {
2274
2284
  // bounce velocity
2275
- this.velocity.y *= -this.elasticity;
2285
+ const elasticity = max(this.elasticity, hitLayer.elasticity);
2286
+ this.velocity.y *= -elasticity;
2276
2287
 
2277
2288
  if (wasMovingDown)
2278
2289
  {
@@ -2281,9 +2292,8 @@ class EngineObject
2281
2292
  const epsilon = .0001;
2282
2293
  this.pos.y = (oldPos.y-this.size.y/2|0)+this.size.y/2+epsilon;
2283
2294
 
2284
- // set ground object to self for tile collision
2285
- // TODO: rework system so tile collision is its own object
2286
- this.groundObject = this;
2295
+ // set ground object for tile collision
2296
+ this.groundObject = hitLayer;
2287
2297
  }
2288
2298
  else
2289
2299
  {
@@ -2292,7 +2302,7 @@ class EngineObject
2292
2302
  this.groundObject = undefined;
2293
2303
  }
2294
2304
  }
2295
- if (isBlockedX)
2305
+ if (blockedLayerX)
2296
2306
  {
2297
2307
  // move to previous position and bounce
2298
2308
  this.pos.x = oldPos.x;
@@ -2463,8 +2473,6 @@ class EngineObject
2463
2473
  * @namespace Draw
2464
2474
  */
2465
2475
 
2466
-
2467
-
2468
2476
  /** The primary 2D canvas visible to the user
2469
2477
  * @type {HTMLCanvasElement}
2470
2478
  * @memberof Draw */
@@ -3086,8 +3094,6 @@ function setCursor(cursorStyle = 'auto')
3086
3094
  * @namespace Input
3087
3095
  */
3088
3096
 
3089
-
3090
-
3091
3097
  /** Returns true if device key is down
3092
3098
  * @param {string|number} key
3093
3099
  * @param {number} [device]
@@ -3139,21 +3145,21 @@ function clearInput() { inputData = [[]]; touchGamepadButtons = []; }
3139
3145
  * @param {number} button
3140
3146
  * @return {boolean}
3141
3147
  * @memberof Input */
3142
- const mouseIsDown = keyIsDown;
3148
+ function mouseIsDown(button) { return keyIsDown(button); }
3143
3149
 
3144
3150
  /** Returns true if mouse button was pressed
3145
3151
  * @function
3146
3152
  * @param {number} button
3147
3153
  * @return {boolean}
3148
3154
  * @memberof Input */
3149
- const mouseWasPressed = keyWasPressed;
3155
+ function mouseWasPressed(button) { return keyWasPressed(button); }
3150
3156
 
3151
3157
  /** Returns true if mouse button was released
3152
3158
  * @function
3153
3159
  * @param {number} button
3154
3160
  * @return {boolean}
3155
3161
  * @memberof Input */
3156
- const mouseWasReleased = keyWasReleased;
3162
+ function mouseWasReleased(button) { return keyWasReleased(button); }
3157
3163
 
3158
3164
  /** Mouse pos in world space
3159
3165
  * @type {Vector2}
@@ -3392,7 +3398,9 @@ function gamepadsUpdate()
3392
3398
  const button = gamepad.buttons[j];
3393
3399
  const wasDown = gamepadIsDown(j,i);
3394
3400
  data[j] = button.pressed ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
3395
- isUsingGamepad ||= !i && button.pressed;
3401
+ if (!button.value || button.value > .9) // must be a full press
3402
+ if (!i && button.pressed)
3403
+ isUsingGamepad = true;
3396
3404
  }
3397
3405
 
3398
3406
  if (gamepadDirectionEmulateStick)
@@ -3610,8 +3618,6 @@ function touchGamepadRender()
3610
3618
  * @namespace Audio
3611
3619
  */
3612
3620
 
3613
-
3614
-
3615
3621
  /** Audio context used by the engine
3616
3622
  * @type {AudioContext}
3617
3623
  * @memberof Audio */
@@ -3648,17 +3654,17 @@ class Sound
3648
3654
  {
3649
3655
  /** Create a sound object and cache the zzfx samples for later use
3650
3656
  * @param {Array} zzfxSound - Array of zzfx parameters, ex. [.5,.5]
3651
- * @param {number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
3657
+ * @param {number} [range=soundDefaultRange] - World space max range of sound
3652
3658
  * @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
3653
3659
  */
3654
3660
  constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
3655
3661
  {
3656
3662
  if (!soundEnable || headlessMode) return;
3657
3663
 
3658
- /** @property {number} - World space max range of sound, will not play if camera is farther away */
3664
+ /** @property {number} - World space max range of sound */
3659
3665
  this.range = range;
3660
3666
 
3661
- /** @property {number} - At what percentage of range should it start tapering off */
3667
+ /** @property {number} - At what percentage of range should it start tapering */
3662
3668
  this.taper = taper;
3663
3669
 
3664
3670
  /** @property {number} - How much to randomize frequency each time sound plays */
@@ -3774,8 +3780,8 @@ class SoundWave extends Sound
3774
3780
  /** Create a sound object and cache the wave file for later use
3775
3781
  * @param {string} filename - Filename of audio file to load
3776
3782
  * @param {number} [randomness] - How much to randomize frequency each time sound plays
3777
- * @param {number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
3778
- * @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
3783
+ * @param {number} [range=soundDefaultRange] - World space max range of sound
3784
+ * @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
3779
3785
  * @param {Function} [onloadCallback] - callback function to call when sound is loaded
3780
3786
  */
3781
3787
  constructor(filename, randomness=0, range, taper, onloadCallback)
@@ -3783,17 +3789,26 @@ class SoundWave extends Sound
3783
3789
  super(undefined, range, taper);
3784
3790
  if (!soundEnable || headlessMode) return;
3785
3791
 
3792
+ /** @property {Function} - callback function to call when sound is loaded */
3793
+ this.onloadCallback = onloadCallback;
3786
3794
  this.randomness = randomness;
3787
- fetch(filename)
3788
- .then(response => response.arrayBuffer())
3789
- .then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))
3790
- .then(audioBuffer =>
3791
- {
3792
- this.sampleChannels = [];
3793
- for (let i = audioBuffer.numberOfChannels; i--;)
3794
- this.sampleChannels[i] = Array.from(audioBuffer.getChannelData(i));
3795
- this.sampleRate = audioBuffer.sampleRate;
3796
- }).then(() => onloadCallback && onloadCallback(this));
3795
+ this.loadSound(filename);
3796
+ }
3797
+
3798
+ /** Loads a sound from a URL and decodes it into sample data. Must be used with await!
3799
+ * @param {string} filename
3800
+ * @return {Promise<void>} */
3801
+ async loadSound(filename)
3802
+ {
3803
+ const response = await fetch(filename);
3804
+ const arrayBuffer = await response.arrayBuffer();
3805
+ const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
3806
+ this.sampleChannels = [];
3807
+ for (let i = audioBuffer.numberOfChannels; i--;)
3808
+ this.sampleChannels[i] = Array.from(audioBuffer.getChannelData(i));
3809
+ this.sampleRate = audioBuffer.sampleRate;
3810
+ if (this.onloadCallback)
3811
+ this.onloadCallback();
3797
3812
  }
3798
3813
  }
3799
3814
 
@@ -4067,75 +4082,45 @@ function zzfxG
4067
4082
  * LittleJS Tile Layer System
4068
4083
  * - Caches arrays of tiles to off screen canvas for fast rendering
4069
4084
  * - Unlimited numbers of layers, allocates canvases as needed
4070
- * - Interfaces with EngineObject for collision
4071
- * - Collision layer is separate from visible layers
4072
- * - It is recommended to have a visible layer that matches the collision
4073
4085
  * - Tile layers can be drawn to using their context with canvas2d
4074
4086
  * - Drawn directly to the main canvas without using WebGL
4087
+ * - Tile layers can also have collision with EngineObjects
4075
4088
  * @namespace TileCollision
4076
4089
  */
4077
4090
 
4091
+ ///////////////////////////////////////////////////////////////////////////////
4092
+ // Tile Layer System
4078
4093
 
4079
-
4080
- /** The tile collision layer grid, use setTileCollisionData and getTileCollisionData to access
4081
- * @type {Array<number>}
4082
- * @memberof TileCollision */
4083
- let tileCollision = [];
4084
-
4085
- /** Size of the tile collision layer 2d grid
4086
- * @type {Vector2}
4087
- * @memberof TileCollision */
4088
- let tileCollisionSize = vec2();
4089
-
4090
- /** Clear and initialize tile collision
4091
- * @param {Vector2} size - width and height of tile collision 2d grid
4092
- * @memberof TileCollision */
4093
- function initTileCollision(size)
4094
- {
4095
- tileCollisionSize = size;
4096
- tileCollision = [];
4097
- for (let i=tileCollision.length = tileCollisionSize.area(); i--;)
4098
- tileCollision[i] = 0;
4099
- }
4100
-
4101
- /** Set tile collision data for a given cell in the grid
4102
- * @param {Vector2} pos
4103
- * @param {number} [data]
4094
+ /** Keep track of all tile layers with collision
4095
+ * @type {Array<TileCollisionLayer>}
4104
4096
  * @memberof TileCollision */
4105
- function setTileCollisionData(pos, data=0)
4106
- {
4107
- pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
4108
- }
4097
+ let tileCollisionLayers = [];
4109
4098
 
4110
4099
  /** Get tile collision data for a given cell in the grid
4111
- * @param {Vector2} pos
4112
- * @return {number}
4113
- * @memberof TileCollision */
4100
+ * @param {Vector2} pos
4101
+ * @return {number}
4102
+ * @memberof TileCollision */
4114
4103
  function getTileCollisionData(pos)
4115
4104
  {
4116
- return pos.arrayCheck(tileCollisionSize) ? tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] : 0;
4105
+ // check all tile collision layers
4106
+ for (const layer of tileCollisionLayers)
4107
+ if (pos.arrayCheck(layer.size))
4108
+ return layer.getCollisionData(pos);
4109
+ return 0;
4117
4110
  }
4118
4111
 
4119
- /** Check if collision with another object should occur
4112
+ /** Check if a tile layer collides with another object
4120
4113
  * @param {Vector2} pos
4121
4114
  * @param {Vector2} [size=(0,0)]
4122
4115
  * @param {EngineObject} [object]
4123
- * @return {boolean}
4116
+ * @return {TileCollisionLayer}
4124
4117
  * @memberof TileCollision */
4125
4118
  function tileCollisionTest(pos, size=vec2(), object)
4126
4119
  {
4127
- const minX = max(pos.x - size.x/2|0, 0);
4128
- const minY = max(pos.y - size.y/2|0, 0);
4129
- const maxX = min(pos.x + size.x/2, tileCollisionSize.x);
4130
- const maxY = min(pos.y + size.y/2, tileCollisionSize.y);
4131
- for (let y = minY; y < maxY; ++y)
4132
- for (let x = minX; x < maxX; ++x)
4133
- {
4134
- const tileData = tileCollision[y*tileCollisionSize.x+x];
4135
- if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
4136
- return true;
4137
- }
4138
- return false;
4120
+ // check all tile collision layers
4121
+ for (const layer of tileCollisionLayers)
4122
+ if (layer.collisionTest(pos, size, object))
4123
+ return layer;
4139
4124
  }
4140
4125
 
4141
4126
  /** Return the center of first tile hit, undefined if nothing was hit.
@@ -4147,49 +4132,17 @@ function tileCollisionTest(pos, size=vec2(), object)
4147
4132
  * @memberof TileCollision */
4148
4133
  function tileCollisionRaycast(posStart, posEnd, object)
4149
4134
  {
4150
- // test if a ray collides with tiles from start to end
4151
- // todo: a way to get the exact hit point, it must still be inside the hit tile
4152
- const delta = posEnd.subtract(posStart);
4153
- const totalLength = delta.length();
4154
- const normalizedDelta = delta.normalize();
4155
- const unit = vec2(abs(1/normalizedDelta.x), abs(1/normalizedDelta.y));
4156
- const flooredPosStart = posStart.floor();
4157
-
4158
- // setup iteration variables
4159
- let pos = flooredPosStart;
4160
- let xi = unit.x * (delta.x < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1);
4161
- let yi = unit.y * (delta.y < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1);
4162
-
4163
- while (true)
4164
- {
4165
- // check for tile collision
4166
- const tileData = getTileCollisionData(pos);
4167
- if (tileData && (!object || object.collideWithTile(tileData, pos)))
4168
- {
4169
- debugRaycast && debugLine(posStart, posEnd, '#f00', .02);
4170
- debugRaycast && debugPoint(pos.add(vec2(.5)), '#ff0');
4171
- return pos.add(vec2(.5));
4172
- }
4173
-
4174
- // check if past the end
4175
- if (xi > totalLength && yi > totalLength)
4176
- break;
4177
-
4178
- // get coordinates of the next tile to check
4179
- if (xi > yi)
4180
- pos.y += sign(delta.y), yi += unit.y;
4181
- else
4182
- pos.x += sign(delta.x), xi += unit.x;
4135
+ // check all tile collision layers
4136
+ for (const layer of tileCollisionLayers)
4137
+ {
4138
+ const hitPos = layer.collisionRaycast(posStart, posEnd, object)
4139
+ if (hitPos)
4140
+ return hitPos;
4183
4141
  }
4184
-
4185
- debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
4186
4142
  }
4187
4143
 
4188
- ///////////////////////////////////////////////////////////////////////////////
4189
- // Tile Layer Rendering System
4190
-
4191
4144
  /**
4192
- * Tile layer data object stores info about how to render a tile
4145
+ * Tile layer data object stores info about how to draw a tile
4193
4146
  * @example
4194
4147
  * // create tile layer data with tile index 0 and random orientation and color
4195
4148
  * const tileIndex = 0;
@@ -4221,28 +4174,27 @@ class TileLayerData
4221
4174
  clear() { this.tile = this.direction = 0; this.mirror = false; this.color = new Color; }
4222
4175
  }
4223
4176
 
4177
+ ///////////////////////////////////////////////////////////////////////////////
4224
4178
  /**
4225
4179
  * Tile Layer - cached rendering system for tile layers
4226
4180
  * - Each Tile layer is rendered to an off screen canvas
4227
4181
  * - To allow dynamic modifications, layers are rendered using canvas 2d
4228
4182
  * - Some devices like mobile phones are limited to 4k texture resolution
4229
- * - So with 16x16 tiles this limits layers to 256x256 on mobile devices
4183
+ * - For with 16x16 tiles this limits layers to 256x256 on mobile devices
4230
4184
  * @extends EngineObject
4231
4185
  * @example
4232
- * // create tile collision and visible tile layer
4233
- * initTileCollision(vec2(200,100));
4234
- * const tileLayer = new TileLayer();
4186
+ * const tileLayer = new TileLayer(vec2(), vec2(200,100));
4235
4187
  */
4236
4188
  class TileLayer extends EngineObject
4237
4189
  {
4238
4190
  /** Create a tile layer object
4239
- * @param {Vector2} [position=(0,0)] - World space position
4240
- * @param {Vector2} [size=tileCollisionSize] - World space size
4241
- * @param {TileInfo} [tileInfo] - Tile info for layer
4242
- * @param {Vector2} [scale=(1,1)] - How much to scale this layer when rendered
4243
- * @param {number} [renderOrder] - Objects are sorted by renderOrder
4191
+ * @param {Vector2} [position=(0,0)] - World space position
4192
+ * @param {Vector2} [size=(1,1)] - World space size
4193
+ * @param {TileInfo} [tileInfo] - Tile info for layer
4194
+ * @param {Vector2} [scale=(1,1)] - How much to scale this layer when rendered
4195
+ * @param {number} [renderOrder] - Objects are sorted by renderOrder
4244
4196
  */
4245
- constructor(position, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderOrder=0)
4197
+ constructor(position, size, tileInfo=tile(), scale=vec2(1), renderOrder=0)
4246
4198
  {
4247
4199
  super(position, size, tileInfo, 0, undefined, renderOrder);
4248
4200
 
@@ -4254,6 +4206,10 @@ class TileLayer extends EngineObject
4254
4206
  this.scale = scale;
4255
4207
  /** @property {boolean} - If true this layer will render to overlay canvas and appear above all objects */
4256
4208
  this.isOverlay = false;
4209
+ // set no friction by default, applied friction is max of both objects
4210
+ this.friction = 0;
4211
+ // set no elasticity by default, applied elasticity is max of both objects
4212
+ this.elasticity = 0;
4257
4213
 
4258
4214
  // init tile data
4259
4215
  this.data = [];
@@ -4450,13 +4406,151 @@ class TileLayer extends EngineObject
4450
4406
  * @param {number} [angle=0] */
4451
4407
  drawRect(pos, size, color, angle)
4452
4408
  { this.drawTile(pos, size, undefined, color, angle); }
4409
+ }
4410
+
4411
+ ///////////////////////////////////////////////////////////////////////////////
4412
+ /**
4413
+ * Tile Collision Layer - a tile layer with collision
4414
+ * - adds collision data and functions to TileLayer
4415
+ * - there can be multiple tile collision layers
4416
+ * - tile collison layers should not overlap each other
4417
+ * @extends TileLayer
4418
+ */
4419
+ class TileCollisionLayer extends TileLayer
4420
+ {
4421
+ /** Create a tile layer object
4422
+ * @param {Vector2} [position=(0,0)] - World space position
4423
+ * @param {Vector2} [size=(0,0)] - World space size
4424
+ * @param {TileInfo} [tileInfo] - Tile info for layer
4425
+ * @param {number} [renderOrder] - Objects are sorted by renderOrder
4426
+ */
4427
+ constructor(position, size, tileInfo=tile(), renderOrder=0)
4428
+ {
4429
+ const scale = vec2(1); // collision layers are not scaled
4430
+ super(position, size.floor(), tileInfo, scale, renderOrder);
4431
+
4432
+ /** @property {Array<number>} - The tile collision grid */
4433
+ this.collisionData = [];
4434
+ this.initCollision(this.size);
4435
+
4436
+ // keep track of all collision layers
4437
+ tileCollisionLayers.push(this);
4438
+ }
4439
+
4440
+ /** Destroy this collision layer */
4441
+ destroy()
4442
+ {
4443
+ if (this.destroyed)
4444
+ return;
4445
+
4446
+ // remove from collision layers array and destroy
4447
+ const index = tileCollisionLayers.indexOf(this);
4448
+ ASSERT(index >= 0, 'tile collision layer not found in array');
4449
+ tileCollisionLayers.splice(index, 1);
4450
+ super.destroy();
4451
+ }
4452
+
4453
+ /** Clear and initialize tile collision to new size
4454
+ * @param {Vector2} size - width and height of tile collision 2d grid */
4455
+ initCollision(size)
4456
+ {
4457
+ this.size = size.floor();
4458
+ this.collisionData = [];
4459
+ this.collisionData.length = size.area();
4460
+ this.collisionData.fill(0);
4461
+ }
4462
+
4463
+ /** Set tile collision data for a given cell in the grid
4464
+ * @param {Vector2} pos
4465
+ * @param {number} [data] */
4466
+ setCollisionData(pos, data=1)
4467
+ {
4468
+ const i = (pos.y|0)*this.size.x + pos.x|0;
4469
+ pos.arrayCheck(this.size) && (this.collisionData[i] = data);
4470
+ }
4471
+
4472
+ /** Get tile collision data for a given cell in the grid
4473
+ * @param {Vector2} pos
4474
+ * @return {number} */
4475
+ getCollisionData(pos)
4476
+ {
4477
+ const i = (pos.y|0)*this.size.x + pos.x|0;
4478
+ return pos.arrayCheck(this.size) ? this.collisionData[i] : 0;
4479
+ }
4480
+
4481
+ /** Check if collision with another object should occur
4482
+ * @param {Vector2} pos
4483
+ * @param {Vector2} [size=(0,0)]
4484
+ * @param {EngineObject} [object]
4485
+ * @return {boolean} */
4486
+ collisionTest(pos, size=vec2(), object)
4487
+ {
4488
+ const minX = max(pos.x - size.x/2|0, 0);
4489
+ const minY = max(pos.y - size.y/2|0, 0);
4490
+ const maxX = min(pos.x + size.x/2, this.size.x);
4491
+ const maxY = min(pos.y + size.y/2, this.size.y);
4492
+ for (let y = minY; y < maxY; ++y)
4493
+ for (let x = minX; x < maxX; ++x)
4494
+ {
4495
+ // check if the object should collide with this tile
4496
+ const tileData = this.collisionData[y*this.size.x+x];
4497
+ if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
4498
+ return true;
4499
+ }
4500
+ return false;
4501
+ }
4502
+
4503
+ /** Return the center of first tile hit, undefined if nothing was hit.
4504
+ * This does not return the exact intersection, but the center of the tile hit.
4505
+ * @param {Vector2} posStart
4506
+ * @param {Vector2} posEnd
4507
+ * @param {EngineObject} [object]
4508
+ * @return {Vector2} */
4509
+ collisionRaycast(posStart, posEnd, object)
4510
+ {
4511
+ // test if a ray collides with tiles from start to end
4512
+ // todo: a way to get the exact hit point, it must still be inside the hit tile
4513
+ const delta = posEnd.subtract(posStart);
4514
+ const totalLength = delta.length();
4515
+ const normalizedDelta = delta.normalize();
4516
+ const unit = vec2(abs(1/normalizedDelta.x), abs(1/normalizedDelta.y));
4517
+ const flooredPosStart = posStart.floor();
4518
+
4519
+ // setup iteration variables
4520
+ let pos = flooredPosStart;
4521
+ let xi = unit.x * (delta.x < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1);
4522
+ let yi = unit.y * (delta.y < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1);
4523
+
4524
+ // use line drawing algorithm to test for collisions
4525
+ while (true)
4526
+ {
4527
+ // check for tile collision
4528
+ const tileData = this.getCollisionData(pos);
4529
+ if (tileData && (!object || object.collideWithTile(tileData, pos)))
4530
+ {
4531
+ debugRaycast && debugLine(posStart, posEnd, '#f00', .02);
4532
+ debugRaycast && debugPoint(pos.add(vec2(.5)), '#ff0');
4533
+ return pos.add(vec2(.5));
4534
+ }
4535
+
4536
+ // check if past the end
4537
+ if (xi > totalLength && yi > totalLength)
4538
+ break;
4539
+
4540
+ // get coordinates of next tile to check
4541
+ if (xi > yi)
4542
+ pos.y += sign(delta.y), yi += unit.y;
4543
+ else
4544
+ pos.x += sign(delta.x), xi += unit.x;
4545
+ }
4546
+
4547
+ debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
4548
+ }
4453
4549
  }
4454
4550
  /**
4455
4551
  * LittleJS Particle System
4456
4552
  */
4457
4553
 
4458
-
4459
-
4460
4554
  /**
4461
4555
  * Particle Emitter - Spawns particles with the given settings
4462
4556
  * @extends EngineObject
@@ -4801,8 +4895,6 @@ class Particle extends EngineObject
4801
4895
  * @namespace Medals
4802
4896
  */
4803
4897
 
4804
-
4805
-
4806
4898
  /** List of all medals
4807
4899
  * @type {Object}
4808
4900
  * @memberof Medals */
@@ -4990,8 +5082,6 @@ class Medal
4990
5082
  * @namespace WebGL
4991
5083
  */
4992
5084
 
4993
-
4994
-
4995
5085
  /** The WebGL canvas which appears above the main canvas and below the overlay canvas
4996
5086
  * @type {HTMLCanvasElement}
4997
5087
  * @memberof WebGL */
@@ -5190,7 +5280,19 @@ function glCreateTexture(image)
5190
5280
  const texture = glContext.createTexture();
5191
5281
  glContext.bindTexture(glContext.TEXTURE_2D, texture);
5192
5282
  if (image && image.width)
5283
+ {
5193
5284
  glSetTextureData(texture, image);
5285
+
5286
+ const isPowerOfTwo = (value)=> !(value & (value - 1));
5287
+ if (!tilesPixelated && isPowerOfTwo(image.width) && isPowerOfTwo(image.height))
5288
+ {
5289
+ // use mipmap filtering
5290
+ glContext.generateMipmap(glContext.TEXTURE_2D);
5291
+ glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, glContext.LINEAR_MIPMAP_LINEAR);
5292
+ glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, glContext.LINEAR);
5293
+ return texture;
5294
+ }
5295
+ }
5194
5296
  else
5195
5297
  {
5196
5298
  // create a white texture
@@ -5198,7 +5300,7 @@ function glCreateTexture(image)
5198
5300
  glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
5199
5301
  }
5200
5302
 
5201
- // use point filtering for pixelated rendering
5303
+ // set texture filtering
5202
5304
  const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
5203
5305
  glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
5204
5306
  glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, filter);
@@ -5314,8 +5416,6 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=-1, rgba
5314
5416
  * @namespace Engine
5315
5417
  */
5316
5418
 
5317
-
5318
-
5319
5419
  /** Name of engine
5320
5420
  * @type {string}
5321
5421
  * @default
@@ -5326,7 +5426,7 @@ const engineName = 'LittleJS';
5326
5426
  * @type {string}
5327
5427
  * @default
5328
5428
  * @memberof Engine */
5329
- const engineVersion = '1.11.17';
5429
+ const engineVersion = '1.12.6';
5330
5430
 
5331
5431
  /** Frames per second to update
5332
5432
  * @type {number}
@@ -5408,7 +5508,7 @@ function engineAddPlugin(updateFunction, renderFunction)
5408
5508
  * @param {Array<string>} [imageSources=[]] - List of images to load
5409
5509
  * @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
5410
5510
  * @memberof Engine */
5411
- function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
5511
+ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
5412
5512
  {
5413
5513
  ASSERT(!mainContext, 'engine already initialized');
5414
5514
  ASSERT(Array.isArray(imageSources), 'pass in images as array');
@@ -5565,16 +5665,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5565
5665
  mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
5566
5666
  }
5567
5667
 
5568
- function startEngine()
5668
+ // wait for gameInit to load
5669
+ async function startEngine()
5569
5670
  {
5570
- new Promise((resolve) => resolve(gameInit())).then(engineUpdate);
5671
+ await gameInit();
5672
+ engineUpdate();
5571
5673
  }
5572
-
5573
5674
  if (headlessMode)
5574
- {
5575
- startEngine();
5576
- return;
5577
- }
5675
+ return startEngine();
5578
5676
 
5579
5677
  // setup html
5580
5678
  const styleRoot =
@@ -5650,8 +5748,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5650
5748
  }));
5651
5749
  }
5652
5750
 
5653
- // load all of the images
5654
- Promise.all(promises).then(startEngine);
5751
+ // wait for all the promises to finish
5752
+ await Promise.all(promises);
5753
+ return startEngine();
5655
5754
  }
5656
5755
 
5657
5756
  /** Update each engine object, remove destroyed objects, and update time
@@ -5920,459 +6019,154 @@ function drawEngineSplashScreen(t)
5920
6019
  x.restore();
5921
6020
  }
5922
6021
 
5923
-
6022
+ /**
6023
+ * LittleJS Newgrounds API
6024
+ * - NewgroundsMedal extends Medal with Newgrounds API functionality
6025
+ * - Call new NewgroundsPlugin() to setup Newgrounds
6026
+ * - Uses CryptoJS for encryption if optional cipher is provided
6027
+ * - Keeps connection alive and logs views
6028
+ * - Functions to interact with scoreboards
6029
+ * - Functions to unlock medals
6030
+ */
6031
+
6032
+ /** Global Newgrounds object
6033
+ * @type {NewgroundsPlugin}
6034
+ * @memberof Medal */
6035
+ let newgrounds;
6036
+
6037
+ ///////////////////////////////////////////////////////////////////////////////
5924
6038
  /**
5925
- * LittleJS Module Export
6039
+ * Newgrounds medal auto unlocks in newgrounds API
6040
+ * @extends Medal
5926
6041
  */
6042
+ class NewgroundsMedal extends Medal
6043
+ {
6044
+ /** Create a newgrounds medal object and adds it to the list of medals
6045
+ * @param {Number} id - The unique identifier of the medal
6046
+ * @param {String} name - Name of the medal
6047
+ * @param {String} [description] - Description of the medal
6048
+ * @param {String} [icon] - Icon for the medal
6049
+ * @param {String} [src] - Image location for the medal
6050
+ */
6051
+ constructor(id, name, description, icon, src)
6052
+ { super(id, name, description, icon, src); }
5927
6053
 
5928
- 'use strict';
6054
+ /** Unlocks a medal if not already unlocked */
6055
+ unlock()
6056
+ {
6057
+ super.unlock();
6058
+ newgrounds && newgrounds.unlockMedal(this.id);
6059
+ }
6060
+ }
5929
6061
 
5930
- export
6062
+ ///////////////////////////////////////////////////////////////////////////////
6063
+ /**
6064
+ * Newgrounds API object
6065
+ */
6066
+ class NewgroundsPlugin
5931
6067
  {
5932
- // Engine
5933
- engineName,
5934
- engineVersion,
5935
- frameRate,
5936
- timeDelta,
5937
- engineObjects,
5938
- frame,
5939
- time,
5940
- timeReal,
5941
- paused,
5942
- setPaused,
5943
- engineInit,
5944
- engineObjectsUpdate,
5945
- engineObjectsDestroy,
5946
- engineObjectsCollect,
5947
- engineObjectsCallback,
5948
- engineObjectsRaycast,
5949
- engineAddPlugin,
6068
+ /** Create the global newgrounds object
6069
+ * @param {string} app_id - The newgrounds App ID
6070
+ * @param {string} [cipher] - The encryption Key (AES-128/Base64)
6071
+ * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher
6072
+ * @example
6073
+ * // create the newgrounds object, replace the app id with your own
6074
+ * const app_id = 'your_app_id_here';
6075
+ * new NewgroundsPlugin(app_id);
6076
+ */
6077
+ constructor(app_id, cipher, cryptoJS)
6078
+ {
6079
+ ASSERT(!newgrounds, 'there can only be one newgrounds object');
6080
+ ASSERT(!cipher || cryptoJS, 'must provide cryptojs if there is a cipher');
5950
6081
 
5951
- // Globals
5952
- debug,
5953
- debugOverlay,
5954
- showWatermark,
6082
+ newgrounds = this; // set global newgrounds object
6083
+ this.app_id = app_id;
6084
+ this.cipher = cipher;
6085
+ this.cryptoJS = cryptoJS;
6086
+ this.host = location ? location.hostname : '';
5955
6087
 
5956
- // Debug
5957
- ASSERT,
5958
- debugRect,
5959
- debugPoly,
5960
- debugCircle,
5961
- debugPoint,
5962
- debugLine,
5963
- debugOverlap,
5964
- debugText,
5965
- debugClear,
5966
- debugScreenshot,
5967
- debugSaveCanvas,
5968
- debugSaveText,
5969
- debugSaveDataURL,
5970
- debugShowErrors,
5971
- debugVideoCaptureIsActive,
5972
- debugVideoCaptureStart,
5973
- debugVideoCaptureStop,
6088
+ // get session id from url search params
6089
+ const url = new URL(location.href);
6090
+ this.session_id = url.searchParams.get('ngio_session_id');
5974
6091
 
5975
- // Settings
5976
- cameraPos,
5977
- cameraScale,
5978
- canvasMaxSize,
5979
- canvasFixedSize,
5980
- canvasPixelated,
5981
- tilesPixelated,
5982
- fontDefault,
5983
- showSplashScreen,
5984
- headlessMode,
5985
- tileSizeDefault,
5986
- tileFixBleedScale,
5987
- enablePhysicsSolver,
5988
- objectDefaultMass,
5989
- objectDefaultDamping,
5990
- objectDefaultAngleDamping,
5991
- objectDefaultElasticity,
5992
- objectDefaultFriction,
5993
- objectMaxSpeed,
5994
- gravity,
5995
- particleEmitRateScale,
5996
- glEnable,
5997
- glOverlay,
5998
- gamepadsEnable,
5999
- gamepadDirectionEmulateStick,
6000
- inputWASDEmulateDirection,
6001
- touchGamepadEnable,
6002
- touchGamepadAnalog,
6003
- touchGamepadSize,
6004
- touchGamepadAlpha,
6005
- vibrateEnable,
6006
- soundEnable,
6007
- soundVolume,
6008
- soundDefaultRange,
6009
- soundDefaultTaper,
6010
- medalDisplayTime,
6011
- medalDisplaySlideTime,
6012
- medalDisplaySize,
6092
+ if (!this.session_id)
6093
+ return; // only use newgrounds when logged in
6013
6094
 
6014
- // Setters for globals
6015
- setCameraPos,
6016
- setCameraScale,
6017
- setCanvasMaxSize,
6018
- setCanvasFixedSize,
6019
- setCanvasPixelated,
6020
- setTilesPixelated,
6021
- setFontDefault,
6022
- setShowSplashScreen,
6023
- setHeadlessMode,
6024
- setGlEnable,
6025
- setGlOverlay,
6026
- setTileSizeDefault,
6027
- setTileFixBleedScale,
6028
- setEnablePhysicsSolver,
6029
- setObjectDefaultMass,
6030
- setObjectDefaultDamping,
6031
- setObjectDefaultAngleDamping,
6032
- setObjectDefaultElasticity,
6033
- setObjectDefaultFriction,
6034
- setObjectMaxSpeed,
6035
- setGravity,
6036
- setParticleEmitRateScale,
6037
- setTouchInputEnable,
6038
- setGamepadsEnable,
6039
- setGamepadDirectionEmulateStick,
6040
- setInputWASDEmulateDirection,
6041
- setTouchGamepadEnable,
6042
- setTouchGamepadAnalog,
6043
- setTouchGamepadSize,
6044
- setTouchGamepadAlpha,
6045
- setVibrateEnable,
6046
- setSoundEnable,
6047
- setSoundVolume,
6048
- setSoundDefaultRange,
6049
- setSoundDefaultTaper,
6050
- setMedalDisplayTime,
6051
- setMedalDisplaySlideTime,
6052
- setMedalDisplaySize,
6053
- setMedalsPreventUnlock,
6054
- setShowWatermark,
6055
- setDebugKey,
6095
+ // get medals
6096
+ const medalsResult = this.call('Medal.getList');
6097
+ this.medals = medalsResult ? medalsResult.result.data['medals'] : [];
6098
+ debugMedals && console.log(this.medals);
6099
+ for (const newgroundsMedal of this.medals)
6100
+ {
6101
+ const medal = medals[newgroundsMedal['id']];
6102
+ if (medal)
6103
+ {
6104
+ // copy newgrounds medal data
6105
+ medal.image = new Image;
6106
+ medal.image.src = newgroundsMedal['icon'];
6107
+ medal.name = newgroundsMedal['name'];
6108
+ medal.description = newgroundsMedal['description'];
6109
+ medal.unlocked = newgroundsMedal['unlocked'];
6110
+ medal.difficulty = newgroundsMedal['difficulty'];
6111
+ medal.value = newgroundsMedal['value'];
6056
6112
 
6057
- // Utilities
6058
- PI,
6059
- abs,
6060
- min,
6061
- max,
6062
- sign,
6063
- mod,
6064
- clamp,
6065
- percent,
6066
- distanceWrap,
6067
- lerpWrap,
6068
- distanceAngle,
6069
- lerpAngle,
6070
- lerp,
6071
- smoothStep,
6072
- nearestPowerOfTwo,
6073
- isOverlapping,
6074
- isIntersecting,
6075
- wave,
6076
- formatTime,
6113
+ if (medal.value) // add value to description
6114
+ medal.description = medal.description + ` (${ medal.value })`;
6115
+ }
6116
+ }
6117
+
6118
+ // get scoreboards
6119
+ const scoreboardResult = this.call('ScoreBoard.getBoards');
6120
+ this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
6121
+ debugMedals && console.log(this.scoreboards);
6077
6122
 
6078
- // Random
6079
- rand,
6080
- randInt,
6081
- randSign,
6082
- randInCircle,
6083
- randVector,
6084
- randColor,
6123
+ // keep the session alive with a ping every minute
6124
+ const keepAliveMS = 60 * 1e3;
6125
+ setInterval(()=>this.call('Gateway.ping', 0, true), keepAliveMS);
6126
+ }
6085
6127
 
6086
- // Utility Classes
6087
- RandomGenerator,
6088
- Vector2,
6089
- Color,
6090
- Timer,
6091
- vec2,
6092
- rgb,
6093
- hsl,
6094
- isColor,
6128
+ /** Send message to unlock a medal by id
6129
+ * @param {number} id - The medal id */
6130
+ unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, true); }
6095
6131
 
6096
- // Default Colors
6097
- WHITE,
6098
- BLACK,
6099
- GRAY,
6100
- RED,
6101
- ORANGE,
6102
- YELLOW,
6103
- GREEN,
6104
- CYAN,
6105
- BLUE,
6106
- PURPLE,
6107
- MAGENTA,
6132
+ /** Send message to post score
6133
+ * @param {number} id - The scoreboard id
6134
+ * @param {number} value - The score value */
6135
+ postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, true); }
6108
6136
 
6109
- // Draw
6110
- textureInfos,
6111
- tile,
6112
- TileInfo,
6113
- TextureInfo,
6114
- mainCanvas,
6115
- mainContext,
6116
- overlayCanvas,
6117
- overlayContext,
6118
- mainCanvasSize,
6119
- screenToWorld,
6120
- worldToScreen,
6121
- drawTile,
6122
- drawRect,
6123
- drawLine,
6124
- drawPoly,
6125
- drawEllipse,
6126
- drawCircle,
6127
- drawCanvas2D,
6128
- drawText,
6129
- drawTextOverlay,
6130
- drawTextScreen,
6131
- setBlendMode,
6132
- combineCanvases,
6133
- engineFontImage,
6134
- FontImage,
6135
- isFullscreen,
6136
- toggleFullscreen,
6137
- setCursor,
6138
- getCameraSize,
6137
+ /** Get scores from a scoreboard
6138
+ * @param {number} id - The scoreboard id
6139
+ * @param {string} [user] - A user's id or name
6140
+ * @param {number} [social] - If true, only social scores will be loaded
6141
+ * @param {number} [skip] - Number of scores to skip before start
6142
+ * @param {number} [limit] - Number of scores to include in the list
6143
+ * @return {Object} - The response JSON object
6144
+ */
6145
+ getScores(id, user, social=0, skip=0, limit=10)
6146
+ { return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
6139
6147
 
6140
- // WebGL
6141
- glCanvas,
6142
- glContext,
6143
- glCompileShader,
6144
- glCopyToContext,
6145
- glCreateProgram,
6146
- glCreateTexture,
6147
- glSetTextureData,
6148
- glDraw,
6149
- glFlush,
6150
- glSetTexture,
6151
- glSetAntialias,
6152
- glClearCanvas,
6153
- glAntialias,
6154
- glShader,
6155
- glActiveTexture,
6156
- glArrayBuffer,
6157
- glGeometryBuffer,
6158
- glPositionData,
6159
- glColorData,
6160
- glInstanceCount,
6161
- glAdditive,
6162
- glBatchAdditive,
6148
+ /** Send message to log a view */
6149
+ logView() { return this.call('App.logView', {'host':this.host}, true); }
6163
6150
 
6164
- // Input
6165
- keyIsDown,
6166
- keyWasPressed,
6167
- keyWasReleased,
6168
- keyDirection,
6169
- clearInput,
6170
- mouseIsDown,
6171
- mouseWasPressed,
6172
- mouseWasReleased,
6173
- mousePos,
6174
- mousePosScreen,
6175
- mouseWheel,
6176
- isUsingGamepad,
6177
- inputPreventDefault,
6178
- setInputPreventDefault,
6179
- gamepadIsDown,
6180
- gamepadWasPressed,
6181
- gamepadWasReleased,
6182
- gamepadStick,
6183
- gamepadsUpdate,
6184
- vibrate,
6185
- vibrateStop,
6186
- isTouchDevice,
6187
-
6188
- // Audio
6189
- Sound,
6190
- SoundWave,
6191
- playAudioFile,
6192
- speak,
6193
- speakStop,
6194
- getNoteFrequency,
6195
- playSamples,
6196
- zzfx,
6197
- zzfxG,
6198
- zzfxR,
6199
- audioContext,
6200
-
6201
- // Base Object
6202
- EngineObject,
6203
-
6204
- // Tiles
6205
- tileCollision,
6206
- tileCollisionSize,
6207
- initTileCollision,
6208
- setTileCollisionData,
6209
- getTileCollisionData,
6210
- tileCollisionTest,
6211
- tileCollisionRaycast,
6212
- TileLayerData,
6213
- TileLayer,
6214
-
6215
- // Particles
6216
- ParticleEmitter,
6217
- Particle,
6218
-
6219
- // Medals
6220
- medals,
6221
- medalsPreventUnlock,
6222
- medalsInit,
6223
- Medal,
6224
- };
6225
-
6226
- /**
6227
- * LittleJS Newgrounds API
6228
- * - NewgroundsMedal extends Medal with Newgrounds API functionality
6229
- * - Call new NewgroundsPlugin() to setup Newgrounds
6230
- * - Uses CryptoJS for encryption if optional cipher is provided
6231
- * - Keeps connection alive and logs views
6232
- * - Functions to interact with scoreboards
6233
- * - Functions to unlock medals
6234
- */
6235
-
6236
- 'use strict';
6237
-
6238
- /** Global Newgrounds object
6239
- * @type {NewgroundsPlugin}
6240
- * @memberof Medal */
6241
- let newgrounds;
6242
-
6243
- ///////////////////////////////////////////////////////////////////////////////
6244
- /**
6245
- * Newgrounds medal auto unlocks in newgrounds API
6246
- * @extends Medal
6247
- */
6248
- class NewgroundsMedal extends Medal
6249
- {
6250
- /** Create a newgrounds medal object and adds it to the list of medals
6251
- * @param {Number} id - The unique identifier of the medal
6252
- * @param {String} name - Name of the medal
6253
- * @param {String} [description] - Description of the medal
6254
- * @param {String} [icon] - Icon for the medal
6255
- * @param {String} [src] - Image location for the medal
6256
- */
6257
- constructor(id, name, description, icon, src)
6258
- { super(id, name, description, icon, src); }
6259
-
6260
- /** Unlocks a medal if not already unlocked */
6261
- unlock()
6262
- {
6263
- super.unlock();
6264
- newgrounds && newgrounds.unlockMedal(this.id);
6265
- }
6266
- }
6267
-
6268
- ///////////////////////////////////////////////////////////////////////////////
6269
- /**
6270
- * Newgrounds API object
6271
- */
6272
- class NewgroundsPlugin
6273
- {
6274
- /** Create the global newgrounds object
6275
- * @param {string} app_id - The newgrounds App ID
6276
- * @param {string} [cipher] - The encryption Key (AES-128/Base64)
6277
- * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher
6278
- * @example
6279
- * // create the newgrounds object, replace the app id with your own
6280
- * const app_id = 'your_app_id_here';
6281
- * new NewgroundsPlugin(app_id);
6282
- */
6283
- constructor(app_id, cipher, cryptoJS)
6284
- {
6285
- ASSERT(!newgrounds, 'there can only be one newgrounds object');
6286
- ASSERT(!cipher || cryptoJS, 'must provide cryptojs if there is a cipher');
6287
-
6288
- newgrounds = this; // set global newgrounds object
6289
- this.app_id = app_id;
6290
- this.cipher = cipher;
6291
- this.cryptoJS = cryptoJS;
6292
- this.host = location ? location.hostname : '';
6293
-
6294
- // get session id from url search params
6295
- const url = new URL(location.href);
6296
- this.session_id = url.searchParams.get('ngio_session_id');
6297
-
6298
- if (!this.session_id)
6299
- return; // only use newgrounds when logged in
6300
-
6301
- // get medals
6302
- const medalsResult = this.call('Medal.getList');
6303
- this.medals = medalsResult ? medalsResult.result.data['medals'] : [];
6304
- debugMedals && console.log(this.medals);
6305
- for (const newgroundsMedal of this.medals)
6306
- {
6307
- const medal = medals[newgroundsMedal['id']];
6308
- if (medal)
6309
- {
6310
- // copy newgrounds medal data
6311
- medal.image = new Image;
6312
- medal.image.src = newgroundsMedal['icon'];
6313
- medal.name = newgroundsMedal['name'];
6314
- medal.description = newgroundsMedal['description'];
6315
- medal.unlocked = newgroundsMedal['unlocked'];
6316
- medal.difficulty = newgroundsMedal['difficulty'];
6317
- medal.value = newgroundsMedal['value'];
6318
-
6319
- if (medal.value) // add value to description
6320
- medal.description = medal.description + ` (${ medal.value })`;
6321
- }
6322
- }
6323
-
6324
- // get scoreboards
6325
- const scoreboardResult = this.call('ScoreBoard.getBoards');
6326
- this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
6327
- debugMedals && console.log(this.scoreboards);
6328
-
6329
- // keep the session alive with a ping every minute
6330
- const keepAliveMS = 60 * 1e3;
6331
- setInterval(()=>this.call('Gateway.ping', 0, true), keepAliveMS);
6332
- }
6333
-
6334
- /** Send message to unlock a medal by id
6335
- * @param {number} id - The medal id */
6336
- unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, true); }
6337
-
6338
- /** Send message to post score
6339
- * @param {number} id - The scoreboard id
6340
- * @param {number} value - The score value */
6341
- postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, true); }
6342
-
6343
- /** Get scores from a scoreboard
6344
- * @param {number} id - The scoreboard id
6345
- * @param {string} [user] - A user's id or name
6346
- * @param {number} [social] - If true, only social scores will be loaded
6347
- * @param {number} [skip] - Number of scores to skip before start
6348
- * @param {number} [limit] - Number of scores to include in the list
6349
- * @return {Object} - The response JSON object
6350
- */
6351
- getScores(id, user, social=0, skip=0, limit=10)
6352
- { return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
6353
-
6354
- /** Send message to log a view */
6355
- logView() { return this.call('App.logView', {'host':this.host}, true); }
6356
-
6357
- /** Send a message to call a component of the Newgrounds API
6358
- * @param {string} component - Name of the component
6359
- * @param {Object} [parameters] - Parameters to use for call
6360
- * @param {boolean} [async] - If true, don't wait for response before continuing
6361
- * @return {Object} - The response JSON object
6362
- */
6363
- call(component, parameters, async=false)
6364
- {
6365
- const call = {'component':component, 'parameters':parameters};
6366
- if (this.cipher)
6367
- {
6368
- // encrypt using AES-128 Base64 with cryptoJS
6369
- const cryptoJS = this.cryptoJS;
6370
- const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
6371
- const iv = cryptoJS['lib']['WordArray']['random'](16);
6372
- const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
6373
- call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
6374
- call['parameters'] = 0;
6375
- }
6151
+ /** Send a message to call a component of the Newgrounds API
6152
+ * @param {string} component - Name of the component
6153
+ * @param {Object} [parameters] - Parameters to use for call
6154
+ * @param {boolean} [async] - If true, don't wait for response before continuing
6155
+ * @return {Object} - The response JSON object
6156
+ */
6157
+ call(component, parameters, async=false)
6158
+ {
6159
+ const call = {'component':component, 'parameters':parameters};
6160
+ if (this.cipher)
6161
+ {
6162
+ // encrypt using AES-128 Base64 with cryptoJS
6163
+ const cryptoJS = this.cryptoJS;
6164
+ const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
6165
+ const iv = cryptoJS['lib']['WordArray']['random'](16);
6166
+ const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
6167
+ call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
6168
+ call['parameters'] = 0;
6169
+ }
6376
6170
 
6377
6171
  // build the input object
6378
6172
  const input =
@@ -6408,8 +6202,6 @@ class NewgroundsPlugin
6408
6202
  * - can be enabled to pass other canvases through a final shader
6409
6203
  */
6410
6204
 
6411
- 'use strict';
6412
-
6413
6205
  ///////////////////////////////////////////////////////////////////////////////
6414
6206
 
6415
6207
  /** Global Post Process plugin object
@@ -6522,8 +6314,6 @@ class PostProcessPlugin
6522
6314
  * LittleJS ZzFXM Plugin
6523
6315
  */
6524
6316
 
6525
- 'use strict';
6526
-
6527
6317
  /**
6528
6318
  * Music Object - Stores a zzfx music track for later use
6529
6319
  *
@@ -6692,8 +6482,6 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
6692
6482
  * - Images
6693
6483
  */
6694
6484
 
6695
- 'use strict';
6696
-
6697
6485
  ///////////////////////////////////////////////////////////////////////////////
6698
6486
 
6699
6487
  /** Global UI system plugin object
@@ -6861,6 +6649,8 @@ class UIObject
6861
6649
  this.lineWidth = uiSystem.defaultLineWidth;
6862
6650
  /** @property {string} */
6863
6651
  this.font = uiSystem.defaultFont;
6652
+ /** @property {number} - override for text height */
6653
+ this.textHeight = undefined;
6864
6654
  /** @property {boolean} */
6865
6655
  this.visible = true;
6866
6656
  /** @property {Array<UIObject>} */
@@ -6971,7 +6761,8 @@ class UIText extends UIObject
6971
6761
  }
6972
6762
  render()
6973
6763
  {
6974
- uiSystem.drawText(this.text, this.pos, this.size, this.textColor, this.lineWidth, this.lineColor, this.align, this.font);
6764
+ const textSize = vec2(this.size.x, this.textHeight || this.size.y);
6765
+ uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.lineWidth, this.lineColor, this.align, this.font);
6975
6766
  }
6976
6767
  }
6977
6768
 
@@ -7034,7 +6825,9 @@ class UIButton extends UIObject
7034
6825
  const lineColor = this.mouseIsHeld ? this.color : this.lineColor;
7035
6826
  const color = this.mouseIsOver? this.hoverColor : this.color;
7036
6827
  uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor);
7037
- const textSize = vec2(this.size.x, this.size.y*.8);
6828
+
6829
+ const textScale = .8; // scale text to fit in button
6830
+ const textSize = vec2(this.size.x, this.textHeight || this.size.y*textScale);
7038
6831
  uiSystem.drawText(this.text, this.pos, textSize,
7039
6832
  this.textColor, 0, undefined, this.align, this.font);
7040
6833
  }
@@ -7131,7 +6924,8 @@ class UIScrollbar extends UIObject
7131
6924
  const barColor = this.mouseIsHeld ? this.color : this.handleColor;
7132
6925
  uiSystem.drawRect(handlePos, handleSize, barColor, this.lineWidth, this.lineColor);
7133
6926
 
7134
- const textSize = vec2(this.size.x, this.size.y*.8);
6927
+ const textScale = .8; // scale text to fit in scrollbar
6928
+ const textSize = vec2(this.size.x, this.textHeight || this.size.y*textScale);
7135
6929
  uiSystem.drawText(this.text, this.pos, textSize,
7136
6930
  this.textColor, 0, undefined, this.align, this.font);
7137
6931
  }
@@ -7140,29 +6934,35 @@ class UIScrollbar extends UIObject
7140
6934
  /**
7141
6935
  * LittleJS Box2D Physics Plugin
7142
6936
  * - Box2dObject extends EngineObject with Box2D physics
7143
- * - Call box2dEngineInit() to start instead of normal engineInit()
6937
+ * - Call box2dInit() before engineInit() to enable
7144
6938
  * - You will also need to include box2d.wasm.js
7145
- * - Uses box2d.js super fast web assembly port of Box2D
6939
+ * - Uses a super fast web assembly port of Box2D
7146
6940
  * - More info: https://github.com/kripken/box2d.js
7147
- * - Fully wraps everything in Box2d
7148
6941
  * - Functions to create polygon, circle, and edge shapes
7149
- * - Raycasting and querying
7150
- * - Joint creation
7151
6942
  * - Contact begin and end callbacks
6943
+ * - Wraps b2Vec2 type to/from Vector2
6944
+ * - Raycasting and querying
6945
+ * - Every type of joint
7152
6946
  * - Debug physics drawing
6947
+ * @namespace Box2D
7153
6948
  */
7154
6949
 
7155
- 'use strict';
7156
-
7157
6950
  /** Global Box2d Plugin object
7158
- * @type {Box2dPlugin} */
6951
+ * @type {Box2dPlugin}
6952
+ * @memberof Box2D */
7159
6953
  let box2d;
7160
6954
 
7161
6955
  /** Enable Box2D debug drawing
7162
6956
  * @type {boolean}
7163
- * @default */
6957
+ * @default
6958
+ * @memberof Box2D */
7164
6959
  let box2dDebug = false;
7165
6960
 
6961
+ /** Enable Box2D debug drawing
6962
+ * @param {boolean} enable
6963
+ * @memberof Box2D */
6964
+ function box2dSetDebug(enable) { box2dDebug = enable; }
6965
+
7166
6966
  ///////////////////////////////////////////////////////////////////////////////
7167
6967
  /**
7168
6968
  * Box2D Object - extend with your own custom physics objects
@@ -7262,7 +7062,7 @@ class Box2dObject extends EngineObject
7262
7062
  * @param {number} [friction]
7263
7063
  * @param {number} [restitution]
7264
7064
  * @param {boolean} [isSensor] */
7265
- addShape(shape, density=1, friction=.2, restitution=0, isSensor=false)
7065
+ addShape(shape, density=1, friction=1, restitution=0, isSensor=false)
7266
7066
  {
7267
7067
  const fd = new box2d.instance.b2FixtureDef();
7268
7068
  fd.set_shape(shape);
@@ -8566,7 +8366,7 @@ class Box2dPlugin
8566
8366
  * @param {number} [frames] */
8567
8367
  step(frames=1)
8568
8368
  {
8569
- box2d.world.SetGravity(box2d.vec2dTo(vec2(0,gravity)));
8369
+ box2d.world.SetGravity(box2d.vec2dTo(gravity));
8570
8370
  for (let i=frames; i--;)
8571
8371
  box2d.world.Step(timeDelta, this.velocityIterations, this.positionIterations);
8572
8372
  }
@@ -8707,8 +8507,8 @@ class Box2dPlugin
8707
8507
  aabb.set_upperBound(box2d.vec2dTo(pos));
8708
8508
 
8709
8509
  let queryObject;
8710
- debugRaycast && debugRect(pos, vec2(), queryObject ? '#f00' : '#00f', .02);
8711
8510
  box2d.world.QueryAABB(queryCallback, aabb);
8511
+ debugRaycast && debugRect(pos, vec2(), queryObject ? '#f00' : '#00f', .02);
8712
8512
  return queryObject;
8713
8513
  }
8714
8514
 
@@ -8839,176 +8639,464 @@ class Box2dPlugin
8839
8639
  return new Vector2(v.get_x(), v.get_y());
8840
8640
  }
8841
8641
 
8842
- /** converts a box2d vec2 pointer to a Vector2
8843
- * @param {Object} v */
8844
- vec2FromPointer(v)
8845
- {
8846
- return box2d.vec2From(box2d.instance.wrapPointer(v, box2d.instance.b2Vec2));
8847
- }
8642
+ /** converts a box2d vec2 pointer to a Vector2
8643
+ * @param {Object} v */
8644
+ vec2FromPointer(v)
8645
+ {
8646
+ return box2d.vec2From(box2d.instance.wrapPointer(v, box2d.instance.b2Vec2));
8647
+ }
8648
+
8649
+ /** converts a Vector2 to a box2 vec2
8650
+ * @param {Vector2} v */
8651
+ vec2dTo(v)
8652
+ {
8653
+ ASSERT(v instanceof Vector2);
8654
+ return new box2d.instance.b2Vec2(v.x, v.y);
8655
+ }
8656
+
8657
+ /** checks if a box2d object is null
8658
+ * @param {Object} o */
8659
+ isNull(o) { return !box2d.instance.getPointer(o); }
8660
+
8661
+ /** casts a box2d object to its correct type
8662
+ * @param {Object} o */
8663
+ castObjectType(o)
8664
+ {
8665
+ switch (o.GetType())
8666
+ {
8667
+ case box2d.instance.b2Shape.e_circle:
8668
+ return box2d.instance.castObject(o, box2d.instance.b2CircleShape);
8669
+ case box2d.instance.b2Shape.e_edge:
8670
+ return box2d.instance.castObject(o, box2d.instance.b2EdgeShape);
8671
+ case box2d.instance.b2Shape.e_polygon:
8672
+ return box2d.instance.castObject(o, box2d.instance.b2PolygonShape);
8673
+ case box2d.instance.b2Shape.e_chain:
8674
+ return box2d.instance.castObject(o, box2d.instance.b2ChainShape);
8675
+ case box2d.instance.e_revoluteJoint:
8676
+ return box2d.instance.castObject(o, box2d.instance.b2RevoluteJoint);
8677
+ case box2d.instance.e_prismaticJoint:
8678
+ return box2d.instance.castObject(o, box2d.instance.b2PrismaticJoint);
8679
+ case box2d.instance.e_distanceJoint:
8680
+ return box2d.instance.castObject(o, box2d.instance.b2DistanceJoint);
8681
+ case box2d.instance.e_pulleyJoint:
8682
+ return box2d.instance.castObject(o, box2d.instance.b2PulleyJoint);
8683
+ case box2d.instance.e_mouseJoint:
8684
+ return box2d.instance.castObject(o, box2d.instance.b2MouseJoint);
8685
+ case box2d.instance.e_gearJoint:
8686
+ return box2d.instance.castObject(o, box2d.instance.b2GearJoint);
8687
+ case box2d.instance.e_wheelJoint:
8688
+ return box2d.instance.castObject(o, box2d.instance.b2WheelJoint);
8689
+ case box2d.instance.e_weldJoint:
8690
+ return box2d.instance.castObject(o, box2d.instance.b2WeldJoint);
8691
+ case box2d.instance.e_frictionJoint:
8692
+ return box2d.instance.castObject(o, box2d.instance.b2FrictionJoint);
8693
+ case box2d.instance.e_ropeJoint:
8694
+ return box2d.instance.castObject(o, box2d.instance.b2RopeJoint);
8695
+ case box2d.instance.e_motorJoint:
8696
+ return box2d.instance.castObject(o, box2d.instance.b2MotorJoint);
8697
+ }
8698
+
8699
+ ASSERT(false, 'Unknown box2d object type');
8700
+ }
8701
+ }
8702
+
8703
+ ///////////////////////////////////////////////////////////////////////////////
8704
+ /** Box2d Init - Call with await before starting LittleJS to init box2d
8705
+ * @return {Promise<Box2dPlugin>}
8706
+ * @memberof Box2D */
8707
+ async function box2dInit()
8708
+ {
8709
+ // load box2d
8710
+ new Box2dPlugin(await Box2D());
8711
+ setupDebugDraw();
8712
+ engineAddPlugin(box2dUpdate, box2dRender);
8713
+ return box2d;
8714
+
8715
+ // add the box2d plugin to the engine
8716
+ function box2dUpdate()
8717
+ {
8718
+ if (!paused)
8719
+ box2d.step();
8720
+ }
8721
+ function box2dRender()
8722
+ {
8723
+ if (box2dDebug || debugPhysics && debugOverlay)
8724
+ box2d.world.DrawDebugData();
8725
+ }
8726
+
8727
+ // box2d debug drawing
8728
+ function setupDebugDraw()
8729
+ {
8730
+ // setup debug draw
8731
+ const debugDraw = new box2d.instance.JSDraw();
8732
+ const box2dColor = (c)=> new Color(c.get_r(), c.get_g(), c.get_b());
8733
+ const box2dColorPointer = (c)=>
8734
+ box2dColor(box2d.instance.wrapPointer(c, box2d.instance.b2Color));
8735
+ const getDebugColor = (color)=>box2dColorPointer(color).scale(1,.8);
8736
+ const getPointsList = (vertices, vertexCount) =>
8737
+ {
8738
+ const points = [];
8739
+ for (let i=vertexCount; i--;)
8740
+ points.push(box2d.vec2FromPointer(vertices+i*8));
8741
+ return points;
8742
+ }
8743
+ debugDraw.DrawSegment = function(point1, point2, color)
8744
+ {
8745
+ color = getDebugColor(color);
8746
+ point1 = box2d.vec2FromPointer(point1);
8747
+ point2 = box2d.vec2FromPointer(point2);
8748
+ box2d.drawLine(vec2(), 0, point1, point2, color, undefined, overlayContext);
8749
+ };
8750
+ debugDraw.DrawPolygon = function(vertices, vertexCount, color)
8751
+ {
8752
+ color = getDebugColor(color);
8753
+ const points = getPointsList(vertices, vertexCount);
8754
+ box2d.drawPoly(vec2(), 0, points, undefined, color, undefined, overlayContext);
8755
+ };
8756
+ debugDraw.DrawSolidPolygon = function(vertices, vertexCount, color)
8757
+ {
8758
+ color = getDebugColor(color);
8759
+ const points = getPointsList(vertices, vertexCount);
8760
+ box2d.drawPoly(vec2(), 0, points, color, color, undefined, overlayContext);
8761
+ };
8762
+ debugDraw.DrawCircle = function(center, radius, color)
8763
+ {
8764
+ color = getDebugColor(color);
8765
+ center = box2d.vec2FromPointer(center);
8766
+ box2d.drawCircle(center, radius, undefined, color, undefined, overlayContext);
8767
+ };
8768
+ debugDraw.DrawSolidCircle = function(center, radius, axis, color)
8769
+ {
8770
+ color = getDebugColor(color);
8771
+ center = box2d.vec2FromPointer(center);
8772
+ axis = box2d.vec2FromPointer(axis).scale(radius);
8773
+ box2d.drawCircle(center, radius, color, color, undefined, overlayContext);
8774
+ box2d.drawLine(center, 0, vec2(), axis, color, undefined, overlayContext);
8775
+ };
8776
+ debugDraw.DrawTransform = function(transform)
8777
+ {
8778
+ transform = box2d.instance.wrapPointer(transform, box2d.instance.b2Transform);
8779
+ const pos = vec2(transform.get_p());
8780
+ const angle = -transform.get_q().GetAngle();
8781
+ const p1 = vec2(1,0), c1 = rgb(.75,0,0,.8);
8782
+ const p2 = vec2(0,1), c2 = rgb(0,.75,0,.8);
8783
+ box2d.drawLine(pos, angle, vec2(), p1, c1, undefined, overlayContext);
8784
+ box2d.drawLine(pos, angle, vec2(), p2, c2, undefined, overlayContext);
8785
+ }
8786
+
8787
+ debugDraw.AppendFlags(box2d.instance.b2Draw.e_shapeBit);
8788
+ debugDraw.AppendFlags(box2d.instance.b2Draw.e_jointBit);
8789
+ //debugDraw.AppendFlags(box2d.instance.b2Draw.e_aabbBit);
8790
+ //debugDraw.AppendFlags(box2d.instance.b2Draw.e_pairBit);
8791
+ //debugDraw.AppendFlags(box2d.instance.b2Draw.e_centerOfMassBit);
8792
+ box2d.world.SetDebugDraw(debugDraw);
8793
+ }
8794
+ }
8795
+
8796
+
8797
+ /**
8798
+ * LittleJS Module Export
8799
+ */
8800
+
8801
+ export
8802
+ {
8803
+ // Engine
8804
+ engineName,
8805
+ engineVersion,
8806
+ frameRate,
8807
+ timeDelta,
8808
+ engineObjects,
8809
+ frame,
8810
+ time,
8811
+ timeReal,
8812
+ paused,
8813
+ setPaused,
8814
+ engineInit,
8815
+ engineObjectsUpdate,
8816
+ engineObjectsDestroy,
8817
+ engineObjectsCollect,
8818
+ engineObjectsCallback,
8819
+ engineObjectsRaycast,
8820
+ engineAddPlugin,
8821
+
8822
+ // Globals
8823
+ debug,
8824
+ debugOverlay,
8825
+ showWatermark,
8826
+
8827
+ // Debug
8828
+ ASSERT,
8829
+ debugRect,
8830
+ debugPoly,
8831
+ debugCircle,
8832
+ debugPoint,
8833
+ debugLine,
8834
+ debugOverlap,
8835
+ debugText,
8836
+ debugClear,
8837
+ debugScreenshot,
8838
+ debugSaveCanvas,
8839
+ debugSaveText,
8840
+ debugSaveDataURL,
8841
+ debugShowErrors,
8842
+ debugVideoCaptureIsActive,
8843
+ debugVideoCaptureStart,
8844
+ debugVideoCaptureStop,
8845
+
8846
+ // Settings
8847
+ cameraPos,
8848
+ cameraScale,
8849
+ canvasMaxSize,
8850
+ canvasFixedSize,
8851
+ canvasPixelated,
8852
+ tilesPixelated,
8853
+ fontDefault,
8854
+ showSplashScreen,
8855
+ headlessMode,
8856
+ tileSizeDefault,
8857
+ tileFixBleedScale,
8858
+ enablePhysicsSolver,
8859
+ objectDefaultMass,
8860
+ objectDefaultDamping,
8861
+ objectDefaultAngleDamping,
8862
+ objectDefaultElasticity,
8863
+ objectDefaultFriction,
8864
+ objectMaxSpeed,
8865
+ gravity,
8866
+ particleEmitRateScale,
8867
+ glEnable,
8868
+ glOverlay,
8869
+ gamepadsEnable,
8870
+ gamepadDirectionEmulateStick,
8871
+ inputWASDEmulateDirection,
8872
+ touchGamepadEnable,
8873
+ touchGamepadAnalog,
8874
+ touchGamepadSize,
8875
+ touchGamepadAlpha,
8876
+ vibrateEnable,
8877
+ soundEnable,
8878
+ soundVolume,
8879
+ soundDefaultRange,
8880
+ soundDefaultTaper,
8881
+ medalDisplayTime,
8882
+ medalDisplaySlideTime,
8883
+ medalDisplaySize,
8884
+
8885
+ // Setters for globals
8886
+ setCameraPos,
8887
+ setCameraScale,
8888
+ setCanvasMaxSize,
8889
+ setCanvasFixedSize,
8890
+ setCanvasPixelated,
8891
+ setTilesPixelated,
8892
+ setFontDefault,
8893
+ setShowSplashScreen,
8894
+ setHeadlessMode,
8895
+ setGlEnable,
8896
+ setGlOverlay,
8897
+ setTileSizeDefault,
8898
+ setTileFixBleedScale,
8899
+ setEnablePhysicsSolver,
8900
+ setObjectDefaultMass,
8901
+ setObjectDefaultDamping,
8902
+ setObjectDefaultAngleDamping,
8903
+ setObjectDefaultElasticity,
8904
+ setObjectDefaultFriction,
8905
+ setObjectMaxSpeed,
8906
+ setGravity,
8907
+ setParticleEmitRateScale,
8908
+ setTouchInputEnable,
8909
+ setGamepadsEnable,
8910
+ setGamepadDirectionEmulateStick,
8911
+ setInputWASDEmulateDirection,
8912
+ setTouchGamepadEnable,
8913
+ setTouchGamepadAnalog,
8914
+ setTouchGamepadSize,
8915
+ setTouchGamepadAlpha,
8916
+ setVibrateEnable,
8917
+ setSoundEnable,
8918
+ setSoundVolume,
8919
+ setSoundDefaultRange,
8920
+ setSoundDefaultTaper,
8921
+ setMedalDisplayTime,
8922
+ setMedalDisplaySlideTime,
8923
+ setMedalDisplaySize,
8924
+ setMedalsPreventUnlock,
8925
+ setShowWatermark,
8926
+ setDebugKey,
8927
+
8928
+ // Utilities
8929
+ PI,
8930
+ abs,
8931
+ min,
8932
+ max,
8933
+ sign,
8934
+ mod,
8935
+ clamp,
8936
+ percent,
8937
+ distanceWrap,
8938
+ lerpWrap,
8939
+ distanceAngle,
8940
+ lerpAngle,
8941
+ lerp,
8942
+ smoothStep,
8943
+ nearestPowerOfTwo,
8944
+ isOverlapping,
8945
+ isIntersecting,
8946
+ wave,
8947
+ formatTime,
8948
+ fetchJSON,
8949
+
8950
+ // Random
8951
+ rand,
8952
+ randInt,
8953
+ randSign,
8954
+ randInCircle,
8955
+ randVector,
8956
+ randColor,
8957
+
8958
+ // Utility Classes
8959
+ RandomGenerator,
8960
+ Vector2,
8961
+ Color,
8962
+ Timer,
8963
+ vec2,
8964
+ rgb,
8965
+ hsl,
8966
+ isColor,
8967
+
8968
+ // Default Colors
8969
+ WHITE,
8970
+ BLACK,
8971
+ GRAY,
8972
+ RED,
8973
+ ORANGE,
8974
+ YELLOW,
8975
+ GREEN,
8976
+ CYAN,
8977
+ BLUE,
8978
+ PURPLE,
8979
+ MAGENTA,
8980
+
8981
+ // Draw
8982
+ textureInfos,
8983
+ tile,
8984
+ TileInfo,
8985
+ TextureInfo,
8986
+ mainCanvas,
8987
+ mainContext,
8988
+ overlayCanvas,
8989
+ overlayContext,
8990
+ mainCanvasSize,
8991
+ screenToWorld,
8992
+ worldToScreen,
8993
+ drawTile,
8994
+ drawRect,
8995
+ drawLine,
8996
+ drawPoly,
8997
+ drawEllipse,
8998
+ drawCircle,
8999
+ drawCanvas2D,
9000
+ drawText,
9001
+ drawTextOverlay,
9002
+ drawTextScreen,
9003
+ setBlendMode,
9004
+ combineCanvases,
9005
+ engineFontImage,
9006
+ FontImage,
9007
+ isFullscreen,
9008
+ toggleFullscreen,
9009
+ setCursor,
9010
+ getCameraSize,
8848
9011
 
8849
- /** converts a Vector2 to a box2 vec2
8850
- * @param {Vector2} v */
8851
- vec2dTo(v)
8852
- {
8853
- ASSERT(v instanceof Vector2);
8854
- return new box2d.instance.b2Vec2(v.x, v.y);
8855
- }
9012
+ // WebGL
9013
+ glCanvas,
9014
+ glContext,
9015
+ glCompileShader,
9016
+ glCopyToContext,
9017
+ glCreateProgram,
9018
+ glCreateTexture,
9019
+ glSetTextureData,
9020
+ glDraw,
9021
+ glFlush,
9022
+ glSetTexture,
9023
+ glSetAntialias,
9024
+ glClearCanvas,
9025
+ glAntialias,
9026
+ glShader,
9027
+ glActiveTexture,
9028
+ glArrayBuffer,
9029
+ glGeometryBuffer,
9030
+ glPositionData,
9031
+ glColorData,
9032
+ glInstanceCount,
9033
+ glAdditive,
9034
+ glBatchAdditive,
8856
9035
 
8857
- /** checks if a box2d object is null
8858
- * @param {Object} o */
8859
- isNull(o) { return !box2d.instance.getPointer(o); }
9036
+ // Input
9037
+ keyIsDown,
9038
+ keyWasPressed,
9039
+ keyWasReleased,
9040
+ keyDirection,
9041
+ clearInput,
9042
+ mouseIsDown,
9043
+ mouseWasPressed,
9044
+ mouseWasReleased,
9045
+ mousePos,
9046
+ mousePosScreen,
9047
+ mouseWheel,
9048
+ isUsingGamepad,
9049
+ inputPreventDefault,
9050
+ setInputPreventDefault,
9051
+ gamepadIsDown,
9052
+ gamepadWasPressed,
9053
+ gamepadWasReleased,
9054
+ gamepadStick,
9055
+ gamepadsUpdate,
9056
+ vibrate,
9057
+ vibrateStop,
9058
+ isTouchDevice,
8860
9059
 
8861
- /** casts a box2d object to its correct type
8862
- * @param {Object} o */
8863
- castObjectType(o)
8864
- {
8865
- switch (o.GetType())
8866
- {
8867
- case box2d.instance.b2Shape.e_circle:
8868
- return box2d.instance.castObject(o, box2d.instance.b2CircleShape);
8869
- case box2d.instance.b2Shape.e_edge:
8870
- return box2d.instance.castObject(o, box2d.instance.b2EdgeShape);
8871
- case box2d.instance.b2Shape.e_polygon:
8872
- return box2d.instance.castObject(o, box2d.instance.b2PolygonShape);
8873
- case box2d.instance.b2Shape.e_chain:
8874
- return box2d.instance.castObject(o, box2d.instance.b2ChainShape);
8875
- case box2d.instance.e_revoluteJoint:
8876
- return box2d.instance.castObject(o, box2d.instance.b2RevoluteJoint);
8877
- case box2d.instance.e_prismaticJoint:
8878
- return box2d.instance.castObject(o, box2d.instance.b2PrismaticJoint);
8879
- case box2d.instance.e_distanceJoint:
8880
- return box2d.instance.castObject(o, box2d.instance.b2DistanceJoint);
8881
- case box2d.instance.e_pulleyJoint:
8882
- return box2d.instance.castObject(o, box2d.instance.b2PulleyJoint);
8883
- case box2d.instance.e_mouseJoint:
8884
- return box2d.instance.castObject(o, box2d.instance.b2MouseJoint);
8885
- case box2d.instance.e_gearJoint:
8886
- return box2d.instance.castObject(o, box2d.instance.b2GearJoint);
8887
- case box2d.instance.e_wheelJoint:
8888
- return box2d.instance.castObject(o, box2d.instance.b2WheelJoint);
8889
- case box2d.instance.e_weldJoint:
8890
- return box2d.instance.castObject(o, box2d.instance.b2WeldJoint);
8891
- case box2d.instance.e_frictionJoint:
8892
- return box2d.instance.castObject(o, box2d.instance.b2FrictionJoint);
8893
- case box2d.instance.e_ropeJoint:
8894
- return box2d.instance.castObject(o, box2d.instance.b2RopeJoint);
8895
- case box2d.instance.e_motorJoint:
8896
- return box2d.instance.castObject(o, box2d.instance.b2MotorJoint);
8897
- }
8898
-
8899
- ASSERT(false, 'Unknown box2d object type');
8900
- }
8901
- }
9060
+ // Audio
9061
+ Sound,
9062
+ SoundWave,
9063
+ playAudioFile,
9064
+ speak,
9065
+ speakStop,
9066
+ getNoteFrequency,
9067
+ playSamples,
9068
+ zzfx,
9069
+ zzfxG,
9070
+ zzfxR,
9071
+ audioContext,
8902
9072
 
8903
- ///////////////////////////////////////////////////////////////////////////////
8904
- /** Box2d Init - Startup LittleJS engine with your callback functions
8905
- * @param {Function|function():Promise} gameInit - Called once after the engine starts up
8906
- * @param {Function} gameUpdate - Called every frame before objects are updated
8907
- * @param {Function} gameUpdatePost - Called after physics and objects are updated, even when paused
8908
- * @param {Function} gameRender - Called before objects are rendered, for drawing the background
8909
- * @param {Function} gameRenderPost - Called after objects are rendered, useful for drawing UI
8910
- * @param {Array<string>} [imageSources=[]] - List of images to load
8911
- * @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default */
8912
- function box2dEngineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources, rootElement)
8913
- {
8914
- Box2D().then(box2dInstance=>
8915
- {
8916
- // create box2d object
8917
- new Box2dPlugin(box2dInstance);
8918
- setupDebugDraw();
9073
+ // Base Object
9074
+ EngineObject,
8919
9075
 
8920
- // start littlejs
8921
- engineAddPlugin(box2dUpdate, box2dRender);
8922
- engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources, rootElement);
8923
- });
9076
+ // Tiles
9077
+ tileCollisionLayers,
9078
+ getTileCollisionData,
9079
+ tileCollisionTest,
9080
+ tileCollisionRaycast,
9081
+ TileLayerData,
9082
+ TileLayer,
9083
+ TileCollisionLayer,
8924
9084
 
8925
- // hook up box2d plugin to update and render
8926
- function box2dUpdate()
8927
- {
8928
- if (!paused)
8929
- box2d.step();
8930
- }
8931
- function box2dRender()
8932
- {
8933
- if (box2dDebug || debugPhysics && debugOverlay)
8934
- box2d.world.DrawDebugData();
8935
- }
8936
-
8937
- // box2d debug drawing
8938
- function setupDebugDraw()
8939
- {
8940
- // setup debug draw
8941
- const debugDraw = new box2d.instance.JSDraw();
8942
- const box2dColor = (c)=> new Color(c.get_r(), c.get_g(), c.get_b());
8943
- const box2dColorPointer = (c)=>
8944
- box2dColor(box2d.instance.wrapPointer(c, box2d.instance.b2Color));
8945
- const getDebugColor = (color)=>box2dColorPointer(color).scale(1,.8);
8946
- const getPointsList = (vertices, vertexCount) =>
8947
- {
8948
- const points = [];
8949
- for (let i=vertexCount; i--;)
8950
- points.push(box2d.vec2FromPointer(vertices+i*8));
8951
- return points;
8952
- }
8953
- debugDraw.DrawSegment = function(point1, point2, color)
8954
- {
8955
- color = getDebugColor(color);
8956
- point1 = box2d.vec2FromPointer(point1);
8957
- point2 = box2d.vec2FromPointer(point2);
8958
- box2d.drawLine(vec2(), 0, point1, point2, color, undefined, overlayContext);
8959
- };
8960
- debugDraw.DrawPolygon = function(vertices, vertexCount, color)
8961
- {
8962
- color = getDebugColor(color);
8963
- const points = getPointsList(vertices, vertexCount);
8964
- box2d.drawPoly(vec2(), 0, points, undefined, color, undefined, overlayContext);
8965
- };
8966
- debugDraw.DrawSolidPolygon = function(vertices, vertexCount, color)
8967
- {
8968
- color = getDebugColor(color);
8969
- const points = getPointsList(vertices, vertexCount);
8970
- box2d.drawPoly(vec2(), 0, points, color, color, undefined, overlayContext);
8971
- };
8972
- debugDraw.DrawCircle = function(center, radius, color)
8973
- {
8974
- color = getDebugColor(color);
8975
- center = box2d.vec2FromPointer(center);
8976
- box2d.drawCircle(center, radius, undefined, color, undefined, overlayContext);
8977
- };
8978
- debugDraw.DrawSolidCircle = function(center, radius, axis, color)
8979
- {
8980
- color = getDebugColor(color);
8981
- center = box2d.vec2FromPointer(center);
8982
- axis = box2d.vec2FromPointer(axis).scale(radius);
8983
- box2d.drawCircle(center, radius, color, color, undefined, overlayContext);
8984
- box2d.drawLine(center, 0, vec2(), axis, color, undefined, overlayContext);
8985
- };
8986
- debugDraw.DrawTransform = function(transform)
8987
- {
8988
- transform = box2d.instance.wrapPointer(transform, box2d.instance.b2Transform);
8989
- const pos = vec2(transform.get_p());
8990
- const angle = -transform.get_q().GetAngle();
8991
- const p1 = vec2(1,0), c1 = rgb(.75,0,0,.8);
8992
- const p2 = vec2(0,1), c2 = rgb(0,.75,0,.8);
8993
- box2d.drawLine(pos, angle, vec2(), p1, c1, undefined, overlayContext);
8994
- box2d.drawLine(pos, angle, vec2(), p2, c2, undefined, overlayContext);
8995
- }
8996
-
8997
- debugDraw.AppendFlags(box2d.instance.b2Draw.e_shapeBit);
8998
- debugDraw.AppendFlags(box2d.instance.b2Draw.e_jointBit);
8999
- //debugDraw.AppendFlags(box2d.instance.b2Draw.e_aabbBit);
9000
- //debugDraw.AppendFlags(box2d.instance.b2Draw.e_pairBit);
9001
- //debugDraw.AppendFlags(box2d.instance.b2Draw.e_centerOfMassBit);
9002
- box2d.world.SetDebugDraw(debugDraw);
9003
- }
9004
- }
9085
+ // Particles
9086
+ ParticleEmitter,
9087
+ Particle,
9088
+
9089
+ // Medals
9090
+ medals,
9091
+ medalsPreventUnlock,
9092
+ medalsInit,
9093
+ Medal,
9094
+ };
9005
9095
 
9006
9096
  /**
9007
9097
  * LittleJS Module Plugins Export
9008
9098
  */
9009
9099
 
9010
- 'use strict';
9011
-
9012
9100
  export
9013
9101
  {
9014
9102
  // Newgrounds
@@ -9036,7 +9124,8 @@ export
9036
9124
  // Box2D Physics
9037
9125
  box2d,
9038
9126
  box2dDebug,
9039
- box2dEngineInit,
9127
+ box2dSetDebug,
9128
+ box2dInit,
9040
9129
  Box2dPlugin,
9041
9130
  Box2dObject,
9042
9131
  Box2dRaycastResult,