littlejsengine 1.18.4 → 1.18.8
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/COPYRIGHT.txt +38 -0
- package/FAQ.md +633 -0
- package/README.md +19 -1
- package/dist/littlejs.d.ts +354 -55
- package/dist/littlejs.esm.js +1531 -489
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +1499 -476
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +1493 -470
- package/package.json +8 -1
- package/plugins/box2d.js +32 -7
- package/{src/engineMedals.js → plugins/medalSystem.js} +248 -196
- package/plugins/pathFinder.js +758 -0
- package/plugins/pluginExport.js +19 -0
- package/plugins/{tween.js → tweenSystem.js} +7 -7
- package/plugins/uiSystem.js +186 -25
- package/src/engine.js +2 -141
- package/src/engineAudio.js +3 -3
- package/src/engineBuild.mjs +4 -2
- package/src/engineDebug.js +7 -7
- package/src/engineDraw.js +26 -8
- package/src/engineExport.js +13 -13
- package/src/engineInput.js +8 -8
- package/src/engineLogo.js +146 -0
- package/src/engineMath.js +15 -6
- package/src/engineObject.js +11 -5
- package/src/engineParticles.js +6 -3
- package/src/engineRelease.js +1 -1
- package/src/engineSettings.js +0 -47
- package/src/engineUtilities.js +13 -8
- package/src/engineWebGL.js +34 -7
- package/src/jsconfig.json +3 -1
- package/.github/workflows/test.yml +0 -17
- package/AI.md +0 -173
- package/CLAUDE.md +0 -1
- package/examples/box2d/game.js +0 -192
- package/examples/box2d/gameObjects.js +0 -564
- package/examples/box2d/index.html +0 -9
- package/examples/box2d/scenes.js +0 -194
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/game.js +0 -175
- package/examples/breakout/gameObjects.js +0 -147
- package/examples/breakout/index.html +0 -8
- package/examples/breakout/tiles.png +0 -0
- package/examples/breakoutTutorial/README.md +0 -521
- package/examples/breakoutTutorial/game.js +0 -191
- package/examples/breakoutTutorial/images/1.png +0 -0
- package/examples/breakoutTutorial/images/10.png +0 -0
- package/examples/breakoutTutorial/images/11.png +0 -0
- package/examples/breakoutTutorial/images/2.png +0 -0
- package/examples/breakoutTutorial/images/3.png +0 -0
- package/examples/breakoutTutorial/images/4.png +0 -0
- package/examples/breakoutTutorial/images/5.png +0 -0
- package/examples/breakoutTutorial/images/6.png +0 -0
- package/examples/breakoutTutorial/images/7.png +0 -0
- package/examples/breakoutTutorial/images/8.png +0 -0
- package/examples/breakoutTutorial/images/9.png +0 -0
- package/examples/breakoutTutorial/index.html +0 -8
- package/examples/electron/build.mjs +0 -127
- package/examples/electron/electron.js +0 -35
- package/examples/electron/game.js +0 -54
- package/examples/electron/index.html +0 -13
- package/examples/electron/package.json +0 -5
- package/examples/electron/tiles.png +0 -0
- package/examples/empty/game.js +0 -51
- package/examples/empty/index.html +0 -5
- package/examples/empty/tiles.png +0 -0
- package/examples/favicon.png +0 -0
- package/examples/games.jpg +0 -0
- package/examples/htmlMenu/game.js +0 -82
- package/examples/htmlMenu/index.html +0 -45
- package/examples/htmlMenu/tiles.png +0 -0
- package/examples/index.html +0 -62
- package/examples/logo.png +0 -0
- package/examples/logo2.png +0 -0
- package/examples/module/build.mjs +0 -124
- package/examples/module/game.js +0 -132
- package/examples/module/index.html +0 -10
- package/examples/module/tiles.png +0 -0
- package/examples/particles/index.html +0 -426
- package/examples/particles/tiles.png +0 -0
- package/examples/platformer/data/gameLevelData.tmx +0 -143
- package/examples/platformer/data/gameLevelData.tsx +0 -4
- package/examples/platformer/game.js +0 -147
- package/examples/platformer/gameCharacter.js +0 -309
- package/examples/platformer/gameEffects.js +0 -278
- package/examples/platformer/gameLevel.js +0 -205
- package/examples/platformer/gameLevelData.json +0 -171
- package/examples/platformer/gameObjects.js +0 -378
- package/examples/platformer/gamePlayer.js +0 -35
- package/examples/platformer/index.html +0 -9
- package/examples/platformer/tiles.png +0 -0
- package/examples/platformer/tilesLevel.png +0 -0
- package/examples/puzzle/game.js +0 -331
- package/examples/puzzle/index.html +0 -8
- package/examples/puzzle/tiles.png +0 -0
- package/examples/screenshot.jpg +0 -0
- package/examples/shorts/animation.js +0 -18
- package/examples/shorts/base.html +0 -54
- package/examples/shorts/blending.js +0 -14
- package/examples/shorts/box2d.js +0 -51
- package/examples/shorts/box2dCar.js +0 -59
- package/examples/shorts/box2dPool.js +0 -107
- package/examples/shorts/box2dTileLayer.js +0 -48
- package/examples/shorts/cameraDrag.js +0 -22
- package/examples/shorts/clock.js +0 -21
- package/examples/shorts/colors.js +0 -25
- package/examples/shorts/debugDraw.js +0 -37
- package/examples/shorts/empty.js +0 -30
- package/examples/shorts/flappyGame.js +0 -55
- package/examples/shorts/fontImage.js +0 -17
- package/examples/shorts/fps.js +0 -90
- package/examples/shorts/helloWorld.js +0 -11
- package/examples/shorts/hillGlideGame.js +0 -63
- package/examples/shorts/input.js +0 -64
- package/examples/shorts/landerGame.js +0 -57
- package/examples/shorts/maze.js +0 -48
- package/examples/shorts/medals.js +0 -51
- package/examples/shorts/music.js +0 -78
- package/examples/shorts/musicPlayer.js +0 -137
- package/examples/shorts/nineSlice.js +0 -40
- package/examples/shorts/parallax.js +0 -72
- package/examples/shorts/particles.js +0 -28
- package/examples/shorts/piano.js +0 -44
- package/examples/shorts/platformer.js +0 -45
- package/examples/shorts/pongGame.js +0 -41
- package/examples/shorts/postProcess.js +0 -62
- package/examples/shorts/sequencer.js +0 -124
- package/examples/shorts/shader.js +0 -29
- package/examples/shorts/shapes.js +0 -21
- package/examples/shorts/slidingPuzzle.js +0 -52
- package/examples/shorts/song.mp3 +0 -0
- package/examples/shorts/sound.js +0 -36
- package/examples/shorts/spaceGame.js +0 -58
- package/examples/shorts/spriteAtlas.js +0 -31
- package/examples/shorts/starfield.js +0 -15
- package/examples/shorts/texture.js +0 -16
- package/examples/shorts/textureWrapped.js +0 -12
- package/examples/shorts/tileLayer.js +0 -48
- package/examples/shorts/tileRaycast.js +0 -39
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/tiltedView.js +0 -63
- package/examples/shorts/timers.js +0 -53
- package/examples/shorts/topDown.js +0 -38
- package/examples/shorts/tween.js +0 -24
- package/examples/shorts/uiSystem.js +0 -61
- package/examples/shorts/video.webm +0 -0
- package/examples/shorts/videoPlayer.js +0 -34
- package/examples/shorts.js +0 -743
- package/examples/starter/build.bat +0 -7
- package/examples/starter/build.mjs +0 -126
- package/examples/starter/game.js +0 -137
- package/examples/starter/index.html +0 -35
- package/examples/starter/tiles.png +0 -0
- package/examples/stress/index.html +0 -173
- package/examples/style.css +0 -150
- package/examples/tweenSystem/game.js +0 -171
- package/examples/tweenSystem/index.html +0 -10
- package/examples/tweenSystem/tiles.png +0 -0
- package/examples/typescript/build.mjs +0 -60
- package/examples/typescript/game.js +0 -100
- package/examples/typescript/game.ts +0 -132
- package/examples/typescript/index.html +0 -10
- package/examples/typescript/tiles.png +0 -0
- package/examples/typescript/tsconfig.json +0 -17
- package/examples/uiSystem/game.js +0 -139
- package/examples/uiSystem/index.html +0 -10
- package/examples/uiSystem/tiles.png +0 -0
- package/jsconfig.json +0 -12
- package/plugins/desktop.ini +0 -2
- package/reference.md +0 -448
- package/test/math.test.mjs +0 -774
- package/test/setup.mjs +0 -22
- package/test/smoke.test.mjs +0 -274
- package/test/tween.test.mjs +0 -576
- package/test/util.test.mjs +0 -80
package/plugins/pluginExport.js
CHANGED
|
@@ -4,6 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
export
|
|
6
6
|
{
|
|
7
|
+
// Medals
|
|
8
|
+
medals,
|
|
9
|
+
medalsPreventUnlock,
|
|
10
|
+
medalsInit,
|
|
11
|
+
medalsForEach,
|
|
12
|
+
Medal,
|
|
13
|
+
medalDisplayTime,
|
|
14
|
+
medalDisplaySlideTime,
|
|
15
|
+
medalDisplaySize,
|
|
16
|
+
setMedalDisplayTime,
|
|
17
|
+
setMedalDisplaySlideTime,
|
|
18
|
+
setMedalDisplaySize,
|
|
19
|
+
setMedalsPreventUnlock,
|
|
20
|
+
|
|
7
21
|
// Newgrounds
|
|
8
22
|
newgrounds,
|
|
9
23
|
NewgroundsPlugin,
|
|
@@ -29,6 +43,7 @@ export
|
|
|
29
43
|
UICheckbox,
|
|
30
44
|
UISlider,
|
|
31
45
|
UIVideo,
|
|
46
|
+
UILayout,
|
|
32
47
|
|
|
33
48
|
// Box2D Physics
|
|
34
49
|
box2d,
|
|
@@ -67,4 +82,8 @@ export
|
|
|
67
82
|
tweenStopAll,
|
|
68
83
|
tweenUpdate,
|
|
69
84
|
Ease,
|
|
85
|
+
|
|
86
|
+
// Path Finding
|
|
87
|
+
PathFinder,
|
|
88
|
+
PathFinderNode,
|
|
70
89
|
}
|
|
@@ -238,13 +238,13 @@ const Ease =
|
|
|
238
238
|
* @param {number} x
|
|
239
239
|
* @returns {number}
|
|
240
240
|
* @memberof TweenSystem */
|
|
241
|
-
SINE: (x) => 1 -
|
|
241
|
+
SINE: (x) => 1 - cos(x * (PI / 2)),
|
|
242
242
|
|
|
243
243
|
/** Circular ease-in curve.
|
|
244
244
|
* @param {number} x
|
|
245
245
|
* @returns {number}
|
|
246
246
|
* @memberof TweenSystem */
|
|
247
|
-
CIRC: (x) => 1 -
|
|
247
|
+
CIRC: (x) => 1 - (1 - x * x)**.5,
|
|
248
248
|
|
|
249
249
|
/** Exponential ease-in curve (`2^(10x-10)`).
|
|
250
250
|
* @param {number} x
|
|
@@ -263,7 +263,7 @@ const Ease =
|
|
|
263
263
|
* @returns {number}
|
|
264
264
|
* @memberof TweenSystem */
|
|
265
265
|
ELASTIC: (x) =>
|
|
266
|
-
-(2 ** (10 * x - 10)) *
|
|
266
|
+
-(2 ** (10 * x - 10)) * sin(((37 - 40 * x) * PI) / 6),
|
|
267
267
|
|
|
268
268
|
/** Spring-like ease-out: oscillates outward after passing the target.
|
|
269
269
|
* @param {number} x
|
|
@@ -271,8 +271,8 @@ const Ease =
|
|
|
271
271
|
* @memberof TweenSystem */
|
|
272
272
|
SPRING: (x) =>
|
|
273
273
|
1 -
|
|
274
|
-
(
|
|
275
|
-
|
|
274
|
+
(sin(PI * (1 - x) * (0.2 + 2.5 * (1 - x) ** 3)) *
|
|
275
|
+
x ** 2.2 +
|
|
276
276
|
(1 - x)) *
|
|
277
277
|
(1.0 + 1.2 * x),
|
|
278
278
|
|
|
@@ -376,7 +376,7 @@ const Ease =
|
|
|
376
376
|
{
|
|
377
377
|
const tMid = (t0 + t1) / 2;
|
|
378
378
|
const [bx, by] = curve(tMid);
|
|
379
|
-
if (
|
|
379
|
+
if (abs(bx - x) < 1e-5) return by;
|
|
380
380
|
if (bx < x) t0 = tMid; else t1 = tMid;
|
|
381
381
|
}
|
|
382
382
|
return curve((t0 + t1) / 2)[1];
|
|
@@ -409,7 +409,7 @@ const Ease =
|
|
|
409
409
|
function tweenProperty(target, propertyPath, start, end, duration = 1, options = {})
|
|
410
410
|
{
|
|
411
411
|
ASSERT(target != null && typeof target === 'object', 'tweenProperty target must be an object');
|
|
412
|
-
ASSERT(
|
|
412
|
+
ASSERT(isStringLike(propertyPath) && propertyPath.length > 0, 'tweenProperty propertyPath must be a non-empty string');
|
|
413
413
|
|
|
414
414
|
const parts = propertyPath.split('.');
|
|
415
415
|
const lastKey = parts.pop();
|
package/plugins/uiSystem.js
CHANGED
|
@@ -118,22 +118,38 @@ class UISystemPlugin
|
|
|
118
118
|
this.lastHoverObject = undefined;
|
|
119
119
|
/** @property {UIObject} - Current confirm menu being shown */
|
|
120
120
|
this.confirmDialog = undefined;
|
|
121
|
-
/** @
|
|
122
|
-
this.
|
|
121
|
+
/** @private */
|
|
122
|
+
this._keyInputObject = undefined;
|
|
123
|
+
/** @private */
|
|
124
|
+
this._onKeyDown = (e) => this._keyInputObject?.onKeyDown(e);
|
|
123
125
|
|
|
124
126
|
engineAddPlugin(uiUpdate, uiRender);
|
|
125
127
|
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
{ uiSystem.keyInputObject?.onKeyDown(e); }
|
|
129
|
-
document.addEventListener('keydown', onKeyDown);
|
|
130
|
-
|
|
131
|
-
// set object position in parent space
|
|
128
|
+
// set object position based on anchor target (parent box, or canvas for roots),
|
|
129
|
+
// self-pivot, and localPos offset
|
|
132
130
|
function updateTransforms(o)
|
|
133
131
|
{
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
132
|
+
let targetPos, targetSize;
|
|
133
|
+
if (o.parent)
|
|
134
|
+
{
|
|
135
|
+
targetPos = o.parent.pos;
|
|
136
|
+
targetSize = o.parent.size;
|
|
137
|
+
}
|
|
138
|
+
else
|
|
139
|
+
{
|
|
140
|
+
// anchor to canvas in native coords (handles nativeHeight if set)
|
|
141
|
+
targetPos = uiSystem.screenToNative(mainCanvasSize.scale(.5));
|
|
142
|
+
targetSize = uiSystem.nativeHeight
|
|
143
|
+
? vec2(mainCanvasSize.x * uiSystem.nativeHeight / mainCanvasSize.y,
|
|
144
|
+
uiSystem.nativeHeight)
|
|
145
|
+
: mainCanvasSize;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const a = o.anchor;
|
|
149
|
+
o.pos = targetPos
|
|
150
|
+
.add(targetSize.multiply(a).scale(.5)) // anchor point on target
|
|
151
|
+
.subtract(o.size.multiply(a).scale(.5)) // pivot shift on self
|
|
152
|
+
.add(o.localPos); // user offset
|
|
137
153
|
}
|
|
138
154
|
|
|
139
155
|
// setup recursive update and render
|
|
@@ -469,8 +485,23 @@ class UISystemPlugin
|
|
|
469
485
|
return p;
|
|
470
486
|
}
|
|
471
487
|
|
|
488
|
+
/** Object to send keyboard input to (typically a UITextInput).
|
|
489
|
+
* The document keydown listener is only attached while this is set,
|
|
490
|
+
* so games that never use text input pay no event-handling cost.
|
|
491
|
+
* @type {UIObject} */
|
|
492
|
+
get keyInputObject() { return this._keyInputObject; }
|
|
493
|
+
set keyInputObject(obj)
|
|
494
|
+
{
|
|
495
|
+
const had = !!this._keyInputObject;
|
|
496
|
+
this._keyInputObject = obj;
|
|
497
|
+
if (!had && obj)
|
|
498
|
+
document.addEventListener('keydown', this._onKeyDown);
|
|
499
|
+
else if (had && !obj)
|
|
500
|
+
document.removeEventListener('keydown', this._onKeyDown);
|
|
501
|
+
}
|
|
502
|
+
|
|
472
503
|
/** Destroy and remove all objects
|
|
473
|
-
* @memberof
|
|
504
|
+
* @memberof UISystem */
|
|
474
505
|
destroyObjects()
|
|
475
506
|
{
|
|
476
507
|
for (const o of this.uiObjects)
|
|
@@ -585,9 +616,8 @@ class UISystemPlugin
|
|
|
585
616
|
// confirm menu
|
|
586
617
|
const confirmMenu = new UIObject(vec2(), size);
|
|
587
618
|
uiSystem.confirmDialog = confirmMenu;
|
|
588
|
-
confirmMenu.onRender = ()=>
|
|
619
|
+
confirmMenu.onRender = ()=>
|
|
589
620
|
{
|
|
590
|
-
confirmMenu.pos = uiSystem.screenToNative(mainCanvasSize.scale(.5));
|
|
591
621
|
const backgroundColor = hsl(0,0,0,.7);
|
|
592
622
|
uiSystem.drawRect(vec2(), vec2(1e9), backgroundColor);
|
|
593
623
|
}
|
|
@@ -720,7 +750,11 @@ class UIObject
|
|
|
720
750
|
this.navigationIndex = undefined;
|
|
721
751
|
/** @property {boolean} - Should this be auto selected by navigation? Must also have valid navigation index. */
|
|
722
752
|
this.navigationAutoSelect = false;
|
|
723
|
-
|
|
753
|
+
/** @property {Vector2} - Where on parent (or canvas if no parent) this object is anchored.
|
|
754
|
+
* Components in [-1, 1]: (0,0)=center, (-1,-1)=top-left, (1,1)=bottom-right.
|
|
755
|
+
* Also acts as self-pivot — e.g. (1,-1) puts your top-right corner at the anchor point. */
|
|
756
|
+
this.anchor = vec2();
|
|
757
|
+
|
|
724
758
|
uiSystem.uiObjects.push(this);
|
|
725
759
|
}
|
|
726
760
|
|
|
@@ -893,15 +927,13 @@ class UIObject
|
|
|
893
927
|
* @return {string} */
|
|
894
928
|
toString()
|
|
895
929
|
{
|
|
896
|
-
if (!debug) return;
|
|
897
|
-
|
|
898
930
|
let text = 'type = ' + this.constructor.name;
|
|
899
931
|
if (this.text)
|
|
900
932
|
text += '\ntext = ' + this.text;
|
|
901
933
|
if (this.pos.x || this.pos.y)
|
|
902
934
|
text += '\npos = ' + this.pos;
|
|
903
935
|
if (this.localPos.x || this.localPos.y)
|
|
904
|
-
text += '\
|
|
936
|
+
text += '\nlocalPos = ' + this.localPos;
|
|
905
937
|
if (this.size.x || this.size.y)
|
|
906
938
|
text += '\nsize = ' + this.size;
|
|
907
939
|
if (this.color)
|
|
@@ -975,9 +1007,9 @@ class UIText extends UIObject
|
|
|
975
1007
|
{
|
|
976
1008
|
super(pos, size);
|
|
977
1009
|
|
|
978
|
-
ASSERT(
|
|
1010
|
+
ASSERT(isStringLike(text), 'ui text must be a string');
|
|
979
1011
|
ASSERT(['left','center','right'].includes(align), 'ui text align must be left, center, or right');
|
|
980
|
-
ASSERT(
|
|
1012
|
+
ASSERT(isStringLike(font), 'ui text font must be a string');
|
|
981
1013
|
|
|
982
1014
|
// set properties
|
|
983
1015
|
this.text = text;
|
|
@@ -1025,7 +1057,7 @@ class UITextInput extends UIObject
|
|
|
1025
1057
|
{
|
|
1026
1058
|
super(pos, size);
|
|
1027
1059
|
|
|
1028
|
-
ASSERT(
|
|
1060
|
+
ASSERT(isStringLike(text), 'ui text must be a string');
|
|
1029
1061
|
|
|
1030
1062
|
/** @property {number} - Max length of input (0 = no limit) */
|
|
1031
1063
|
this.maxLength = 0;
|
|
@@ -1162,7 +1194,7 @@ class UIButton extends UIObject
|
|
|
1162
1194
|
{
|
|
1163
1195
|
super(pos, size);
|
|
1164
1196
|
|
|
1165
|
-
ASSERT(
|
|
1197
|
+
ASSERT(isStringLike(text), 'ui button must be a string');
|
|
1166
1198
|
ASSERT(isColor(color), 'ui button color must be a color');
|
|
1167
1199
|
|
|
1168
1200
|
/** @property {Vector2} - Text offset for the button */
|
|
@@ -1203,7 +1235,7 @@ class UICheckbox extends UIObject
|
|
|
1203
1235
|
{
|
|
1204
1236
|
super(pos, size);
|
|
1205
1237
|
|
|
1206
|
-
ASSERT(
|
|
1238
|
+
ASSERT(isStringLike(text), 'ui checkbox must be a string');
|
|
1207
1239
|
ASSERT(isColor(color), 'ui checkbox color must be a color');
|
|
1208
1240
|
|
|
1209
1241
|
/** @property {boolean} - Current percentage value of this slider 0-1 */
|
|
@@ -1260,7 +1292,7 @@ class UISlider extends UIObject
|
|
|
1260
1292
|
super(pos, size);
|
|
1261
1293
|
|
|
1262
1294
|
ASSERT(isNumber(value), 'ui slider value must be a number');
|
|
1263
|
-
ASSERT(
|
|
1295
|
+
ASSERT(isStringLike(text), 'ui slider must be a string');
|
|
1264
1296
|
ASSERT(isColor(color), 'ui slider color must be a color');
|
|
1265
1297
|
ASSERT(isColor(handleColor), 'ui slider handleColor must be a color');
|
|
1266
1298
|
|
|
@@ -1378,7 +1410,7 @@ class UIVideo extends UIObject
|
|
|
1378
1410
|
{
|
|
1379
1411
|
super(pos, size || vec2());
|
|
1380
1412
|
|
|
1381
|
-
ASSERT(
|
|
1413
|
+
ASSERT(isStringLike(src), 'video src must be a string');
|
|
1382
1414
|
ASSERT(isNumber(volume), 'video volume must be a number');
|
|
1383
1415
|
|
|
1384
1416
|
this.color = BLACK; // default to black background
|
|
@@ -1495,4 +1527,133 @@ class UIVideo extends UIObject
|
|
|
1495
1527
|
this.video.remove();
|
|
1496
1528
|
super.destroy();
|
|
1497
1529
|
}
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1533
|
+
/**
|
|
1534
|
+
* UILayout - A container that auto-arranges children in a vertical list, horizontal list, or grid
|
|
1535
|
+
* - Set columns to 1 for a vertical list (default)
|
|
1536
|
+
* - Set columns to children.length for a horizontal list
|
|
1537
|
+
* - Set columns to N (1 < N < children.length) for a grid with N columns
|
|
1538
|
+
* - Per-child sizing: each row's height = max child.size.y in that row, each column's width = max child.size.x in that column
|
|
1539
|
+
* - Children are positioned centered in their cell
|
|
1540
|
+
* - Container auto-sizes to fit children plus padding
|
|
1541
|
+
* @extends UIObject
|
|
1542
|
+
* @memberof UISystem
|
|
1543
|
+
*/
|
|
1544
|
+
class UILayout extends UIObject
|
|
1545
|
+
{
|
|
1546
|
+
/** Create a UILayout container that auto-arranges children
|
|
1547
|
+
* @param {Vector2} [pos]
|
|
1548
|
+
* @param {number} [columns=1] - Number of columns (1 = vertical list)
|
|
1549
|
+
* @param {number} [gap=10] - Space between children
|
|
1550
|
+
* @param {number} [padding=10] - Space between container border and children
|
|
1551
|
+
* @param {boolean} [transparent=false] - If true, draws no background, outline, or shadow
|
|
1552
|
+
*/
|
|
1553
|
+
constructor(pos, columns=1, gap=10, padding=10, transparent=false)
|
|
1554
|
+
{
|
|
1555
|
+
super(pos);
|
|
1556
|
+
|
|
1557
|
+
ASSERT(isNumber(columns) && columns >= 1, 'ui layout columns must be a number >= 1');
|
|
1558
|
+
ASSERT(isNumber(gap), 'ui layout gap must be a number');
|
|
1559
|
+
ASSERT(isNumber(padding), 'ui layout padding must be a number');
|
|
1560
|
+
|
|
1561
|
+
/** @property {number} - Number of columns in the layout */
|
|
1562
|
+
this.columns = columns;
|
|
1563
|
+
/** @property {number} - Space between children */
|
|
1564
|
+
this.gap = gap;
|
|
1565
|
+
/** @property {number} - Space between container border and children */
|
|
1566
|
+
this.padding = padding;
|
|
1567
|
+
|
|
1568
|
+
if (transparent)
|
|
1569
|
+
{
|
|
1570
|
+
// pure positioning helper - skip background, outline, and shadow
|
|
1571
|
+
this.color = CLEAR_BLACK;
|
|
1572
|
+
this.gradientColor = undefined;
|
|
1573
|
+
this.lineWidth = 0;
|
|
1574
|
+
this.shadowColor = CLEAR_BLACK;
|
|
1575
|
+
}
|
|
1576
|
+
this.relayout();
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
/** Add a child UIObject and re-layout
|
|
1580
|
+
* @param {UIObject} child
|
|
1581
|
+
* @return {UIObject} The child object added */
|
|
1582
|
+
addChild(child)
|
|
1583
|
+
{
|
|
1584
|
+
super.addChild(child);
|
|
1585
|
+
this.relayout();
|
|
1586
|
+
return child;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
/** Remove a child UIObject and re-layout
|
|
1590
|
+
* @param {UIObject} child */
|
|
1591
|
+
removeChild(child)
|
|
1592
|
+
{
|
|
1593
|
+
super.removeChild(child);
|
|
1594
|
+
this.relayout();
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
/** Recompute child positions and container size based on per-child sizes.
|
|
1598
|
+
* Called automatically by addChild and removeChild. Call manually if you
|
|
1599
|
+
* mutate a child's size or change columns, gap, or padding. */
|
|
1600
|
+
relayout()
|
|
1601
|
+
{
|
|
1602
|
+
const n = this.children.length;
|
|
1603
|
+
if (!n)
|
|
1604
|
+
{
|
|
1605
|
+
this.size = vec2(this.padding * 2);
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
const cols = this.columns;
|
|
1610
|
+
const rows = ceil(n / cols);
|
|
1611
|
+
const colWidths = new Array(cols).fill(0);
|
|
1612
|
+
const rowHeights = new Array(rows).fill(0);
|
|
1613
|
+
|
|
1614
|
+
// first pass: compute column widths and row heights from child sizes
|
|
1615
|
+
for (let i = 0; i < n; ++i)
|
|
1616
|
+
{
|
|
1617
|
+
const col = i % cols;
|
|
1618
|
+
const row = floor(i / cols);
|
|
1619
|
+
const child = this.children[i];
|
|
1620
|
+
colWidths[col] = max(colWidths[col], child.size.x);
|
|
1621
|
+
rowHeights[row] = max(rowHeights[row], child.size.y);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
// total content size (sum of column widths/row heights plus gaps between them)
|
|
1625
|
+
let contentWidth = this.gap * (cols - 1);
|
|
1626
|
+
for (const w of colWidths) contentWidth += w;
|
|
1627
|
+
let contentHeight = this.gap * (rows - 1);
|
|
1628
|
+
for (const h of rowHeights) contentHeight += h;
|
|
1629
|
+
|
|
1630
|
+
// cumulative column/row offsets so positioning is O(n) not O(n^2)
|
|
1631
|
+
const colOffsets = new Array(cols);
|
|
1632
|
+
let xAcc = 0;
|
|
1633
|
+
for (let c = 0; c < cols; ++c)
|
|
1634
|
+
{
|
|
1635
|
+
colOffsets[c] = xAcc;
|
|
1636
|
+
xAcc += colWidths[c];
|
|
1637
|
+
}
|
|
1638
|
+
const rowOffsets = new Array(rows);
|
|
1639
|
+
let yAcc = 0;
|
|
1640
|
+
for (let r = 0; r < rows; ++r)
|
|
1641
|
+
{
|
|
1642
|
+
rowOffsets[r] = yAcc;
|
|
1643
|
+
yAcc += rowHeights[r];
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
// second pass: position each child centered in its cell
|
|
1647
|
+
for (let i = 0; i < n; ++i)
|
|
1648
|
+
{
|
|
1649
|
+
const col = i % cols;
|
|
1650
|
+
const row = floor(i / cols);
|
|
1651
|
+
const x = -contentWidth/2 + colOffsets[col] + this.gap * col + colWidths[col] / 2;
|
|
1652
|
+
const y = -contentHeight/2 + rowOffsets[row] + this.gap * row + rowHeights[row] / 2;
|
|
1653
|
+
this.children[i].localPos = vec2(x, y);
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
// container size = content + padding on all sides
|
|
1657
|
+
this.size = vec2(contentWidth + this.padding * 2, contentHeight + this.padding * 2);
|
|
1658
|
+
}
|
|
1498
1659
|
}
|
package/src/engine.js
CHANGED
|
@@ -32,7 +32,7 @@ const engineName = 'LittleJS';
|
|
|
32
32
|
* @type {string}
|
|
33
33
|
* @default
|
|
34
34
|
* @memberof Engine */
|
|
35
|
-
const engineVersion = '1.18.
|
|
35
|
+
const engineVersion = '1.18.8';
|
|
36
36
|
|
|
37
37
|
/** Frames per second to update
|
|
38
38
|
* @type {number}
|
|
@@ -204,7 +204,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
204
204
|
const combinedScale = timeScale * debugScale;
|
|
205
205
|
frameTimeDeltaMS *= combinedScale;
|
|
206
206
|
frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
|
|
207
|
-
if (
|
|
207
|
+
if (debugScale <= 1)
|
|
208
208
|
frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp min framerate
|
|
209
209
|
|
|
210
210
|
let wasUpdated = false;
|
|
@@ -562,143 +562,4 @@ function engineObjectsRaycast(start, end, objects=engineObjects)
|
|
|
562
562
|
|
|
563
563
|
debugRaycast && debugLine(start, end, hitObjects.length ? '#f00' : '#00f', .02);
|
|
564
564
|
return hitObjects;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
568
|
-
function drawEngineLogo(t)
|
|
569
|
-
{
|
|
570
|
-
const blackAndWhite = 0;
|
|
571
|
-
const showName = 1;
|
|
572
|
-
|
|
573
|
-
// LittleJS Logo and Splash Screen
|
|
574
|
-
const x = mainContext;
|
|
575
|
-
const dpr = canvasPixelRatio ?? (devicePixelRatio || 1);
|
|
576
|
-
const w = mainCanvas.width = innerWidth * dpr;
|
|
577
|
-
const h = mainCanvas.height = innerHeight * dpr;
|
|
578
|
-
{
|
|
579
|
-
// background
|
|
580
|
-
const p3 = percent(t, 1, .8);
|
|
581
|
-
const p4 = percent(t, 0, .5);
|
|
582
|
-
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.6);
|
|
583
|
-
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
584
|
-
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
585
|
-
x.save();
|
|
586
|
-
x.fillStyle = g;
|
|
587
|
-
x.fillRect(0,0,w,h);
|
|
588
|
-
}
|
|
589
|
-
const gradient = (X1,Y1,X2,Y2,C,S=1)=>
|
|
590
|
-
{
|
|
591
|
-
if (C >= 0)
|
|
592
|
-
{
|
|
593
|
-
if (blackAndWhite)
|
|
594
|
-
x.fillStyle = '#fff';
|
|
595
|
-
else
|
|
596
|
-
{
|
|
597
|
-
const g = x.fillStyle = x.createLinearGradient(X1,Y1,X2,Y2);
|
|
598
|
-
g.addColorStop(0,color(C,2));
|
|
599
|
-
g.addColorStop(1,color(C,1));
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
else
|
|
603
|
-
x.fillStyle = '#000';
|
|
604
|
-
C >= -1 ? (x.fill(), S && x.stroke()) : x.stroke();
|
|
605
|
-
}
|
|
606
|
-
const circle = (X,Y,R,A=0,B=2*PI,C,S)=>
|
|
607
|
-
{
|
|
608
|
-
x.beginPath();
|
|
609
|
-
x.arc(X,Y,R,p*A,p*B);
|
|
610
|
-
gradient(X,Y-R,X,Y+R,C,S);
|
|
611
|
-
}
|
|
612
|
-
const rect = (X,Y,W,H,C)=>
|
|
613
|
-
{
|
|
614
|
-
x.beginPath();
|
|
615
|
-
x.rect(X,Y,W,H*p);
|
|
616
|
-
gradient(X,Y+H,X+W,Y,C);
|
|
617
|
-
}
|
|
618
|
-
const poly = (points,C,Y,H)=>
|
|
619
|
-
{
|
|
620
|
-
x.beginPath();
|
|
621
|
-
for (const p of points)
|
|
622
|
-
x.lineTo(p.x, p.y);
|
|
623
|
-
x.closePath();
|
|
624
|
-
gradient(0, Y, 0, Y+H,C);
|
|
625
|
-
}
|
|
626
|
-
const color = (c,l)=> l?`hsl(${[.95,.56,.13][c%3]*360} 99%${[0,50,75][l]}%`:'#000';
|
|
627
|
-
|
|
628
|
-
// center and fit tos screen
|
|
629
|
-
const alpha = oscillate(1,1,t);
|
|
630
|
-
const p = percent(alpha, .1, .5);
|
|
631
|
-
const size = min(6, min(w,h)/99);
|
|
632
|
-
x.translate(w/2,h/2);
|
|
633
|
-
x.scale(size,size);
|
|
634
|
-
x.translate(-40,-35);
|
|
635
|
-
p < 1 && x.setLineDash([99*p,99]);
|
|
636
|
-
x.lineJoin = x.lineCap = 'round';
|
|
637
|
-
x.lineWidth = .1 + p*1.9;
|
|
638
|
-
//x.strokeStyle='#fff7';
|
|
639
|
-
|
|
640
|
-
if (showName)
|
|
641
|
-
{
|
|
642
|
-
// engine name text
|
|
643
|
-
const Y = 54;
|
|
644
|
-
const s = 'LittleJS';
|
|
645
|
-
x.font = '900 15.5px arial';
|
|
646
|
-
x.lineWidth = .1+p*3.9;
|
|
647
|
-
x.textAlign = 'center';
|
|
648
|
-
x.textBaseline = 'top';
|
|
649
|
-
rect(11,Y+1,59,8*p,-1);
|
|
650
|
-
x.beginPath();
|
|
651
|
-
|
|
652
|
-
let w2 = 0;
|
|
653
|
-
for (let i=0;i<s.length;++i)
|
|
654
|
-
w2 += x.measureText(s[i]).width;
|
|
655
|
-
for (let j=2;j--;)
|
|
656
|
-
for (let i=0,X=40-w2/2;i<s.length;++i)
|
|
657
|
-
{
|
|
658
|
-
const w = x.measureText(s[i]).width, X2 = X+w/2;
|
|
659
|
-
gradient(X2,Y,X2+2,Y+13,i>5?1:0);
|
|
660
|
-
x[j?'strokeText':'fillText'](s[i],X2,Y+.5,17*p);
|
|
661
|
-
X += w;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
x.lineWidth = .1 + p*1.9;
|
|
665
|
-
rect(3,Y,73,0); // bottom
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
rect(7,15,26,-7,0); // cab top
|
|
669
|
-
rect(25,15,8,25,-1); // cab front
|
|
670
|
-
rect(10,40,15,-25,1); // cab back
|
|
671
|
-
rect(14,21,7,9,2); // cab window
|
|
672
|
-
rect(38,20,6,-6,2); // little stack
|
|
673
|
-
|
|
674
|
-
// big stack
|
|
675
|
-
rect(49,20,10,-6,0);
|
|
676
|
-
const stackPoints = [vec2(44,8),vec2(64,8),vec2(59,8+6*p),vec2(49,8+6*p)];
|
|
677
|
-
poly(stackPoints,2,8,6*p);
|
|
678
|
-
rect(44,8,20,-7,0);
|
|
679
|
-
|
|
680
|
-
// engine
|
|
681
|
-
for (let i=5;i--;) circle(59-i*6*p,30,10,0,2*PI,1,0);
|
|
682
|
-
circle(59,30,4,0,7,2); // light
|
|
683
|
-
|
|
684
|
-
// engine outline
|
|
685
|
-
rect(35,20,24,0); // top
|
|
686
|
-
circle(59,30,10); // front
|
|
687
|
-
circle(47,30,10,PI/2,PI*3/2); // middle
|
|
688
|
-
circle(35,30,10,PI/2,PI*3/2); // back
|
|
689
|
-
rect(7,40,13,7,-1); // bottom back
|
|
690
|
-
rect(17,40,43,14,-1); // bottom center
|
|
691
|
-
|
|
692
|
-
// wheels
|
|
693
|
-
for (let i=3;i--;) for (let j=2;j--;) circle(17+15*i,47,j?7:1,0,2*PI,2);
|
|
694
|
-
|
|
695
|
-
// cowcatcher
|
|
696
|
-
for (let i=2;i--;)
|
|
697
|
-
{
|
|
698
|
-
let w=6, s=7, o=53+w*p*i
|
|
699
|
-
const points = [vec2(o+s,54),vec2(o,40),vec2(o+w*p,40),vec2(o+s+w*p,54)];
|
|
700
|
-
poly(points,0,40,14);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
x.restore();
|
|
704
565
|
}
|
package/src/engineAudio.js
CHANGED
|
@@ -84,7 +84,7 @@ class Sound
|
|
|
84
84
|
{
|
|
85
85
|
if (!soundEnable || headlessMode) return;
|
|
86
86
|
|
|
87
|
-
ASSERT(!asset || isArray(asset) ||
|
|
87
|
+
ASSERT(!asset || isArray(asset) || isStringLike(asset), 'asset must be a file name or zzfx array');
|
|
88
88
|
ASSERT(randomness === undefined || isNumber(randomness), 'randomness must be a number');
|
|
89
89
|
ASSERT(randomness === undefined || randomness >= 0 && randomness <=1, 'randomness must be between 0 and 1');
|
|
90
90
|
ASSERT(isNumber(range), 'range must be a number');
|
|
@@ -105,8 +105,8 @@ class Sound
|
|
|
105
105
|
|
|
106
106
|
if (Array.isArray(asset))
|
|
107
107
|
{
|
|
108
|
-
// generate zzfx sound
|
|
109
|
-
const zzfxSound = asset;
|
|
108
|
+
// generate zzfx sound — copy so we don't mutate the caller's array
|
|
109
|
+
const zzfxSound = asset.slice();
|
|
110
110
|
|
|
111
111
|
// remove randomness so it can be applied on playback
|
|
112
112
|
const defaultRandomness = randomness ?? .05;
|
package/src/engineBuild.mjs
CHANGED
|
@@ -33,18 +33,20 @@ const engineSourceFiles =
|
|
|
33
33
|
`${SOURCE_FOLDER}/engineAudio.js`,
|
|
34
34
|
`${SOURCE_FOLDER}/engineTileLayer.js`,
|
|
35
35
|
`${SOURCE_FOLDER}/engineParticles.js`,
|
|
36
|
-
`${SOURCE_FOLDER}/engineMedals.js`,
|
|
37
36
|
`${SOURCE_FOLDER}/engineWebGL.js`,
|
|
37
|
+
`${SOURCE_FOLDER}/engineLogo.js`,
|
|
38
38
|
];
|
|
39
39
|
const enginePluginFiles =
|
|
40
40
|
[
|
|
41
|
+
`${PLUGIN_FOLDER}/medalSystem.js`,
|
|
41
42
|
`${PLUGIN_FOLDER}/newgrounds.js`,
|
|
42
43
|
`${PLUGIN_FOLDER}/postProcess.js`,
|
|
43
44
|
`${PLUGIN_FOLDER}/zzfxm.js`,
|
|
44
45
|
`${PLUGIN_FOLDER}/uiSystem.js`,
|
|
45
46
|
`${PLUGIN_FOLDER}/box2d.js`,
|
|
46
47
|
`${PLUGIN_FOLDER}/drawUtilities.js`,
|
|
47
|
-
`${PLUGIN_FOLDER}/
|
|
48
|
+
`${PLUGIN_FOLDER}/tweenSystem.js`,
|
|
49
|
+
`${PLUGIN_FOLDER}/pathFinder.js`,
|
|
48
50
|
];
|
|
49
51
|
const engineExtraFiles =
|
|
50
52
|
[
|
package/src/engineDebug.js
CHANGED
|
@@ -44,7 +44,7 @@ let debugKey = 'Escape';
|
|
|
44
44
|
let debugOverlay = false;
|
|
45
45
|
|
|
46
46
|
// Engine internal variables not exposed to documentation
|
|
47
|
-
let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParticles = false, debugGamepads = false,
|
|
47
|
+
let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParticles = false, debugGamepads = false, debugTakeScreenshot;
|
|
48
48
|
|
|
49
49
|
///////////////////////////////////////////////////////////////////////////////
|
|
50
50
|
// Debug helper functions
|
|
@@ -79,7 +79,7 @@ function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false, s
|
|
|
79
79
|
{
|
|
80
80
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
81
81
|
ASSERT(isVector2(size), 'size must be a vec2');
|
|
82
|
-
ASSERT(
|
|
82
|
+
ASSERT(isStringLike(color) || isColor(color), 'color is invalid');
|
|
83
83
|
ASSERT(isNumber(time), 'time must be a number');
|
|
84
84
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
85
85
|
|
|
@@ -106,7 +106,7 @@ function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false, screen
|
|
|
106
106
|
{
|
|
107
107
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
108
108
|
ASSERT(isArray(points), 'points must be an array');
|
|
109
|
-
ASSERT(
|
|
109
|
+
ASSERT(isStringLike(color) || isColor(color), 'color is invalid');
|
|
110
110
|
ASSERT(isNumber(time), 'time must be a number');
|
|
111
111
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
112
112
|
|
|
@@ -130,7 +130,7 @@ function debugCircle(pos, size=0, color=WHITE, time=0, fill=false, screenSpace=f
|
|
|
130
130
|
{
|
|
131
131
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
132
132
|
ASSERT(isNumber(size), 'size must be a number');
|
|
133
|
-
ASSERT(
|
|
133
|
+
ASSERT(isStringLike(color) || isColor(color), 'color is invalid');
|
|
134
134
|
ASSERT(isNumber(time), 'time must be a number');
|
|
135
135
|
|
|
136
136
|
if (isColor(color))
|
|
@@ -208,13 +208,13 @@ function debugOverlap(posA, sizeA, posB, sizeB, color, time, screenSpace=false)
|
|
|
208
208
|
* @memberof Debug */
|
|
209
209
|
function debugText(text, pos, size=1, color=WHITE, time=0, angle=0, font='monospace', screenSpace=false)
|
|
210
210
|
{
|
|
211
|
-
ASSERT(
|
|
211
|
+
ASSERT(isStringLike(text), 'text must be a string');
|
|
212
212
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
213
213
|
ASSERT(isNumber(size), 'size must be a number');
|
|
214
|
-
ASSERT(
|
|
214
|
+
ASSERT(isStringLike(color) || isColor(color), 'color is invalid');
|
|
215
215
|
ASSERT(isNumber(time), 'time must be a number');
|
|
216
216
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
217
|
-
ASSERT(
|
|
217
|
+
ASSERT(isStringLike(font), 'font must be a string');
|
|
218
218
|
|
|
219
219
|
if (isColor(color))
|
|
220
220
|
color = color.toString();
|