littlejsengine 1.24.2 → 1.25.0
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/CHANGELOG.md +55 -0
- package/EDITOR.md +2 -2
- package/FAQ.md +6 -0
- package/REFERENCE.md +86 -41
- package/dist/littlejs.d.ts +600 -323
- package/dist/littlejs.esm.js +2412 -1797
- package/dist/littlejs.esm.min.js +38 -24
- package/dist/littlejs.js +2410 -1797
- package/dist/littlejs.min.js +37 -23
- package/dist/littlejs.release.js +2393 -1826
- package/package.json +1 -1
- package/plugins/box2d.js +41 -28
- package/plugins/drawUtilities.js +374 -380
- package/plugins/gltf.js +331 -55
- package/plugins/lightSystem.js +2 -2
- package/plugins/math3d.js +1013 -1014
- package/plugins/newgrounds.js +1 -4
- package/plugins/particleEffects.js +3 -4
- package/plugins/pathFinder.js +52 -28
- package/plugins/pluginExport.js +2 -0
- package/plugins/postProcess.js +196 -65
- package/plugins/render3d.js +8 -6
- package/plugins/render3dEditor.js +3 -3
- package/plugins/render3dExtras.js +31 -10
- package/plugins/render3dLevel.js +15 -4
- package/plugins/textureSheet.js +15 -7
- package/plugins/tweenSystem.js +43 -38
- package/plugins/uiSystem.js +25 -20
- package/src/engine.js +39 -21
- package/src/engineAudio.js +13 -9
- package/src/engineBuild.mjs +49 -2
- package/src/engineDebug.js +3 -2
- package/src/engineDraw.js +20 -18
- package/src/engineEditor.js +75 -23
- package/src/engineInput.js +10 -10
- package/src/engineMath.js +3 -6
- package/src/engineObject.js +8 -9
- package/src/engineParticles.js +8 -7
- package/src/engineRelease.js +17 -10
- package/src/engineTileLayer.js +33 -14
- package/src/engineUtilities.js +15 -2
- package/src/engineWebGL.js +34 -12
package/plugins/render3dLevel.js
CHANGED
|
@@ -15,6 +15,16 @@
|
|
|
15
15
|
|
|
16
16
|
const LEVEL3D_VERSION = 1; // the format a level's littlejs3D names
|
|
17
17
|
|
|
18
|
+
/** A 3D level as it is saved, what level3DLoad takes and the 3D level editor edits
|
|
19
|
+
* @typedef {Object} Level3D
|
|
20
|
+
* @property {number} [littlejs3D] - The format's version, 1
|
|
21
|
+
* @property {Array<Object>} objects - Each {id, type, pos, rotation, scale, properties}, all but type optional
|
|
22
|
+
* @property {Object} [scene] - The scene block: sky, ambient, sunDirection, sunColor, fog, fogColor, shadows, lensFlare
|
|
23
|
+
* @property {Object} [voxels] - A block map: {pos, size, blocks}
|
|
24
|
+
* @property {Object} [terrain] - A height map: {pos, size, height, color, heights, paint}
|
|
25
|
+
* @property {Object} [prefabs] - The level's own prefabs by name, each {objects, attached}
|
|
26
|
+
* @memberof Level3D */
|
|
27
|
+
|
|
18
28
|
// the types a level's objects are made from, by name
|
|
19
29
|
const level3DTypes = new Map;
|
|
20
30
|
|
|
@@ -33,7 +43,7 @@ const level3DBaseScale = new WeakMap;
|
|
|
33
43
|
* - Adding a name again replaces it, Box, Sphere, Cylinder and Light too
|
|
34
44
|
* @param {string} name - The type the objects have in the level
|
|
35
45
|
* @param {Function} make - A class made at each object's position, or a function called with it
|
|
36
|
-
* @param {Object} [defaults] - Properties of each one made, the level editor shows inputs for them
|
|
46
|
+
* @param {Object<string, any>} [defaults] - Properties of each one made, the level editor shows inputs for them
|
|
37
47
|
* @param {TileInfo} [tileInfo] - An icon for the level editor
|
|
38
48
|
* @memberof Level3D
|
|
39
49
|
* @example
|
|
@@ -87,7 +97,7 @@ function level3DAddMesh(name, mesh, tileInfo, color=WHITE)
|
|
|
87
97
|
* sunColor, fog, its start and end, fogColor, the horizon color when not given, shadows, and lensFlare, the
|
|
88
98
|
* sun's lens flare; what the block leaves out stays as the game set it, and a level with no block changes
|
|
89
99
|
* nothing
|
|
90
|
-
* @param {
|
|
100
|
+
* @param {Level3D} level - The level, the level editor edits this same object
|
|
91
101
|
* @return {Array<any>} - What each object's type made, a function that made nothing is left out
|
|
92
102
|
* @memberof Level3D */
|
|
93
103
|
function level3DLoad(level)
|
|
@@ -420,7 +430,7 @@ function level3DMakeAt(object, pos, rotation, scale)
|
|
|
420
430
|
* @param {Vector3} [pos3D]
|
|
421
431
|
* @param {Vector3} [rotation3D] - In radians, as an object has it
|
|
422
432
|
* @param {Vector3} [scale3D] - Times the scale the type makes it with
|
|
423
|
-
* @param {Object} [properties] - Over the type's defaults
|
|
433
|
+
* @param {Object<string, any>} [properties] - Over the type's defaults
|
|
424
434
|
* @return {any} - What the type made, a Prefab3D for a prefab, undefined when there is no such type
|
|
425
435
|
* @memberof Level3D
|
|
426
436
|
* @example
|
|
@@ -459,7 +469,8 @@ const level3DPrefabMaking = [];
|
|
|
459
469
|
* by hand does
|
|
460
470
|
* - Adding a name again replaces it
|
|
461
471
|
* @param {string} name - The type its instances have in a level
|
|
462
|
-
* @param {Object} prefab -
|
|
472
|
+
* @param {{objects: Array<Object>, attached?: boolean}} prefab - Its objects, about its own origin, and whether
|
|
473
|
+
* they are attached
|
|
463
474
|
* @memberof Level3D
|
|
464
475
|
* @example
|
|
465
476
|
* level3DAddPrefab('Tower', {objects: [{type: 'Box', pos: [0, 1, 0], scale: [2, 2, 2]},
|
package/plugins/textureSheet.js
CHANGED
|
@@ -37,6 +37,8 @@ let textureSheetPendingCount = 0;
|
|
|
37
37
|
const textureSheetLoaded = new Map;
|
|
38
38
|
// a load's key: what it was given, a vector by its numbers
|
|
39
39
|
const textureSheetLoadKey = (...parts)=> parts.map((p)=> isVector2(p) ? p.x + ',' + p.y : String(p)).join('|');
|
|
40
|
+
// a load whose file failed is forgotten, so loading it again tries again, as when the server comes back
|
|
41
|
+
const textureSheetLoadFailed = (key, loaded)=> textureSheetLoaded.get(key) === loaded && textureSheetLoaded.delete(key);
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
44
|
* Texture Sheet - A texture that images are packed into as they load
|
|
@@ -193,7 +195,8 @@ class TextureSheet
|
|
|
193
195
|
* - Pass frameSize for animations, then step through them with TileInfo.frame
|
|
194
196
|
* - Grid images keep their layout and frames wrap down to the next row
|
|
195
197
|
* - Pass sourcePadding if the source image has padding baked in around frames
|
|
196
|
-
* - The same image loaded again with the same settings gives back what the first load did, packed once
|
|
198
|
+
* - The same image loaded again with the same settings gives back what the first load did, packed once, unless
|
|
199
|
+
* that load failed
|
|
197
200
|
* @param {string} src - Image source path
|
|
198
201
|
* @param {Vector2|number} [frameSize] - Size of each animation frame in pixels, or the whole image less its
|
|
199
202
|
* source padding if not passed
|
|
@@ -248,7 +251,7 @@ function loadSprite(src, frameSize, padding=textureSheetPadding, sourcePadding=0
|
|
|
248
251
|
if (!added)
|
|
249
252
|
{
|
|
250
253
|
// leave the tile empty, no sheet can hold it
|
|
251
|
-
|
|
254
|
+
console.warn('loadSprite image is too large to fit on a texture sheet:', src);
|
|
252
255
|
return;
|
|
253
256
|
}
|
|
254
257
|
Object.assign(tileInfo, added.tile);
|
|
@@ -257,7 +260,8 @@ function loadSprite(src, frameSize, padding=textureSheetPadding, sourcePadding=0
|
|
|
257
260
|
else
|
|
258
261
|
{
|
|
259
262
|
// leave the tile empty if the image failed to load
|
|
260
|
-
|
|
263
|
+
console.warn('loadSprite failed to load image:', src);
|
|
264
|
+
textureSheetLoadFailed(key, tileInfo);
|
|
261
265
|
}
|
|
262
266
|
});
|
|
263
267
|
|
|
@@ -275,7 +279,8 @@ function loadSprite(src, frameSize, padding=textureSheetPadding, sourcePadding=0
|
|
|
275
279
|
* is not read, cut it into its tiles first
|
|
276
280
|
* - An image that fails to load, or that no sheet can hold, adds no tiles and says so in the console, so the tiles
|
|
277
281
|
* of the images after it move up
|
|
278
|
-
* - The same images loaded again with the same settings give back what the first load did, packed once
|
|
282
|
+
* - The same images loaded again with the same settings give back what the first load did, packed once, unless
|
|
283
|
+
* an image of it failed
|
|
279
284
|
* @param {Array<string>} sources - Image source paths
|
|
280
285
|
* @param {Vector2|number} [tileSize] - Size of a tile in pixels
|
|
281
286
|
* @param {number} [padding] - How many pixels padding around each tile on the sheet
|
|
@@ -317,6 +322,7 @@ function loadTiles(sources, tileSize=tileDefaultSize, padding=textureSheetPaddin
|
|
|
317
322
|
// the whole tiles of it, an edge past the last one left out
|
|
318
323
|
const columns = image.width / size.x | 0, rows = image.height / size.y | 0, count = columns * rows;
|
|
319
324
|
const added = count && textureSheetAdd(vec2(columns * size.x, rows * size.y), size, padding, 0);
|
|
325
|
+
image.width || textureSheetLoadFailed(key, set);
|
|
320
326
|
if (!added)
|
|
321
327
|
return console.warn('loadTiles: ' + src + (count ? ' does not fit on a texture sheet' :
|
|
322
328
|
' failed to load, or is smaller than a tile') + ', its tiles are left out');
|
|
@@ -335,7 +341,8 @@ function loadTiles(sources, tileSize=tileDefaultSize, padding=textureSheetPaddin
|
|
|
335
341
|
* - Aseprite frame tags become animations, so do names like run_0, run_1
|
|
336
342
|
* - Trimmed frames are restored to their full source size when packed
|
|
337
343
|
* - Rotated frames are rotated back upright when packed
|
|
338
|
-
* - The same atlas loaded again by its paths, with the same padding, gives back what the first load did, packed
|
|
344
|
+
* - The same atlas loaded again by its paths, with the same padding, gives back what the first load did, packed
|
|
345
|
+
* once, unless that load failed
|
|
339
346
|
* @param {string} imageSrc - Atlas image path
|
|
340
347
|
* @param {string|Object} jsonSrc - Atlas json path, or already parsed json data
|
|
341
348
|
* @param {number} [padding] - How many pixels padding around each frame
|
|
@@ -390,7 +397,7 @@ function loadAtlas(imageSrc, jsonSrc, padding=textureSheetPadding)
|
|
|
390
397
|
const added = textureSheetAdd(blockSize, sourceSize, padding);
|
|
391
398
|
if (!added)
|
|
392
399
|
{
|
|
393
|
-
|
|
400
|
+
console.warn('loadAtlas frames are too large to fit on a texture sheet:', group.name);
|
|
394
401
|
continue;
|
|
395
402
|
}
|
|
396
403
|
const {sheet, tile} = added;
|
|
@@ -424,7 +431,8 @@ function loadAtlas(imageSrc, jsonSrc, padding=textureSheetPadding)
|
|
|
424
431
|
else
|
|
425
432
|
{
|
|
426
433
|
// leave the atlas empty if either file failed to load
|
|
427
|
-
|
|
434
|
+
console.warn('loadAtlas failed to load:', imageSrc, jsonSrc);
|
|
435
|
+
textureSheetLoadFailed(key, atlas);
|
|
428
436
|
}
|
|
429
437
|
});
|
|
430
438
|
|
package/plugins/tweenSystem.js
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
|
|
12
12
|
///////////////////////////////////////////////////////////////////////////////
|
|
13
13
|
|
|
14
|
-
// Module-private list of tweens currently running, each with its active flag set while it is in it
|
|
14
|
+
// Module-private list of tweens currently running, each with its active flag set while it is in it; a stopped
|
|
15
|
+
// one stays listed, inactive, until the next update takes it out, so stopping many is not a search for each
|
|
15
16
|
const tweenActive = [];
|
|
16
17
|
const tweenUpdateList = []; // the tweens an update moves, the ones active when it began
|
|
17
18
|
let tweenUpdatePass = 0; // counts the updates, a tween started during one waits for the next
|
|
@@ -22,14 +23,10 @@ function tweenActivate(tween)
|
|
|
22
23
|
tween.activePass = tweenUpdatePass; // started again, even while active, so this update leaves it alone
|
|
23
24
|
if (tween.active) return;
|
|
24
25
|
tween.active = true;
|
|
25
|
-
tweenActive.push(tween);
|
|
26
|
-
|
|
27
|
-
function tweenDeactivate(tween)
|
|
28
|
-
{
|
|
29
|
-
if (!tween.active) return;
|
|
30
|
-
tween.active = false;
|
|
31
|
-
tweenActive.splice(tweenActive.indexOf(tween), 1);
|
|
26
|
+
tween.listed || tweenActive.push(tween); // one stopped this update is still listed, in its place
|
|
27
|
+
tween.listed = true;
|
|
32
28
|
}
|
|
29
|
+
function tweenDeactivate(tween) { tween.active = false; }
|
|
33
30
|
|
|
34
31
|
// True if the value is an instance of a class that exposes a numeric-percent
|
|
35
32
|
// `lerp(other, percent)` method (Vector2, Color, or any future class).
|
|
@@ -57,9 +54,9 @@ class Tween
|
|
|
57
54
|
* callback receives the interpolated value (a number, or a fresh instance
|
|
58
55
|
* for lerp-able types). Both endpoints must be the same type.
|
|
59
56
|
* @param {function(NonNullable<T>):void} callback - Called with the interpolated value each frame
|
|
60
|
-
* @param {T} [start
|
|
61
|
-
* @param {T} [end
|
|
62
|
-
* @param {number} [duration
|
|
57
|
+
* @param {T} [start] - Starting value
|
|
58
|
+
* @param {T} [end] - Ending value
|
|
59
|
+
* @param {number} [duration] - Duration in seconds
|
|
63
60
|
* @param {Object} [options]
|
|
64
61
|
* @param {function(number):number} [options.ease] - Easing function (defaults to LINEAR)
|
|
65
62
|
* @param {boolean} [options.useRealTime=false] - Advance even when the game is paused (matches Timer's useRealTime)
|
|
@@ -111,9 +108,12 @@ class Tween
|
|
|
111
108
|
/** Remaining iterations including the current run (loop/pingPong only).
|
|
112
109
|
* @private */
|
|
113
110
|
this.loopRemaining = 0;
|
|
114
|
-
/** Whether it is
|
|
111
|
+
/** Whether it is running, see isActive
|
|
115
112
|
* @private */
|
|
116
113
|
this.active = false;
|
|
114
|
+
/** Whether it is in the active list, which a stopped one leaves at the next update
|
|
115
|
+
* @private */
|
|
116
|
+
this.listed = false;
|
|
117
117
|
/** The update it was started in, it first moves on the one after
|
|
118
118
|
* @private */
|
|
119
119
|
this.activePass = 0;
|
|
@@ -134,7 +134,7 @@ class Tween
|
|
|
134
134
|
|
|
135
135
|
/** Set the easing curve and return this for chaining.
|
|
136
136
|
* @param {function(number):number} easeFn
|
|
137
|
-
* @
|
|
137
|
+
* @return {Tween<T>} */
|
|
138
138
|
setEase(easeFn)
|
|
139
139
|
{
|
|
140
140
|
this.ease = easeFn;
|
|
@@ -149,7 +149,7 @@ class Tween
|
|
|
149
149
|
* - It is kept by `restart`, so a restarted tween calls it again when it completes
|
|
150
150
|
* - `stop` and `tweenStopAll` end a tween without calling it
|
|
151
151
|
* @param {function():void} callback
|
|
152
|
-
* @
|
|
152
|
+
* @return {Tween<T>} */
|
|
153
153
|
then(callback)
|
|
154
154
|
{
|
|
155
155
|
this.onComplete = callback;
|
|
@@ -165,7 +165,7 @@ class Tween
|
|
|
165
165
|
* A `then` callback, set before or after, is called when the last
|
|
166
166
|
* iteration ends.
|
|
167
167
|
* @param {number} [count=Infinity]
|
|
168
|
-
* @
|
|
168
|
+
* @return {Tween<T>} */
|
|
169
169
|
loop(count = Infinity)
|
|
170
170
|
{
|
|
171
171
|
this.loopRemaining = count;
|
|
@@ -180,7 +180,7 @@ class Tween
|
|
|
180
180
|
* A `then` callback, set before or after, is called when the last
|
|
181
181
|
* iteration ends.
|
|
182
182
|
* @param {number} [count=Infinity]
|
|
183
|
-
* @
|
|
183
|
+
* @return {Tween<T>} */
|
|
184
184
|
pingPong(count = Infinity)
|
|
185
185
|
{
|
|
186
186
|
this.loopRemaining = count;
|
|
@@ -213,7 +213,7 @@ class Tween
|
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
/** True if this tween is in the active list and not paused.
|
|
216
|
-
* @
|
|
216
|
+
* @return {boolean} */
|
|
217
217
|
isActive()
|
|
218
218
|
{
|
|
219
219
|
return !this.paused && this.active;
|
|
@@ -221,7 +221,7 @@ class Tween
|
|
|
221
221
|
|
|
222
222
|
/** Get how far this tween has progressed, from 0 (just started) to 1
|
|
223
223
|
* (completed). Clamped — overshoot past completion still reads 1.
|
|
224
|
-
* @
|
|
224
|
+
* @return {number} */
|
|
225
225
|
getPercent()
|
|
226
226
|
{
|
|
227
227
|
return percent(this.duration - this.life, 0, this.duration);
|
|
@@ -230,7 +230,7 @@ class Tween
|
|
|
230
230
|
/** Get the current interpolated value (the value most recently passed to
|
|
231
231
|
* the callback). Returns a number, Vector2, Vector3 or Color depending on the
|
|
232
232
|
* tween's start/end types.
|
|
233
|
-
* @
|
|
233
|
+
* @return {T} */
|
|
234
234
|
getValue()
|
|
235
235
|
{
|
|
236
236
|
return this.interp(this.life);
|
|
@@ -242,7 +242,7 @@ class Tween
|
|
|
242
242
|
* - A vector goes past its ends as far as the easing does, as a number does; a Color stays between them,
|
|
243
243
|
* so its channels stay in range, and any other type goes as far as its own lerp takes it
|
|
244
244
|
* @param {number} life
|
|
245
|
-
* @
|
|
245
|
+
* @return {T} */
|
|
246
246
|
interp(life)
|
|
247
247
|
{
|
|
248
248
|
// the ends of whatever type it tweens, each kind is handled below
|
|
@@ -285,44 +285,44 @@ const Ease =
|
|
|
285
285
|
{
|
|
286
286
|
/** Linear (identity) curve.
|
|
287
287
|
* @param {number} x
|
|
288
|
-
* @
|
|
288
|
+
* @return {number}
|
|
289
289
|
* @memberof TweenSystem.Ease */
|
|
290
290
|
LINEAR: (x) => x,
|
|
291
291
|
|
|
292
292
|
/** Power curve factory: `Ease.POWER(n)` returns `x => x**n`.
|
|
293
293
|
* Use n=2 for quadratic, n=3 for cubic, etc.
|
|
294
294
|
* @param {number} n
|
|
295
|
-
* @
|
|
295
|
+
* @return {function(number):number}
|
|
296
296
|
* @memberof TweenSystem.Ease */
|
|
297
297
|
POWER: (n) => (x) => x ** n,
|
|
298
298
|
|
|
299
299
|
/** Sine ease-in curve: starts slow, ends fast.
|
|
300
300
|
* @param {number} x
|
|
301
|
-
* @
|
|
301
|
+
* @return {number}
|
|
302
302
|
* @memberof TweenSystem.Ease */
|
|
303
303
|
SINE: (x) => 1 - cos(x * (PI / 2)),
|
|
304
304
|
|
|
305
305
|
/** Circular ease-in curve.
|
|
306
306
|
* @param {number} x
|
|
307
|
-
* @
|
|
307
|
+
* @return {number}
|
|
308
308
|
* @memberof TweenSystem.Ease */
|
|
309
309
|
CIRC: (x) => 1 - (1 - x * x)**.5,
|
|
310
310
|
|
|
311
311
|
/** Exponential ease-in curve (`2^(10x-10)`).
|
|
312
312
|
* @param {number} x
|
|
313
|
-
* @
|
|
313
|
+
* @return {number}
|
|
314
314
|
* @memberof TweenSystem.Ease */
|
|
315
315
|
EXPO: (x) => x === 0 ? 0 : 2 ** (10 * x - 10),
|
|
316
316
|
|
|
317
317
|
/** Back ease-in: overshoots backward at the start before snapping forward.
|
|
318
318
|
* @param {number} x
|
|
319
|
-
* @
|
|
319
|
+
* @return {number}
|
|
320
320
|
* @memberof TweenSystem.Ease */
|
|
321
321
|
BACK: (x) => x * x * (2.70158 * x - 1.70158),
|
|
322
322
|
|
|
323
323
|
/** Elastic ease-in: oscillations that grow toward the end.
|
|
324
324
|
* @param {number} x
|
|
325
|
-
* @
|
|
325
|
+
* @return {number}
|
|
326
326
|
* @memberof TweenSystem.Ease */
|
|
327
327
|
ELASTIC: (x) =>
|
|
328
328
|
x === 0 ? 0 :
|
|
@@ -332,7 +332,7 @@ const Ease =
|
|
|
332
332
|
/** Spring ease-in: wobbles around the start before springing to the end;
|
|
333
333
|
* `Ease.OUT(Ease.SPRING)` overshoots and settles on the target.
|
|
334
334
|
* @param {number} x
|
|
335
|
-
* @
|
|
335
|
+
* @return {number}
|
|
336
336
|
* @memberof TweenSystem.Ease */
|
|
337
337
|
SPRING: (x) =>
|
|
338
338
|
1 -
|
|
@@ -346,7 +346,7 @@ const Ease =
|
|
|
346
346
|
* classic "object falls and hits the ground" shape (bounces near x=1),
|
|
347
347
|
* wrap with `Ease.OUT`: `Ease.OUT(Ease.BOUNCE)`.
|
|
348
348
|
* @param {number} x
|
|
349
|
-
* @
|
|
349
|
+
* @return {number}
|
|
350
350
|
* @memberof TweenSystem.Ease
|
|
351
351
|
* @example
|
|
352
352
|
* Ease.BOUNCE // ease-in bounce (bouncy at start)
|
|
@@ -369,7 +369,7 @@ const Ease =
|
|
|
369
369
|
* convention, so wrapping a curve in `IN` is a no-op — useful when
|
|
370
370
|
* picking the direction programmatically.
|
|
371
371
|
* @param {function(number):number} f - Curve to use as ease-in (returned unchanged)
|
|
372
|
-
* @
|
|
372
|
+
* @return {function(number):number}
|
|
373
373
|
* @memberof TweenSystem.Ease
|
|
374
374
|
* @example
|
|
375
375
|
* // Pick direction at runtime
|
|
@@ -380,7 +380,7 @@ const Ease =
|
|
|
380
380
|
|
|
381
381
|
/** Reverse a curve so it eases out instead of in: `x => 1 - f(1 - x)`.
|
|
382
382
|
* @param {function(number):number} f
|
|
383
|
-
* @
|
|
383
|
+
* @return {function(number):number}
|
|
384
384
|
* @memberof TweenSystem.Ease
|
|
385
385
|
* @example
|
|
386
386
|
* Ease.OUT(Ease.POWER(2)) // ease-out quadratic
|
|
@@ -389,7 +389,7 @@ const Ease =
|
|
|
389
389
|
|
|
390
390
|
/** Combine the first half of `f` with `Ease.OUT(f)` for a symmetric curve.
|
|
391
391
|
* @param {function(number):number} f
|
|
392
|
-
* @
|
|
392
|
+
* @return {function(number):number}
|
|
393
393
|
* @memberof TweenSystem.Ease */
|
|
394
394
|
IN_OUT: (f) => Ease.PIECEWISE(f, Ease.OUT(f)),
|
|
395
395
|
|
|
@@ -397,7 +397,7 @@ const Ease =
|
|
|
397
397
|
* Each curve is mapped to its section: section i runs over [i/n, (i+1)/n]
|
|
398
398
|
* and its output is mapped to [i/n, (i+1)/n] of the overall range.
|
|
399
399
|
* @param {...function(number):number} fns
|
|
400
|
-
* @
|
|
400
|
+
* @return {function(number):number}
|
|
401
401
|
* @memberof TweenSystem.Ease */
|
|
402
402
|
PIECEWISE: (...fns) =>
|
|
403
403
|
{
|
|
@@ -415,7 +415,7 @@ const Ease =
|
|
|
415
415
|
* @param {number} y1
|
|
416
416
|
* @param {number} x2
|
|
417
417
|
* @param {number} y2
|
|
418
|
-
* @
|
|
418
|
+
* @return {function(number):number}
|
|
419
419
|
* @memberof TweenSystem.Ease
|
|
420
420
|
* @example
|
|
421
421
|
* Ease.BEZIER(0.25, 0.1, 0.25, 1) // CSS "ease"
|
|
@@ -465,12 +465,12 @@ const Ease =
|
|
|
465
465
|
* @param {string} propertyPath - Dot-separated path, e.g. `'pos.x'` or `'color'`
|
|
466
466
|
* @param {T} start - Starting value
|
|
467
467
|
* @param {T} end - Ending value
|
|
468
|
-
* @param {number} [duration
|
|
468
|
+
* @param {number} [duration] - Duration in seconds
|
|
469
469
|
* @param {Object} [options] - Same options as the Tween constructor
|
|
470
470
|
* @param {function(number):number} [options.ease] - Easing function (defaults to LINEAR)
|
|
471
471
|
* @param {boolean} [options.useRealTime=false] - Advance even when the game is paused
|
|
472
472
|
* @param {boolean} [options.paused=false] - Start in paused state
|
|
473
|
-
* @
|
|
473
|
+
* @return {Tween<T>}
|
|
474
474
|
* @memberof TweenSystem
|
|
475
475
|
* @example
|
|
476
476
|
* // Numeric: slide an object's x with an ease-out sine curve
|
|
@@ -591,8 +591,13 @@ function tweenUpdate(gameDelta, realDelta)
|
|
|
591
591
|
// from the next update. Newest first, as the list has always been walked.
|
|
592
592
|
// a callback that calls tweenUpdate itself gets a list of its own, the outer update is still walking this one
|
|
593
593
|
const list = tweenUpdateList.length ? [] : tweenUpdateList, pass = ++tweenUpdatePass;
|
|
594
|
+
let kept = 0;
|
|
594
595
|
for (const t of tweenActive)
|
|
595
|
-
|
|
596
|
+
if (t.active)
|
|
597
|
+
list.push(tweenActive[kept++] = t);
|
|
598
|
+
else
|
|
599
|
+
t.listed = false; // stopped since the last update, out of the list now
|
|
600
|
+
tweenActive.length = kept;
|
|
596
601
|
// the list is let go however the walk ends: a callback that throws must not leave it held, or every
|
|
597
602
|
// update after would take it for an update still going and make a list of its own
|
|
598
603
|
try
|
|
@@ -645,7 +650,7 @@ function tweenUpdate(gameDelta, realDelta)
|
|
|
645
650
|
function tweenStopAll()
|
|
646
651
|
{
|
|
647
652
|
for (const t of tweenActive)
|
|
648
|
-
t.thenCallback = undefined, t.active = false;
|
|
653
|
+
t.thenCallback = undefined, t.active = t.listed = false;
|
|
649
654
|
tweenActive.length = 0;
|
|
650
655
|
}
|
|
651
656
|
|
package/plugins/uiSystem.js
CHANGED
|
@@ -35,8 +35,14 @@ let uiDebug = 0;
|
|
|
35
35
|
function uiSetDebug(debugMode)
|
|
36
36
|
{ uiDebug = typeof debugMode === 'boolean' ? (debugMode ? 1 : 0) : debugMode; }
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* @callback DragAndDropCallback - Callback for drag and drop events
|
|
40
|
+
* @param {DragEvent} event - The drag event
|
|
41
|
+
* @memberof UISystem
|
|
42
|
+
*/
|
|
43
|
+
|
|
38
44
|
///////////////////////////////////////////////////////////////////////////////
|
|
39
|
-
/**
|
|
45
|
+
/**
|
|
40
46
|
* UI System Global Object
|
|
41
47
|
* @memberof UISystem
|
|
42
48
|
*/
|
|
@@ -513,7 +519,7 @@ class UISystemPlugin
|
|
|
513
519
|
* @param {'left'|'center'|'right'} [align]
|
|
514
520
|
* @param {string} [font=uiSystem.defaultFont]
|
|
515
521
|
* @param {string} [fontStyle]
|
|
516
|
-
* @param {boolean} [applyMaxWidth
|
|
522
|
+
* @param {boolean} [applyMaxWidth]
|
|
517
523
|
* @param {Vector2} [textShadow]
|
|
518
524
|
* @param {Color} [shadowColor]
|
|
519
525
|
* @param {number} [shadowBlur]
|
|
@@ -528,12 +534,6 @@ class UISystemPlugin
|
|
|
528
534
|
context.shadowColor = '#0000';
|
|
529
535
|
}
|
|
530
536
|
|
|
531
|
-
/**
|
|
532
|
-
* @callback DragAndDropCallback - Callback for drag and drop events
|
|
533
|
-
* @param {DragEvent} event - The drag event
|
|
534
|
-
* @memberof UISystem
|
|
535
|
-
*/
|
|
536
|
-
|
|
537
537
|
/** Setup drag and drop event handlers
|
|
538
538
|
* Automatically prevents defaults and calls the given functions
|
|
539
539
|
* @param {DragAndDropCallback} [onDrop] - when a file is dropped
|
|
@@ -1212,19 +1212,24 @@ class UIObject
|
|
|
1212
1212
|
/** Called when the navigation button is pressed on this object */
|
|
1213
1213
|
navigatePressed() { this.click(); }
|
|
1214
1214
|
|
|
1215
|
-
/**
|
|
1215
|
+
/** Is the mouse hovering over this element
|
|
1216
|
+
* @return {boolean} */
|
|
1216
1217
|
isHoverObject() { return uiSystem.hoverObject === this; }
|
|
1217
1218
|
|
|
1218
|
-
/**
|
|
1219
|
+
/** Is the mouse held onto this element
|
|
1220
|
+
* @return {boolean} */
|
|
1219
1221
|
isActiveObject() { return uiSystem.activeObject === this; }
|
|
1220
1222
|
|
|
1221
|
-
/**
|
|
1223
|
+
/** Is the gamepad or keyboard navigation object
|
|
1224
|
+
* @return {boolean} */
|
|
1222
1225
|
isNavigationObject() { return uiSystem.navigationObject === this; }
|
|
1223
1226
|
|
|
1224
|
-
/**
|
|
1227
|
+
/** Is this object in keyboard input mode
|
|
1228
|
+
* @return {boolean} */
|
|
1225
1229
|
isKeyInputObject() { return uiSystem.keyInputObject === this; }
|
|
1226
1230
|
|
|
1227
|
-
/**
|
|
1231
|
+
/** Can it be interacted with, it and every parent visible and enabled
|
|
1232
|
+
* @return {boolean} */
|
|
1228
1233
|
isInteractive() { return this.interactive && uiObjectIsUsable(this); }
|
|
1229
1234
|
|
|
1230
1235
|
/** Returns string containing info about this object for debugging
|
|
@@ -1755,9 +1760,9 @@ class UIVideo extends UIObject
|
|
|
1755
1760
|
* @param {Vector2} pos
|
|
1756
1761
|
* @param {Vector2} size
|
|
1757
1762
|
* @param {string} src - Video file path or URL
|
|
1758
|
-
* @param {boolean} [autoplay
|
|
1759
|
-
* @param {boolean} [loop
|
|
1760
|
-
* @param {number} [volume
|
|
1763
|
+
* @param {boolean} [autoplay] - Start playing immediately?
|
|
1764
|
+
* @param {boolean} [loop] - Loop the video?
|
|
1765
|
+
* @param {number} [volume] - Volume percent scaled by global volume (0-1)
|
|
1761
1766
|
*/
|
|
1762
1767
|
constructor(pos, size, src, autoplay=false, loop=false, volume=1)
|
|
1763
1768
|
{
|
|
@@ -1912,10 +1917,10 @@ class UILayout extends UIObject
|
|
|
1912
1917
|
{
|
|
1913
1918
|
/** Create a UILayout container that auto-arranges children
|
|
1914
1919
|
* @param {Vector2} [pos]
|
|
1915
|
-
* @param {number} [columns
|
|
1916
|
-
* @param {number} [gap
|
|
1917
|
-
* @param {number} [padding
|
|
1918
|
-
* @param {boolean} [transparent
|
|
1920
|
+
* @param {number} [columns] - Number of columns (1 = vertical list)
|
|
1921
|
+
* @param {number} [gap] - Space between children
|
|
1922
|
+
* @param {number} [padding] - Space between container border and children
|
|
1923
|
+
* @param {boolean} [transparent] - If true, draws no background, outline, or shadow
|
|
1919
1924
|
*/
|
|
1920
1925
|
constructor(pos, columns=1, gap=10, padding=10, transparent=false)
|
|
1921
1926
|
{
|
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.
|
|
35
|
+
const engineVersion = '1.25.0';
|
|
36
36
|
|
|
37
37
|
/** Frames per second to update
|
|
38
38
|
* @type {number}
|
|
@@ -165,7 +165,7 @@ let timeFixedStart = 0, frameFixedStart = 0;
|
|
|
165
165
|
let windowWidthLast = 0, windowHeightLast = 0, windowPixelRatioLast = 0;
|
|
166
166
|
let engineUpdateInternal; // assigned by engineInit so engineStep can drive it
|
|
167
167
|
let engineFrameScheduled = false; // a frame of the loop is asked for and has not run yet
|
|
168
|
-
let
|
|
168
|
+
let engineFrameErrorLast; // the last error a release build's loop went on past, as text
|
|
169
169
|
|
|
170
170
|
// the pairs of objects asked about a collision this update, so the other's own physics does not ask again: each
|
|
171
171
|
// asker's others, with true for a pair both said to resolve, and false for one left overlapping, ignored or only
|
|
@@ -319,7 +319,8 @@ function engineLoadingScreenDraw(elapsed)
|
|
|
319
319
|
* ()=> { drawHUD(); }, // gameRenderPost
|
|
320
320
|
* ['tiles.png', 'tilesLevel.png'] // images to load
|
|
321
321
|
* );
|
|
322
|
-
* @memberof Engine
|
|
322
|
+
* @memberof Engine
|
|
323
|
+
* @return {Promise<void>} - Done when the images have loaded and gameInit has run */
|
|
323
324
|
async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement)
|
|
324
325
|
{
|
|
325
326
|
showEngineVersion && console.log(`${engineName} Engine v${engineVersion}`);
|
|
@@ -419,20 +420,30 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
419
420
|
frameTimeBufferMS = 0;
|
|
420
421
|
}
|
|
421
422
|
|
|
422
|
-
// update multiple frames if necessary in case of slow framerate
|
|
423
|
-
|
|
423
|
+
// update multiple frames if necessary in case of slow framerate; a tick that throws still spends its time
|
|
424
|
+
// and the smoothing still comes back, so a release build going on past an error each tick keeps its rate
|
|
425
|
+
try
|
|
424
426
|
{
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
427
|
+
while (frameTimeBufferMS >= 0)
|
|
428
|
+
{
|
|
429
|
+
try
|
|
430
|
+
{
|
|
431
|
+
// read again each tick, so a pause set by the game stops the rest of this frame's catch-up ticks
|
|
432
|
+
const frozenTick = paused || !(timeScale * debugScale);
|
|
433
|
+
|
|
434
|
+
// increment frame and update time, frozen does not advance time
|
|
435
|
+
if (!frozenTick)
|
|
436
|
+
time = timeFixedStart + (frame++ - frameFixedStart) / frameRate;
|
|
437
|
+
engineTick(frozenTick);
|
|
438
|
+
}
|
|
439
|
+
finally { frameTimeBufferMS -= 1e3 / frameRate; }
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
finally
|
|
443
|
+
{
|
|
444
|
+
// add the time smoothing back in
|
|
445
|
+
frameTimeBufferMS += deltaSmooth;
|
|
432
446
|
}
|
|
433
|
-
|
|
434
|
-
// add the time smoothing back in
|
|
435
|
-
frameTimeBufferMS += deltaSmooth;
|
|
436
447
|
}
|
|
437
448
|
|
|
438
449
|
// one tick of the loop: update game and objects, when frozen update everything except them
|
|
@@ -729,9 +740,13 @@ function engineScheduleFrame()
|
|
|
729
740
|
try { engineUpdateInternal(frameTimeMS); }
|
|
730
741
|
catch (error)
|
|
731
742
|
{
|
|
732
|
-
// a release build goes on past an error in a frame, a frozen game is the worst a player can get;
|
|
733
|
-
//
|
|
734
|
-
|
|
743
|
+
// a release build goes on past an error in a frame, a frozen game is the worst a player can get; an error
|
|
744
|
+
// is logged when it is not the last one again, one every frame would flood the console
|
|
745
|
+
const text = String(error);
|
|
746
|
+
text === engineFrameErrorLast || console.error(error);
|
|
747
|
+
engineFrameErrorLast = text;
|
|
748
|
+
// the frame's input is cleared as its tick would have, or a key press that threw would be pressed again
|
|
749
|
+
inputUpdatePost();
|
|
735
750
|
engineScheduleFrame();
|
|
736
751
|
}
|
|
737
752
|
};
|
|
@@ -805,9 +820,12 @@ function engineObjectsUpdate()
|
|
|
805
820
|
const start = engineChildStack.length;
|
|
806
821
|
for (const child of children)
|
|
807
822
|
engineChildStack.push(child);
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
823
|
+
try
|
|
824
|
+
{
|
|
825
|
+
for (let i = start; i < engineChildStack.length; ++i)
|
|
826
|
+
updateChildObject(engineChildStack[i]);
|
|
827
|
+
}
|
|
828
|
+
finally { engineChildStack.length = start; } // put back when an update throws too, or it keeps them
|
|
811
829
|
}
|
|
812
830
|
const pass = engineObjectsUpdateCount;
|
|
813
831
|
function updateChildObject(o)
|