littlejsengine 1.10.2 → 1.10.7
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.
- package/README.md +15 -93
- package/dist/littlejs.d.ts +79 -17
- package/dist/littlejs.esm.js +179 -90
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +165 -90
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +165 -90
- package/examples/box2d/game.js +15 -1
- package/examples/box2d/gameObjects.js +10 -9
- package/examples/box2d/index.html +5 -5
- package/examples/box2d/scenes.js +4 -4
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/index.html +3 -3
- package/examples/breakoutTutorial/README.md +9 -7
- package/examples/breakoutTutorial/game.js +6 -6
- package/examples/breakoutTutorial/index.html +2 -2
- package/examples/electron/game.js +3 -0
- package/examples/electron/index.html +2 -2
- package/examples/htmlMenu/index.html +3 -3
- package/examples/js13k/game.js +3 -0
- package/examples/js13k/index.html +13 -13
- package/examples/module/game.js +3 -0
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +4 -1
- package/examples/platformer/game.js +21 -15
- package/examples/platformer/gameCharacter.js +15 -14
- package/examples/platformer/gameEffects.js +3 -4
- package/examples/platformer/gameLevel.js +18 -33
- package/examples/platformer/gameObjects.js +9 -14
- package/examples/platformer/index.html +8 -8
- package/examples/platformer/tiles.png +0 -0
- package/examples/puzzle/index.html +2 -2
- package/examples/starter/game.js +3 -0
- package/examples/starter/index.html +13 -13
- package/examples/stress/index.html +1 -1
- package/examples/typescript/game.js +2 -0
- package/examples/typescript/game.ts +4 -1
- package/examples/typescript/index.html +1 -1
- package/examples/uiSystem/game.js +23 -16
- package/examples/uiSystem/index.html +3 -14
- package/package.json +1 -1
- package/plugins/postProcess.js +1 -1
- package/plugins/uiSystem.js +84 -24
- package/src/engine.js +14 -22
- package/src/engineAudio.js +7 -13
- package/src/engineDraw.js +20 -19
- package/src/engineExport.js +14 -0
- package/src/engineInput.js +8 -6
- package/src/engineMedals.js +19 -5
- package/src/engineObject.js +9 -5
- package/src/engineTileLayer.js +10 -8
- package/src/engineUtilities.js +60 -10
- package/src/engineWebGL.js +17 -2
package/dist/littlejs.js
CHANGED
|
@@ -671,7 +671,8 @@ function formatTime(t) { return (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0); }
|
|
|
671
671
|
* @memberof Random */
|
|
672
672
|
function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valueB); }
|
|
673
673
|
|
|
674
|
-
/** Returns a floored random value the two values passed in
|
|
674
|
+
/** Returns a floored random value between the two values passed in
|
|
675
|
+
* The upper bound is exclusive. (If 2 is passed in, result will be 0 or 1)
|
|
675
676
|
* @param {Number} valueA
|
|
676
677
|
* @param {Number} [valueB]
|
|
677
678
|
* @return {Number}
|
|
@@ -800,18 +801,24 @@ class Vector2
|
|
|
800
801
|
* @param {Number} [y] - Y axis location */
|
|
801
802
|
constructor(x=0, y=0)
|
|
802
803
|
{
|
|
803
|
-
ASSERT(typeof x == 'number' && typeof y == 'number');
|
|
804
804
|
/** @property {Number} - X axis location */
|
|
805
805
|
this.x = x;
|
|
806
806
|
/** @property {Number} - Y axis location */
|
|
807
807
|
this.y = y;
|
|
808
|
+
ASSERT(this.isValid());
|
|
808
809
|
}
|
|
809
810
|
|
|
810
811
|
/** Sets values of this vector and returns self
|
|
811
812
|
* @param {Number} [x] - X axis location
|
|
812
813
|
* @param {Number} [y] - Y axis location
|
|
813
814
|
* @return {Vector2} */
|
|
814
|
-
set(x=0, y=0)
|
|
815
|
+
set(x=0, y=0)
|
|
816
|
+
{
|
|
817
|
+
this.x = x;
|
|
818
|
+
this.y = y;
|
|
819
|
+
ASSERT(this.isValid());
|
|
820
|
+
return this;
|
|
821
|
+
}
|
|
815
822
|
|
|
816
823
|
/** Returns a new vector that is a copy of this
|
|
817
824
|
* @return {Vector2} */
|
|
@@ -953,6 +960,7 @@ class Vector2
|
|
|
953
960
|
* @param {Number} [length] */
|
|
954
961
|
setDirection(direction, length=1)
|
|
955
962
|
{
|
|
963
|
+
direction = mod(direction, 4);
|
|
956
964
|
ASSERT(direction==0 || direction==1 || direction==2 || direction==3);
|
|
957
965
|
return vec2(direction%2 ? direction-1 ? -length : length : 0,
|
|
958
966
|
direction%2 ? 0 : direction ? -length : length);
|
|
@@ -1002,6 +1010,14 @@ class Vector2
|
|
|
1002
1010
|
if (debug)
|
|
1003
1011
|
return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`;
|
|
1004
1012
|
}
|
|
1013
|
+
|
|
1014
|
+
/** Checks if this is a valid vector
|
|
1015
|
+
* @return {Boolean} */
|
|
1016
|
+
isValid()
|
|
1017
|
+
{
|
|
1018
|
+
return typeof this.x == 'number' && !isNaN(this.x)
|
|
1019
|
+
&& typeof this.y == 'number' && !isNaN(this.y);
|
|
1020
|
+
}
|
|
1005
1021
|
}
|
|
1006
1022
|
|
|
1007
1023
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -1062,6 +1078,7 @@ class Color
|
|
|
1062
1078
|
this.b = b;
|
|
1063
1079
|
/** @property {Number} - Alpha */
|
|
1064
1080
|
this.a = a;
|
|
1081
|
+
ASSERT(this.isValid());
|
|
1065
1082
|
}
|
|
1066
1083
|
|
|
1067
1084
|
/** Sets values of this color and returns self
|
|
@@ -1071,7 +1088,14 @@ class Color
|
|
|
1071
1088
|
* @param {Number} [a] - alpha
|
|
1072
1089
|
* @return {Color} */
|
|
1073
1090
|
set(r=1, g=1, b=1, a=1)
|
|
1074
|
-
{
|
|
1091
|
+
{
|
|
1092
|
+
this.r = r;
|
|
1093
|
+
this.g = g;
|
|
1094
|
+
this.b = b;
|
|
1095
|
+
this.a = a;
|
|
1096
|
+
ASSERT(this.isValid());
|
|
1097
|
+
return this;
|
|
1098
|
+
}
|
|
1075
1099
|
|
|
1076
1100
|
/** Returns a new color that is a copy of this
|
|
1077
1101
|
* @return {Color} */
|
|
@@ -1154,6 +1178,7 @@ class Color
|
|
|
1154
1178
|
this.g = f(p, q, h);
|
|
1155
1179
|
this.b = f(p, q, h - 1/3);
|
|
1156
1180
|
this.a = a;
|
|
1181
|
+
ASSERT(this.isValid());
|
|
1157
1182
|
return this;
|
|
1158
1183
|
}
|
|
1159
1184
|
|
|
@@ -1181,7 +1206,6 @@ class Color
|
|
|
1181
1206
|
else if (b == max)
|
|
1182
1207
|
h = (r - g) / d + 4;
|
|
1183
1208
|
}
|
|
1184
|
-
|
|
1185
1209
|
return [h / 6, s, l, a];
|
|
1186
1210
|
}
|
|
1187
1211
|
|
|
@@ -1214,11 +1238,27 @@ class Color
|
|
|
1214
1238
|
* @return {Color} */
|
|
1215
1239
|
setHex(hex)
|
|
1216
1240
|
{
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1241
|
+
ASSERT(typeof hex == 'string' && hex[0] == '#');
|
|
1242
|
+
ASSERT([4,5,7,9].includes(hex.length), 'Invalid hex');
|
|
1243
|
+
|
|
1244
|
+
if (hex.length < 6)
|
|
1245
|
+
{
|
|
1246
|
+
const fromHex = (c)=> clamp(parseInt(hex[c],16)/15);
|
|
1247
|
+
this.r = fromHex(1);
|
|
1248
|
+
this.g = fromHex(2),
|
|
1249
|
+
this.b = fromHex(3);
|
|
1250
|
+
this.a = hex.length == 5 ? fromHex(4) : 1;
|
|
1251
|
+
}
|
|
1252
|
+
else
|
|
1253
|
+
{
|
|
1254
|
+
const fromHex = (c)=> clamp(parseInt(hex.slice(c,c+2),16)/255);
|
|
1255
|
+
this.r = fromHex(1);
|
|
1256
|
+
this.g = fromHex(3),
|
|
1257
|
+
this.b = fromHex(5);
|
|
1258
|
+
this.a = hex.length == 9 ? fromHex(7) : 1;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
ASSERT(this.isValid());
|
|
1222
1262
|
return this;
|
|
1223
1263
|
}
|
|
1224
1264
|
|
|
@@ -1232,6 +1272,16 @@ class Color
|
|
|
1232
1272
|
const a = clamp(this.a)*255<<24;
|
|
1233
1273
|
return r + g + b + a;
|
|
1234
1274
|
}
|
|
1275
|
+
|
|
1276
|
+
/** Checks if this is a valid color
|
|
1277
|
+
* @return {Boolean} */
|
|
1278
|
+
isValid()
|
|
1279
|
+
{
|
|
1280
|
+
return typeof this.r == 'number' && !isNaN(this.r)
|
|
1281
|
+
&& typeof this.g == 'number' && !isNaN(this.g)
|
|
1282
|
+
&& typeof this.b == 'number' && !isNaN(this.b)
|
|
1283
|
+
&& typeof this.a == 'number' && !isNaN(this.a);
|
|
1284
|
+
}
|
|
1235
1285
|
}
|
|
1236
1286
|
|
|
1237
1287
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -1867,7 +1917,7 @@ class EngineObject
|
|
|
1867
1917
|
* @param {Color} [color=(1,1,1,1)] - Color to apply to tile when rendered
|
|
1868
1918
|
* @param {Number} [renderOrder] - Objects sorted by renderOrder before being rendered
|
|
1869
1919
|
*/
|
|
1870
|
-
constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color, renderOrder=0)
|
|
1920
|
+
constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color=new Color, renderOrder=0)
|
|
1871
1921
|
{
|
|
1872
1922
|
// set passed in params
|
|
1873
1923
|
ASSERT(isVector2(pos) && isVector2(size), 'ensure pos and size are vec2s');
|
|
@@ -1981,15 +2031,18 @@ class EngineObject
|
|
|
1981
2031
|
|
|
1982
2032
|
// apply physics
|
|
1983
2033
|
const oldPos = this.pos.copy();
|
|
1984
|
-
this.
|
|
1985
|
-
this.
|
|
1986
|
-
|
|
2034
|
+
this.velocity.x *= this.damping;
|
|
2035
|
+
this.velocity.y *= this.damping;
|
|
2036
|
+
if (this.mass) // dont apply gravity to static objects
|
|
2037
|
+
this.velocity.y += gravity * this.gravityScale;
|
|
2038
|
+
this.pos.x += this.velocity.x;
|
|
2039
|
+
this.pos.y += this.velocity.y;
|
|
1987
2040
|
this.angle += this.angleVelocity *= this.angleDamping;
|
|
1988
2041
|
|
|
1989
2042
|
// physics sanity checks
|
|
1990
2043
|
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
1991
2044
|
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
1992
|
-
if (!enablePhysicsSolver || !this.mass) // dont do collision for
|
|
2045
|
+
if (!enablePhysicsSolver || !this.mass) // dont do collision for static objects
|
|
1993
2046
|
return;
|
|
1994
2047
|
|
|
1995
2048
|
const wasMovingDown = this.velocity.y < 0;
|
|
@@ -2135,6 +2188,7 @@ class EngineObject
|
|
|
2135
2188
|
this.pos.x = oldPos.x;
|
|
2136
2189
|
this.velocity.x *= -this.elasticity;
|
|
2137
2190
|
}
|
|
2191
|
+
debugOverlay && debugPhysics && debugRect(this.pos, this.size, '#f00');
|
|
2138
2192
|
}
|
|
2139
2193
|
}
|
|
2140
2194
|
}
|
|
@@ -2349,7 +2403,7 @@ let drawCount;
|
|
|
2349
2403
|
* tile(2) // a tile at index 2 using the default tile size of 16
|
|
2350
2404
|
* tile(5, 8) // a tile at index 5 using a tile size of 8
|
|
2351
2405
|
* tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
|
|
2352
|
-
* tile(vec2(4,8), vec2(30,10)) // a tile at
|
|
2406
|
+
* tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
|
|
2353
2407
|
* @memberof Draw
|
|
2354
2408
|
*/
|
|
2355
2409
|
function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
|
|
@@ -2567,6 +2621,22 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
2567
2621
|
drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
|
|
2568
2622
|
}
|
|
2569
2623
|
|
|
2624
|
+
/** Draw colored line between two points
|
|
2625
|
+
* @param {Vector2} posA
|
|
2626
|
+
* @param {Vector2} posB
|
|
2627
|
+
* @param {Number} [thickness]
|
|
2628
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
2629
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
2630
|
+
* @param {Boolean} [screenSpace=false]
|
|
2631
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
2632
|
+
* @memberof Draw */
|
|
2633
|
+
function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
|
|
2634
|
+
{
|
|
2635
|
+
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
2636
|
+
const size = vec2(thickness, halfDelta.length()*2);
|
|
2637
|
+
drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), useWebGL, screenSpace, context);
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2570
2640
|
/** Draw colored polygon using passed in points
|
|
2571
2641
|
* @param {Array} points - Array of Vector2 points
|
|
2572
2642
|
* @param {Color} [color=(1,1,1,1)]
|
|
@@ -2635,22 +2705,6 @@ function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth
|
|
|
2635
2705
|
function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
|
|
2636
2706
|
{ drawEllipse(pos, radius, radius, 0, color, lineWidth, lineColor, screenSpace, context); }
|
|
2637
2707
|
|
|
2638
|
-
/** Draw colored line between two points
|
|
2639
|
-
* @param {Vector2} posA
|
|
2640
|
-
* @param {Vector2} posB
|
|
2641
|
-
* @param {Number} [thickness]
|
|
2642
|
-
* @param {Color} [color=(1,1,1,1)]
|
|
2643
|
-
* @param {Boolean} [useWebGL=glEnable]
|
|
2644
|
-
* @param {Boolean} [screenSpace=false]
|
|
2645
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
2646
|
-
* @memberof Draw */
|
|
2647
|
-
function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
|
|
2648
|
-
{
|
|
2649
|
-
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
2650
|
-
const size = vec2(thickness, halfDelta.length()*2);
|
|
2651
|
-
drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), useWebGL, screenSpace, context);
|
|
2652
|
-
}
|
|
2653
|
-
|
|
2654
2708
|
/** Draw directly to a 2d canvas context in world space
|
|
2655
2709
|
* @param {Vector2} pos
|
|
2656
2710
|
* @param {Vector2} size
|
|
@@ -2842,13 +2896,14 @@ function isFullscreen() { return !!document.fullscreenElement; }
|
|
|
2842
2896
|
* @memberof Draw */
|
|
2843
2897
|
function toggleFullscreen()
|
|
2844
2898
|
{
|
|
2899
|
+
const rootElement = mainCanvas.parentElement;
|
|
2845
2900
|
if (isFullscreen())
|
|
2846
2901
|
{
|
|
2847
2902
|
if (document.exitFullscreen)
|
|
2848
2903
|
document.exitFullscreen();
|
|
2849
2904
|
}
|
|
2850
|
-
else if (
|
|
2851
|
-
|
|
2905
|
+
else if (rootElement.requestFullscreen)
|
|
2906
|
+
rootElement.requestFullscreen();
|
|
2852
2907
|
}
|
|
2853
2908
|
/**
|
|
2854
2909
|
* LittleJS Input System
|
|
@@ -2897,7 +2952,7 @@ function keyWasReleased(key, device=0)
|
|
|
2897
2952
|
|
|
2898
2953
|
/** Clears all input
|
|
2899
2954
|
* @memberof Input */
|
|
2900
|
-
function clearInput() { inputData = [[]]; }
|
|
2955
|
+
function clearInput() { inputData = [[]]; touchGamepadButtons = []; }
|
|
2901
2956
|
|
|
2902
2957
|
/** Returns true if mouse button is down
|
|
2903
2958
|
* @function
|
|
@@ -3019,7 +3074,6 @@ function inputInit()
|
|
|
3019
3074
|
|
|
3020
3075
|
onkeydown = (e)=>
|
|
3021
3076
|
{
|
|
3022
|
-
if (debug && e.target != engineRoot) return;
|
|
3023
3077
|
if (!e.repeat)
|
|
3024
3078
|
{
|
|
3025
3079
|
isUsingGamepad = false;
|
|
@@ -3032,7 +3086,6 @@ function inputInit()
|
|
|
3032
3086
|
|
|
3033
3087
|
onkeyup = (e)=>
|
|
3034
3088
|
{
|
|
3035
|
-
if (debug && e.target != engineRoot) return;
|
|
3036
3089
|
inputData[0][e.code] = 4;
|
|
3037
3090
|
if (inputWASDEmulateDirection)
|
|
3038
3091
|
inputData[0][remapKey(e.code)] = 4;
|
|
@@ -3064,6 +3117,7 @@ function inputInit()
|
|
|
3064
3117
|
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
3065
3118
|
onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
|
|
3066
3119
|
oncontextmenu = (e)=> false; // prevent right click menu
|
|
3120
|
+
onblur = (e) => clearInput(); // reset input when focus is lost
|
|
3067
3121
|
|
|
3068
3122
|
// init touch input
|
|
3069
3123
|
if (isTouchDevice && touchInputEnable)
|
|
@@ -3234,7 +3288,7 @@ function touchInputInit()
|
|
|
3234
3288
|
// set event pos and pass it along
|
|
3235
3289
|
const p = vec2(e.touches[0].clientX, e.touches[0].clientY);
|
|
3236
3290
|
mousePosScreen = mouseToScreen(p);
|
|
3237
|
-
wasTouching ? isUsingGamepad =
|
|
3291
|
+
wasTouching ? isUsingGamepad = touchGamepadEnable : inputData[0][button] = 3;
|
|
3238
3292
|
}
|
|
3239
3293
|
else if (wasTouching)
|
|
3240
3294
|
inputData[0][button] = inputData[0][button] & 2 | 4;
|
|
@@ -3262,10 +3316,13 @@ function touchInputInit()
|
|
|
3262
3316
|
if (touching)
|
|
3263
3317
|
{
|
|
3264
3318
|
touchGamepadTimer.set();
|
|
3265
|
-
if (paused)
|
|
3319
|
+
if (paused && !wasTouching)
|
|
3266
3320
|
{
|
|
3267
3321
|
// touch anywhere to press start when paused
|
|
3268
3322
|
touchGamepadButtons[9] = 1;
|
|
3323
|
+
|
|
3324
|
+
// call default touch handler so normal touch events still work
|
|
3325
|
+
handleTouchDefault(e);
|
|
3269
3326
|
return;
|
|
3270
3327
|
}
|
|
3271
3328
|
}
|
|
@@ -3290,7 +3347,7 @@ function touchInputInit()
|
|
|
3290
3347
|
const button = touchPos.subtract(buttonCenter).direction();
|
|
3291
3348
|
touchGamepadButtons[button] = 1;
|
|
3292
3349
|
}
|
|
3293
|
-
else if (touchPos.distance(startCenter) < touchGamepadSize)
|
|
3350
|
+
else if (touchPos.distance(startCenter) < touchGamepadSize && !wasTouching)
|
|
3294
3351
|
{
|
|
3295
3352
|
// virtual start button in center
|
|
3296
3353
|
touchGamepadButtons[9] = 1;
|
|
@@ -3378,7 +3435,7 @@ function touchGamepadRender()
|
|
|
3378
3435
|
/** Audio context used by the engine
|
|
3379
3436
|
* @type {AudioContext}
|
|
3380
3437
|
* @memberof Audio */
|
|
3381
|
-
let audioContext;
|
|
3438
|
+
let audioContext = new AudioContext;
|
|
3382
3439
|
|
|
3383
3440
|
/** Master gain node for all audio to pass through
|
|
3384
3441
|
* @type {GainNode}
|
|
@@ -3389,14 +3446,10 @@ function audioInit()
|
|
|
3389
3446
|
{
|
|
3390
3447
|
if (!soundEnable || headlessMode) return;
|
|
3391
3448
|
|
|
3392
|
-
// create audio context
|
|
3393
|
-
audioContext = new AudioContext;
|
|
3394
|
-
|
|
3395
|
-
// create and connect gain node
|
|
3396
3449
|
// (createGain is more widely spported then GainNode construtor)
|
|
3397
3450
|
audioGainNode = audioContext.createGain();
|
|
3398
3451
|
audioGainNode.connect(audioContext.destination);
|
|
3399
|
-
|
|
3452
|
+
audioGainNode.gain.value = soundVolume; // set starting value
|
|
3400
3453
|
}
|
|
3401
3454
|
|
|
3402
3455
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -3431,12 +3484,15 @@ class Sound
|
|
|
3431
3484
|
|
|
3432
3485
|
/** @property {Number} - How much to randomize frequency each time sound plays */
|
|
3433
3486
|
this.randomness = 0;
|
|
3487
|
+
|
|
3488
|
+
/** @property {GainNode} - Gain node for this sound */
|
|
3489
|
+
this.gainNode = audioContext.createGain();
|
|
3434
3490
|
|
|
3435
3491
|
if (zzfxSound)
|
|
3436
3492
|
{
|
|
3437
3493
|
// generate zzfx sound now for fast playback
|
|
3438
3494
|
const defaultRandomness = .05;
|
|
3439
|
-
this.randomness = zzfxSound[1]
|
|
3495
|
+
this.randomness = zzfxSound[1] != undefined ? zzfxSound[1] : defaultRandomness;
|
|
3440
3496
|
zzfxSound[1] = 0; // generate without randomness
|
|
3441
3497
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
3442
3498
|
this.sampleRate = zzfxR;
|
|
@@ -3477,18 +3533,13 @@ class Sound
|
|
|
3477
3533
|
|
|
3478
3534
|
// play the sound
|
|
3479
3535
|
const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
|
|
3480
|
-
this.gainNode = audioContext.createGain();
|
|
3481
3536
|
return this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate, this.gainNode);
|
|
3482
3537
|
}
|
|
3483
3538
|
|
|
3484
3539
|
/** Set the sound volume
|
|
3485
3540
|
* @param {Number} [volume] - How much to scale volume by
|
|
3486
3541
|
*/
|
|
3487
|
-
setVolume(volume=1)
|
|
3488
|
-
{
|
|
3489
|
-
if (this.gainNode)
|
|
3490
|
-
this.gainNode.gain.value = volume;
|
|
3491
|
-
}
|
|
3542
|
+
setVolume(volume=1) { this.gainNode.gain.value = volume; }
|
|
3492
3543
|
|
|
3493
3544
|
/** Stop the last instance of this sound that was played */
|
|
3494
3545
|
stop()
|
|
@@ -3972,18 +4023,18 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
3972
4023
|
|
|
3973
4024
|
|
|
3974
4025
|
|
|
3975
|
-
/** The tile collision layer
|
|
4026
|
+
/** The tile collision layer grid, use setTileCollisionData and getTileCollisionData to access
|
|
3976
4027
|
* @type {Array}
|
|
3977
4028
|
* @memberof TileCollision */
|
|
3978
4029
|
let tileCollision = [];
|
|
3979
4030
|
|
|
3980
|
-
/** Size of the tile collision layer
|
|
4031
|
+
/** Size of the tile collision layer 2d grid
|
|
3981
4032
|
* @type {Vector2}
|
|
3982
4033
|
* @memberof TileCollision */
|
|
3983
4034
|
let tileCollisionSize = vec2();
|
|
3984
4035
|
|
|
3985
4036
|
/** Clear and initialize tile collision
|
|
3986
|
-
* @param {Vector2} size
|
|
4037
|
+
* @param {Vector2} size - width and height of tile collision 2d grid
|
|
3987
4038
|
* @memberof TileCollision */
|
|
3988
4039
|
function initTileCollision(size)
|
|
3989
4040
|
{
|
|
@@ -3993,7 +4044,7 @@ function initTileCollision(size)
|
|
|
3993
4044
|
tileCollision[i] = 0;
|
|
3994
4045
|
}
|
|
3995
4046
|
|
|
3996
|
-
/** Set tile collision data
|
|
4047
|
+
/** Set tile collision data for a given cell in the grid
|
|
3997
4048
|
* @param {Vector2} pos
|
|
3998
4049
|
* @param {Number} [data]
|
|
3999
4050
|
* @memberof TileCollision */
|
|
@@ -4002,7 +4053,7 @@ function setTileCollisionData(pos, data=0)
|
|
|
4002
4053
|
pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
|
|
4003
4054
|
}
|
|
4004
4055
|
|
|
4005
|
-
/** Get tile collision data
|
|
4056
|
+
/** Get tile collision data for a given cell in the grid
|
|
4006
4057
|
* @param {Vector2} pos
|
|
4007
4058
|
* @return {Number}
|
|
4008
4059
|
* @memberof TileCollision */
|
|
@@ -4030,9 +4081,11 @@ function tileCollisionTest(pos, size=vec2(), object)
|
|
|
4030
4081
|
if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
|
|
4031
4082
|
return true;
|
|
4032
4083
|
}
|
|
4084
|
+
return false;
|
|
4033
4085
|
}
|
|
4034
4086
|
|
|
4035
|
-
/** Return the center of first tile hit
|
|
4087
|
+
/** Return the center of first tile hit, undefined if nothing was hit.
|
|
4088
|
+
* This does not return the exact intersection, but the center of the tile hit.
|
|
4036
4089
|
* @param {Vector2} posStart
|
|
4037
4090
|
* @param {Vector2} posEnd
|
|
4038
4091
|
* @param {EngineObject} [object]
|
|
@@ -4053,7 +4106,7 @@ function tileCollisionRaycast(posStart, posEnd, object)
|
|
|
4053
4106
|
let xi = unit.x * (delta.x < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1);
|
|
4054
4107
|
let yi = unit.y * (delta.y < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1);
|
|
4055
4108
|
|
|
4056
|
-
while (
|
|
4109
|
+
while (true)
|
|
4057
4110
|
{
|
|
4058
4111
|
// check for tile collision
|
|
4059
4112
|
const tileData = getTileCollisionData(pos);
|
|
@@ -4277,8 +4330,8 @@ class TileLayer extends EngineObject
|
|
|
4277
4330
|
const d = this.getData(layerPos);
|
|
4278
4331
|
if (d.tile != undefined)
|
|
4279
4332
|
{
|
|
4280
|
-
const pos = this.pos.add(layerPos).add(vec2(.5));
|
|
4281
4333
|
ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
|
|
4334
|
+
const pos = layerPos.add(vec2(.5));
|
|
4282
4335
|
const tileInfo = tile(d.tile, s, this.tileInfo.textureIndex);
|
|
4283
4336
|
drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
|
|
4284
4337
|
}
|
|
@@ -4707,7 +4760,7 @@ function medalsInit(saveName)
|
|
|
4707
4760
|
// check if medals are unlocked
|
|
4708
4761
|
medalsSaveName = saveName;
|
|
4709
4762
|
if (!debugMedals)
|
|
4710
|
-
medalsForEach(medal=> medal.unlocked =
|
|
4763
|
+
medalsForEach(medal=> medal.unlocked = !!localStorage[medal.storageKey()]);
|
|
4711
4764
|
|
|
4712
4765
|
// engine automatically renders medals
|
|
4713
4766
|
engineAddPlugin(undefined, medalsRender);
|
|
@@ -4770,14 +4823,28 @@ class Medal
|
|
|
4770
4823
|
constructor(id, name, description='', icon='🏆', src)
|
|
4771
4824
|
{
|
|
4772
4825
|
ASSERT(id >= 0 && !medals[id]);
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4826
|
+
|
|
4827
|
+
/** @property {Number} - The unique identifier of the medal */
|
|
4828
|
+
this.id = id;
|
|
4829
|
+
|
|
4830
|
+
/** @property {String} - Name of the medal */
|
|
4776
4831
|
this.name = name;
|
|
4832
|
+
|
|
4833
|
+
/** @property {String} - Description of the medal */
|
|
4777
4834
|
this.description = description;
|
|
4835
|
+
|
|
4836
|
+
/** @property {String} - Icon for the medal */
|
|
4778
4837
|
this.icon = icon;
|
|
4838
|
+
|
|
4839
|
+
/** @property {Boolean} - Is the medal unlocked? */
|
|
4840
|
+
this.unlocked = false;
|
|
4841
|
+
|
|
4842
|
+
// load the source image if provided
|
|
4779
4843
|
if (src)
|
|
4780
4844
|
(this.image = new Image).src = src;
|
|
4845
|
+
|
|
4846
|
+
// add this to list of medals
|
|
4847
|
+
medals[id] = this;
|
|
4781
4848
|
}
|
|
4782
4849
|
|
|
4783
4850
|
/** Unlocks a medal if not already unlocked */
|
|
@@ -4788,7 +4855,7 @@ class Medal
|
|
|
4788
4855
|
|
|
4789
4856
|
// save the medal
|
|
4790
4857
|
ASSERT(medalsSaveName, 'save name must be set');
|
|
4791
|
-
localStorage[this.storageKey()] = this.unlocked =
|
|
4858
|
+
localStorage[this.storageKey()] = this.unlocked = true;
|
|
4792
4859
|
medalsDisplayQueue.push(this);
|
|
4793
4860
|
}
|
|
4794
4861
|
|
|
@@ -4862,6 +4929,11 @@ let glCanvas;
|
|
|
4862
4929
|
* @memberof WebGL */
|
|
4863
4930
|
let glContext;
|
|
4864
4931
|
|
|
4932
|
+
/** Shoule webgl be setup with antialiasing, must be set before calling engineInit
|
|
4933
|
+
* @type {Boolean}
|
|
4934
|
+
* @memberof WebGL */
|
|
4935
|
+
let glAntialias = true;
|
|
4936
|
+
|
|
4865
4937
|
// WebGL internal variables not exposed to documentation
|
|
4866
4938
|
let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glInstanceCount, glAdditive, glBatchAdditive;
|
|
4867
4939
|
|
|
@@ -4874,10 +4946,11 @@ function glInit()
|
|
|
4874
4946
|
|
|
4875
4947
|
// create the canvas and textures
|
|
4876
4948
|
glCanvas = document.createElement('canvas');
|
|
4877
|
-
glContext = glCanvas.getContext('webgl2', {antialias
|
|
4949
|
+
glContext = glCanvas.getContext('webgl2', {antialias:glAntialias});
|
|
4878
4950
|
|
|
4879
4951
|
// some browsers are much faster without copying the gl buffer so we just overlay it instead
|
|
4880
|
-
|
|
4952
|
+
const rootElement = mainCanvas.parentElement;
|
|
4953
|
+
glOverlay && rootElement.appendChild(glCanvas);
|
|
4881
4954
|
|
|
4882
4955
|
// setup vertex and fragment shaders
|
|
4883
4956
|
glShader = glCreateProgram(
|
|
@@ -5081,6 +5154,15 @@ function glCopyToContext(context, forceDraw=false)
|
|
|
5081
5154
|
context.drawImage(glCanvas, 0, 0);
|
|
5082
5155
|
}
|
|
5083
5156
|
|
|
5157
|
+
/** Set antialiasing for webgl canvas
|
|
5158
|
+
* @param {Boolean} [antialias]
|
|
5159
|
+
* @memberof WebGL */
|
|
5160
|
+
function glSetAntialias(antialias=true)
|
|
5161
|
+
{
|
|
5162
|
+
ASSERT(!glCanvas, 'must be called before engineInit');
|
|
5163
|
+
glAntialias = antialias;
|
|
5164
|
+
}
|
|
5165
|
+
|
|
5084
5166
|
/** Add a sprite to the gl draw list, used by all gl draw functions
|
|
5085
5167
|
* @param {Number} x
|
|
5086
5168
|
* @param {Number} y
|
|
@@ -5181,7 +5263,7 @@ const engineName = 'LittleJS';
|
|
|
5181
5263
|
* @type {String}
|
|
5182
5264
|
* @default
|
|
5183
5265
|
* @memberof Engine */
|
|
5184
|
-
const engineVersion = '1.10.
|
|
5266
|
+
const engineVersion = '1.10.7';
|
|
5185
5267
|
|
|
5186
5268
|
/** Frames per second to update
|
|
5187
5269
|
* @type {Number}
|
|
@@ -5225,13 +5307,6 @@ let timeReal = 0;
|
|
|
5225
5307
|
* @default false
|
|
5226
5308
|
* @memberof Engine */
|
|
5227
5309
|
let paused = false;
|
|
5228
|
-
|
|
5229
|
-
/** The root element that engine is attached to
|
|
5230
|
-
* @type {HTMLElement}
|
|
5231
|
-
* @default document.body
|
|
5232
|
-
* @memberof Engine */
|
|
5233
|
-
let engineRoot;
|
|
5234
|
-
|
|
5235
5310
|
/** Set if game is paused
|
|
5236
5311
|
* @param {Boolean} isPaused
|
|
5237
5312
|
* @memberof Engine */
|
|
@@ -5251,6 +5326,8 @@ const pluginUpdateList = [], pluginRenderList = [];
|
|
|
5251
5326
|
* @memberof Engine */
|
|
5252
5327
|
function engineAddPlugin(updateFunction, renderFunction)
|
|
5253
5328
|
{
|
|
5329
|
+
ASSERT(!pluginUpdateList.includes(updateFunction));
|
|
5330
|
+
ASSERT(!pluginRenderList.includes(renderFunction));
|
|
5254
5331
|
updateFunction && pluginUpdateList.push(updateFunction);
|
|
5255
5332
|
renderFunction && pluginRenderList.push(renderFunction);
|
|
5256
5333
|
}
|
|
@@ -5259,12 +5336,12 @@ function engineAddPlugin(updateFunction, renderFunction)
|
|
|
5259
5336
|
// Main engine functions
|
|
5260
5337
|
|
|
5261
5338
|
/** Startup LittleJS engine with your callback functions
|
|
5262
|
-
* @param {Function} gameInit
|
|
5263
|
-
* @param {Function} gameUpdate
|
|
5264
|
-
* @param {Function} gameUpdatePost - Called after physics and objects are updated,
|
|
5265
|
-
* @param {Function} gameRender
|
|
5266
|
-
* @param {Function} gameRenderPost - Called after objects are rendered,
|
|
5267
|
-
* @param {Array} [imageSources=[
|
|
5339
|
+
* @param {Function|function():Promise} gameInit - Called once after the engine starts up
|
|
5340
|
+
* @param {Function} gameUpdate - Called every frame before objects are updated
|
|
5341
|
+
* @param {Function} gameUpdatePost - Called after physics and objects are updated, even when paused
|
|
5342
|
+
* @param {Function} gameRender - Called before objects are rendered, for drawing the background
|
|
5343
|
+
* @param {Function} gameRenderPost - Called after objects are rendered, useful for drawing UI
|
|
5344
|
+
* @param {Array} [imageSources=[]] - List of images to load
|
|
5268
5345
|
* @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
|
|
5269
5346
|
* @memberof Engine */
|
|
5270
5347
|
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
@@ -5414,8 +5491,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5414
5491
|
|
|
5415
5492
|
function startEngine()
|
|
5416
5493
|
{
|
|
5417
|
-
gameInit();
|
|
5418
|
-
engineUpdate();
|
|
5494
|
+
new Promise((resolve) => resolve(gameInit())).then(engineUpdate);
|
|
5419
5495
|
}
|
|
5420
5496
|
|
|
5421
5497
|
if (headlessMode)
|
|
@@ -5430,7 +5506,6 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5430
5506
|
'width:100vw;height:100vh;' + // fill the window
|
|
5431
5507
|
'display:flex;' + // use flexbox
|
|
5432
5508
|
'align-items:center;' + // horizontal center
|
|
5433
|
-
(canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
|
|
5434
5509
|
'justify-content:center;' + // vertical center
|
|
5435
5510
|
'background:#000;' + // set background color
|
|
5436
5511
|
'user-select:none;' + // prevent hold to select
|
|
@@ -5438,9 +5513,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5438
5513
|
(!touchInputEnable ? '' : // no touch css setttings
|
|
5439
5514
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
5440
5515
|
'-webkit-touch-callout:none');// compatibility for ios
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
engineRoot.appendChild(mainCanvas = document.createElement('canvas'));
|
|
5516
|
+
rootElement.style.cssText = styleRoot;
|
|
5517
|
+
rootElement.appendChild(mainCanvas = document.createElement('canvas'));
|
|
5444
5518
|
mainContext = mainCanvas.getContext('2d');
|
|
5445
5519
|
|
|
5446
5520
|
// init stuff and start engine
|
|
@@ -5450,7 +5524,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5450
5524
|
glInit();
|
|
5451
5525
|
|
|
5452
5526
|
// create overlay canvas for hud to appear above gl canvas
|
|
5453
|
-
|
|
5527
|
+
rootElement.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
5454
5528
|
overlayContext = overlayCanvas.getContext('2d');
|
|
5455
5529
|
|
|
5456
5530
|
// set canvas style
|
|
@@ -5769,4 +5843,5 @@ function drawEngineSplashScreen(t)
|
|
|
5769
5843
|
}
|
|
5770
5844
|
|
|
5771
5845
|
x.restore();
|
|
5772
|
-
}
|
|
5846
|
+
}
|
|
5847
|
+
|