littlejsengine 1.24.3 → 1.26.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 +123 -0
- package/EDITOR.md +2 -2
- package/FAQ.md +55 -11
- package/README.md +6 -2
- package/REFERENCE.md +203 -57
- package/dist/littlejs.d.ts +933 -313
- package/dist/littlejs.esm.js +5143 -2419
- package/dist/littlejs.esm.min.js +38 -24
- package/dist/littlejs.js +5132 -2416
- package/dist/littlejs.min.js +37 -23
- package/dist/littlejs.release.js +4905 -2232
- package/package.json +4 -4
- package/plugins/box2d.js +141 -56
- package/plugins/drawUtilities.js +374 -380
- package/plugins/gltf.js +383 -60
- package/plugins/lightSystem.js +412 -30
- package/plugins/math3d.js +1015 -1014
- package/plugins/medalSystem.js +19 -7
- package/plugins/newgrounds.js +5 -6
- package/plugins/parallax.js +1 -0
- package/plugins/particleEffects.js +47 -14
- package/plugins/pathFinder.js +60 -38
- package/plugins/pluginExport.js +11 -0
- package/plugins/postProcess.js +200 -67
- package/plugins/render3d.js +493 -2391
- package/plugins/render3dDebug.js +5 -8
- package/plugins/render3dEditor.js +84 -9
- package/plugins/render3dExtras.js +122 -33
- package/plugins/render3dLevel.js +67 -8
- package/plugins/render3dMesh.js +1158 -0
- package/plugins/render3dObject.js +1451 -0
- package/plugins/render3dVoxels.js +15 -3
- package/plugins/textureSheet.js +9 -8
- package/plugins/threejs.js +1 -1
- package/plugins/tweenSystem.js +117 -87
- package/plugins/uiSystem.js +62 -28
- package/plugins/wavedash.js +229 -0
- package/src/engine.js +192 -40
- package/src/engineAudio.js +47 -27
- package/src/engineBuild.mjs +6 -1
- package/src/engineDebug.js +23 -162
- package/src/engineDraw.js +148 -43
- package/src/engineEditor.js +153 -40
- package/src/engineExport.js +0 -3
- package/src/engineInput.js +31 -25
- package/src/engineMath.js +38 -20
- package/src/engineObject.js +68 -50
- package/src/engineParticles.js +66 -18
- package/src/engineRelease.js +18 -15
- package/src/engineSettings.js +48 -9
- package/src/engineTileLayer.js +44 -18
- package/src/engineUtilities.js +56 -11
- package/src/engineWebGL.js +68 -22
|
@@ -88,9 +88,11 @@ class VoxelMap extends EngineObject3D
|
|
|
88
88
|
this.chunkTransparentMeshes = []; // each chunk's transparent blocks, drawn blended
|
|
89
89
|
/** @type {Array<Vector3>} */
|
|
90
90
|
this.chunkCenters = []; // a chunk mesh's points are around its center, which it is drawn at
|
|
91
|
+
/** @type {Set<number>} */
|
|
91
92
|
this.chunksChanged = new Set;
|
|
92
93
|
/** @type {Array<{faces: Array<number>, seeThrough: boolean, transparent: boolean, doubleSided: boolean}|undefined>} */
|
|
93
94
|
this.blockTypes = [];
|
|
95
|
+
/** @type {Map<number, Array<Vector2>>} */
|
|
94
96
|
this.tiles = new Map; // tile index to the uvs of its corners
|
|
95
97
|
|
|
96
98
|
// the transparent blocks draw in the transparent stage, blended and sorted, through a child that draws them
|
|
@@ -98,6 +100,7 @@ class VoxelMap extends EngineObject3D
|
|
|
98
100
|
glass.size3D = vec3(); // not a thing to collect either
|
|
99
101
|
glass.transparent = true;
|
|
100
102
|
glass.render3D = ()=> this.renderChunks(true);
|
|
103
|
+
render3DShareSettings(glass, this); // it draws as the map does, its material, stage and render order
|
|
101
104
|
this.addChild(glass);
|
|
102
105
|
render3DLevel.push(this);
|
|
103
106
|
}
|
|
@@ -473,15 +476,24 @@ class VoxelMap extends EngineObject3D
|
|
|
473
476
|
levelRaycast3D(ray) { return this.raycast(ray)?.distance; }
|
|
474
477
|
|
|
475
478
|
/** Where a short move goes into a block, for a particle's move in one frame: the part of the move made before it,
|
|
476
|
-
* 0 to 1, and the normal of the face it comes in through; undefined when it hits none, or starts inside one
|
|
479
|
+
* 0 to 1, and the normal of the face it comes in through; undefined when it hits none, or starts inside one; a
|
|
480
|
+
* move that starts on a block's face, coming in, hits it at 0
|
|
477
481
|
* @param {Vector3} from
|
|
478
482
|
* @param {Vector3} to
|
|
479
483
|
* @return {{distance: number, normal: Vector3}|undefined}
|
|
480
484
|
* @ignore */
|
|
481
485
|
levelSegment3D(from, to)
|
|
482
486
|
{
|
|
483
|
-
const hit = this.raycast(new Ray3D(from,
|
|
484
|
-
|
|
487
|
+
const move = to.subtract(from), hit = this.raycast(new Ray3D(from, move), 1);
|
|
488
|
+
if (!hit) return;
|
|
489
|
+
if (!hit.distance)
|
|
490
|
+
{
|
|
491
|
+
// met at its start: on a face when the point just behind the start is clear, inside a block when not
|
|
492
|
+
const behind = from.subtract(move.normalize(1e-6)).subtract(this.pos3D);
|
|
493
|
+
if (this.voxelAt(floor(behind.x), floor(behind.y), floor(behind.z)))
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
return {distance: hit.distance, normal: hit.normal};
|
|
485
497
|
}
|
|
486
498
|
|
|
487
499
|
/** Keeps an eye on its placement, called automatically each frame */
|
package/plugins/textureSheet.js
CHANGED
|
@@ -263,7 +263,7 @@ function loadSprite(src, frameSize, padding=textureSheetPadding, sourcePadding=0
|
|
|
263
263
|
console.warn('loadSprite failed to load image:', src);
|
|
264
264
|
textureSheetLoadFailed(key, tileInfo);
|
|
265
265
|
}
|
|
266
|
-
});
|
|
266
|
+
}, ()=> textureSheetLoadFailed(key, tileInfo));
|
|
267
267
|
|
|
268
268
|
return tileInfo;
|
|
269
269
|
}
|
|
@@ -329,7 +329,7 @@ function loadTiles(sources, tileSize=tileDefaultSize, padding=textureSheetPaddin
|
|
|
329
329
|
added.sheet.drawImage(image, added.tile, false); // upload once per batch
|
|
330
330
|
for (let k = 0; k < count; ++k)
|
|
331
331
|
set.tiles.push(added.tile.frame(k));
|
|
332
|
-
});
|
|
332
|
+
}, ()=> textureSheetLoadFailed(key, set));
|
|
333
333
|
}
|
|
334
334
|
return set;
|
|
335
335
|
}
|
|
@@ -434,20 +434,21 @@ function loadAtlas(imageSrc, jsonSrc, padding=textureSheetPadding)
|
|
|
434
434
|
console.warn('loadAtlas failed to load:', imageSrc, jsonSrc);
|
|
435
435
|
textureSheetLoadFailed(key, atlas);
|
|
436
436
|
}
|
|
437
|
-
});
|
|
437
|
+
}, ()=> textureSheetLoadFailed(key, atlas));
|
|
438
438
|
|
|
439
439
|
return atlas;
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
-
// run a load in the queue: a load that throws is reported
|
|
443
|
-
//
|
|
444
|
-
|
|
442
|
+
// run a load in the queue: a load that throws is reported, let go of so the next call loads it again (failed), as
|
|
443
|
+
// one whose file did not load is, and the loads after it carry on; the pending count always comes back down, and the
|
|
444
|
+
// sheets upload to webgl once per batch, when the last pending load finishes
|
|
445
|
+
function textureSheetQueueJob(name, job, failed)
|
|
445
446
|
{
|
|
446
447
|
++textureSheetPendingCount;
|
|
447
448
|
textureSheetQueue = textureSheetQueue.then(async ()=>
|
|
448
449
|
{
|
|
449
450
|
try { await job(); }
|
|
450
|
-
catch (e) { console.error(name + ' failed:', e); }
|
|
451
|
+
catch (e) { console.error(name + ' failed:', e); failed?.(); }
|
|
451
452
|
finally
|
|
452
453
|
{
|
|
453
454
|
if (!--textureSheetPendingCount)
|
|
@@ -464,7 +465,7 @@ function textureSheetQueueJob(name, job)
|
|
|
464
465
|
* - Accepts TexturePacker json (hash and array) and Aseprite json
|
|
465
466
|
* - Frames tagged in Aseprite or named like run_0, run_1 group into animations
|
|
466
467
|
* @param {Object} data - Parsed atlas json data
|
|
467
|
-
* @return {Array<Object>} List of {name, frames} groups in atlas order
|
|
468
|
+
* @return {Array<{name: string, frames: Array<Object>}>} List of {name, frames} groups in atlas order
|
|
468
469
|
* @memberof TextureSheets */
|
|
469
470
|
function parseAtlas(data)
|
|
470
471
|
{
|
package/plugins/threejs.js
CHANGED
|
@@ -65,7 +65,7 @@ class ThreeJSPlugin
|
|
|
65
65
|
rootElement.insertBefore(threeCanvas, rootElement.firstChild);
|
|
66
66
|
threeCanvas.style.cssText = mainCanvas.style.cssText;
|
|
67
67
|
|
|
68
|
-
// composite the 3D canvas into screenshots
|
|
68
|
+
// composite the 3D canvas into screenshots
|
|
69
69
|
setBackgroundCanvas(threeCanvas);
|
|
70
70
|
|
|
71
71
|
// render automatically each frame after the engine renders
|
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,13 +23,23 @@ 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
|
+
tween.listed || tweenActive.push(tween); // one stopped this update is still listed, in its place
|
|
27
|
+
tween.listed = true;
|
|
26
28
|
}
|
|
27
|
-
function tweenDeactivate(tween)
|
|
29
|
+
function tweenDeactivate(tween) { tween.active = false; }
|
|
30
|
+
|
|
31
|
+
// call a tween's callback with its value at life; one that throws, its easing too, is ended, or it would be called and
|
|
32
|
+
// throw at every update, unless the callback itself started it again, which is its own new run (pass, the update it
|
|
33
|
+
// is in, undefined outside one)
|
|
34
|
+
function tweenCall(tween, life, pass)
|
|
28
35
|
{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
try { tween.callback(tween.interp(life)); }
|
|
37
|
+
catch (error)
|
|
38
|
+
{
|
|
39
|
+
if (tween.active && (pass === undefined || tween.activePass < pass))
|
|
40
|
+
tweenDeactivate(tween);
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
32
43
|
}
|
|
33
44
|
|
|
34
45
|
// True if the value is an instance of a class that exposes a numeric-percent
|
|
@@ -57,9 +68,9 @@ class Tween
|
|
|
57
68
|
* callback receives the interpolated value (a number, or a fresh instance
|
|
58
69
|
* for lerp-able types). Both endpoints must be the same type.
|
|
59
70
|
* @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
|
|
71
|
+
* @param {T} [start] - Starting value
|
|
72
|
+
* @param {T} [end] - Ending value
|
|
73
|
+
* @param {number} [duration] - Duration in seconds
|
|
63
74
|
* @param {Object} [options]
|
|
64
75
|
* @param {function(number):number} [options.ease] - Easing function (defaults to LINEAR)
|
|
65
76
|
* @param {boolean} [options.useRealTime=false] - Advance even when the game is paused (matches Timer's useRealTime)
|
|
@@ -105,15 +116,18 @@ class Tween
|
|
|
105
116
|
* @type {undefined|function():void} */
|
|
106
117
|
this.onComplete = undefined;
|
|
107
118
|
|
|
108
|
-
/** Continuation when a pass ends, set by loop() and pingPong() to start the next iteration
|
|
119
|
+
/** Continuation when a pass ends, set by loop() and pingPong() to start the next iteration
|
|
109
120
|
* @private */
|
|
110
121
|
this.thenCallback = undefined;
|
|
111
|
-
/** Remaining iterations including the current run (loop/pingPong only)
|
|
122
|
+
/** Remaining iterations including the current run (loop/pingPong only)
|
|
112
123
|
* @private */
|
|
113
124
|
this.loopRemaining = 0;
|
|
114
|
-
/** Whether it is
|
|
125
|
+
/** Whether it is running, see isActive
|
|
115
126
|
* @private */
|
|
116
127
|
this.active = false;
|
|
128
|
+
/** Whether it is in the active list, which a stopped one leaves at the next update
|
|
129
|
+
* @private */
|
|
130
|
+
this.listed = false;
|
|
117
131
|
/** The update it was started in, it first moves on the one after
|
|
118
132
|
* @private */
|
|
119
133
|
this.activePass = 0;
|
|
@@ -128,13 +142,13 @@ class Tween
|
|
|
128
142
|
this.target = undefined;
|
|
129
143
|
|
|
130
144
|
tweenActivate(this);
|
|
131
|
-
// Snap target to start immediately
|
|
132
|
-
|
|
145
|
+
// Snap target to start immediately; one that throws here is no tween, the game never gets it to stop
|
|
146
|
+
tweenCall(this, duration);
|
|
133
147
|
}
|
|
134
148
|
|
|
135
|
-
/** Set the easing curve and return this for chaining
|
|
149
|
+
/** Set the easing curve and return this for chaining
|
|
136
150
|
* @param {function(number):number} easeFn
|
|
137
|
-
* @
|
|
151
|
+
* @return {Tween<T>} */
|
|
138
152
|
setEase(easeFn)
|
|
139
153
|
{
|
|
140
154
|
this.ease = easeFn;
|
|
@@ -149,7 +163,7 @@ class Tween
|
|
|
149
163
|
* - It is kept by `restart`, so a restarted tween calls it again when it completes
|
|
150
164
|
* - `stop` and `tweenStopAll` end a tween without calling it
|
|
151
165
|
* @param {function():void} callback
|
|
152
|
-
* @
|
|
166
|
+
* @return {Tween<T>} */
|
|
153
167
|
then(callback)
|
|
154
168
|
{
|
|
155
169
|
this.onComplete = callback;
|
|
@@ -165,7 +179,7 @@ class Tween
|
|
|
165
179
|
* A `then` callback, set before or after, is called when the last
|
|
166
180
|
* iteration ends.
|
|
167
181
|
* @param {number} [count=Infinity]
|
|
168
|
-
* @
|
|
182
|
+
* @return {Tween<T>} */
|
|
169
183
|
loop(count = Infinity)
|
|
170
184
|
{
|
|
171
185
|
this.loopRemaining = count;
|
|
@@ -180,7 +194,7 @@ class Tween
|
|
|
180
194
|
* A `then` callback, set before or after, is called when the last
|
|
181
195
|
* iteration ends.
|
|
182
196
|
* @param {number} [count=Infinity]
|
|
183
|
-
* @
|
|
197
|
+
* @return {Tween<T>} */
|
|
184
198
|
pingPong(count = Infinity)
|
|
185
199
|
{
|
|
186
200
|
this.loopRemaining = count;
|
|
@@ -188,10 +202,10 @@ class Tween
|
|
|
188
202
|
return this;
|
|
189
203
|
}
|
|
190
204
|
|
|
191
|
-
/** Pause this tween. While paused, tweenUpdate skips it
|
|
205
|
+
/** Pause this tween. While paused, tweenUpdate skips it */
|
|
192
206
|
pause() { this.paused = true; }
|
|
193
207
|
|
|
194
|
-
/** Resume a paused tween
|
|
208
|
+
/** Resume a paused tween */
|
|
195
209
|
resume() { this.paused = false; }
|
|
196
210
|
|
|
197
211
|
/** Reset this tween to the start: life back to duration, pause cleared,
|
|
@@ -209,11 +223,11 @@ class Tween
|
|
|
209
223
|
this.lastTime = time;
|
|
210
224
|
this.lastTimeReal = timeReal;
|
|
211
225
|
tweenActivate(this);
|
|
212
|
-
|
|
226
|
+
tweenCall(this, this.duration);
|
|
213
227
|
}
|
|
214
228
|
|
|
215
|
-
/** True if this tween is in the active list and not paused
|
|
216
|
-
* @
|
|
229
|
+
/** True if this tween is in the active list and not paused
|
|
230
|
+
* @return {boolean} */
|
|
217
231
|
isActive()
|
|
218
232
|
{
|
|
219
233
|
return !this.paused && this.active;
|
|
@@ -221,7 +235,7 @@ class Tween
|
|
|
221
235
|
|
|
222
236
|
/** Get how far this tween has progressed, from 0 (just started) to 1
|
|
223
237
|
* (completed). Clamped — overshoot past completion still reads 1.
|
|
224
|
-
* @
|
|
238
|
+
* @return {number} */
|
|
225
239
|
getPercent()
|
|
226
240
|
{
|
|
227
241
|
return percent(this.duration - this.life, 0, this.duration);
|
|
@@ -230,7 +244,7 @@ class Tween
|
|
|
230
244
|
/** Get the current interpolated value (the value most recently passed to
|
|
231
245
|
* the callback). Returns a number, Vector2, Vector3 or Color depending on the
|
|
232
246
|
* tween's start/end types.
|
|
233
|
-
* @
|
|
247
|
+
* @return {T} */
|
|
234
248
|
getValue()
|
|
235
249
|
{
|
|
236
250
|
return this.interp(this.life);
|
|
@@ -242,7 +256,7 @@ class Tween
|
|
|
242
256
|
* - A vector goes past its ends as far as the easing does, as a number does; a Color stays between them,
|
|
243
257
|
* so its channels stay in range, and any other type goes as far as its own lerp takes it
|
|
244
258
|
* @param {number} life
|
|
245
|
-
* @
|
|
259
|
+
* @return {T} */
|
|
246
260
|
interp(life)
|
|
247
261
|
{
|
|
248
262
|
// the ends of whatever type it tweens, each kind is handled below
|
|
@@ -283,46 +297,46 @@ class Tween
|
|
|
283
297
|
*/
|
|
284
298
|
const Ease =
|
|
285
299
|
{
|
|
286
|
-
/** Linear (identity) curve
|
|
300
|
+
/** Linear (identity) curve
|
|
287
301
|
* @param {number} x
|
|
288
|
-
* @
|
|
302
|
+
* @return {number}
|
|
289
303
|
* @memberof TweenSystem.Ease */
|
|
290
304
|
LINEAR: (x) => x,
|
|
291
305
|
|
|
292
306
|
/** Power curve factory: `Ease.POWER(n)` returns `x => x**n`.
|
|
293
307
|
* Use n=2 for quadratic, n=3 for cubic, etc.
|
|
294
308
|
* @param {number} n
|
|
295
|
-
* @
|
|
309
|
+
* @return {function(number):number}
|
|
296
310
|
* @memberof TweenSystem.Ease */
|
|
297
311
|
POWER: (n) => (x) => x ** n,
|
|
298
312
|
|
|
299
|
-
/** Sine ease-in curve: starts slow, ends fast
|
|
313
|
+
/** Sine ease-in curve: starts slow, ends fast
|
|
300
314
|
* @param {number} x
|
|
301
|
-
* @
|
|
315
|
+
* @return {number}
|
|
302
316
|
* @memberof TweenSystem.Ease */
|
|
303
317
|
SINE: (x) => 1 - cos(x * (PI / 2)),
|
|
304
318
|
|
|
305
|
-
/** Circular ease-in curve
|
|
319
|
+
/** Circular ease-in curve
|
|
306
320
|
* @param {number} x
|
|
307
|
-
* @
|
|
321
|
+
* @return {number}
|
|
308
322
|
* @memberof TweenSystem.Ease */
|
|
309
323
|
CIRC: (x) => 1 - (1 - x * x)**.5,
|
|
310
324
|
|
|
311
|
-
/** Exponential ease-in curve (`2^(10x-10)`)
|
|
325
|
+
/** Exponential ease-in curve (`2^(10x-10)`)
|
|
312
326
|
* @param {number} x
|
|
313
|
-
* @
|
|
327
|
+
* @return {number}
|
|
314
328
|
* @memberof TweenSystem.Ease */
|
|
315
329
|
EXPO: (x) => x === 0 ? 0 : 2 ** (10 * x - 10),
|
|
316
330
|
|
|
317
|
-
/** Back ease-in: overshoots backward at the start before snapping forward
|
|
331
|
+
/** Back ease-in: overshoots backward at the start before snapping forward
|
|
318
332
|
* @param {number} x
|
|
319
|
-
* @
|
|
333
|
+
* @return {number}
|
|
320
334
|
* @memberof TweenSystem.Ease */
|
|
321
335
|
BACK: (x) => x * x * (2.70158 * x - 1.70158),
|
|
322
336
|
|
|
323
|
-
/** Elastic ease-in: oscillations that grow toward the end
|
|
337
|
+
/** Elastic ease-in: oscillations that grow toward the end
|
|
324
338
|
* @param {number} x
|
|
325
|
-
* @
|
|
339
|
+
* @return {number}
|
|
326
340
|
* @memberof TweenSystem.Ease */
|
|
327
341
|
ELASTIC: (x) =>
|
|
328
342
|
x === 0 ? 0 :
|
|
@@ -332,7 +346,7 @@ const Ease =
|
|
|
332
346
|
/** Spring ease-in: wobbles around the start before springing to the end;
|
|
333
347
|
* `Ease.OUT(Ease.SPRING)` overshoots and settles on the target.
|
|
334
348
|
* @param {number} x
|
|
335
|
-
* @
|
|
349
|
+
* @return {number}
|
|
336
350
|
* @memberof TweenSystem.Ease */
|
|
337
351
|
SPRING: (x) =>
|
|
338
352
|
1 -
|
|
@@ -346,7 +360,7 @@ const Ease =
|
|
|
346
360
|
* classic "object falls and hits the ground" shape (bounces near x=1),
|
|
347
361
|
* wrap with `Ease.OUT`: `Ease.OUT(Ease.BOUNCE)`.
|
|
348
362
|
* @param {number} x
|
|
349
|
-
* @
|
|
363
|
+
* @return {number}
|
|
350
364
|
* @memberof TweenSystem.Ease
|
|
351
365
|
* @example
|
|
352
366
|
* Ease.BOUNCE // ease-in bounce (bouncy at start)
|
|
@@ -369,7 +383,7 @@ const Ease =
|
|
|
369
383
|
* convention, so wrapping a curve in `IN` is a no-op — useful when
|
|
370
384
|
* picking the direction programmatically.
|
|
371
385
|
* @param {function(number):number} f - Curve to use as ease-in (returned unchanged)
|
|
372
|
-
* @
|
|
386
|
+
* @return {function(number):number}
|
|
373
387
|
* @memberof TweenSystem.Ease
|
|
374
388
|
* @example
|
|
375
389
|
* // Pick direction at runtime
|
|
@@ -380,16 +394,16 @@ const Ease =
|
|
|
380
394
|
|
|
381
395
|
/** Reverse a curve so it eases out instead of in: `x => 1 - f(1 - x)`.
|
|
382
396
|
* @param {function(number):number} f
|
|
383
|
-
* @
|
|
397
|
+
* @return {function(number):number}
|
|
384
398
|
* @memberof TweenSystem.Ease
|
|
385
399
|
* @example
|
|
386
400
|
* Ease.OUT(Ease.POWER(2)) // ease-out quadratic
|
|
387
401
|
*/
|
|
388
402
|
OUT: (f) => (x) => 1 - f(1 - x),
|
|
389
403
|
|
|
390
|
-
/** Combine the first half of `f` with `Ease.OUT(f)` for a symmetric curve
|
|
404
|
+
/** Combine the first half of `f` with `Ease.OUT(f)` for a symmetric curve
|
|
391
405
|
* @param {function(number):number} f
|
|
392
|
-
* @
|
|
406
|
+
* @return {function(number):number}
|
|
393
407
|
* @memberof TweenSystem.Ease */
|
|
394
408
|
IN_OUT: (f) => Ease.PIECEWISE(f, Ease.OUT(f)),
|
|
395
409
|
|
|
@@ -397,7 +411,7 @@ const Ease =
|
|
|
397
411
|
* Each curve is mapped to its section: section i runs over [i/n, (i+1)/n]
|
|
398
412
|
* and its output is mapped to [i/n, (i+1)/n] of the overall range.
|
|
399
413
|
* @param {...function(number):number} fns
|
|
400
|
-
* @
|
|
414
|
+
* @return {function(number):number}
|
|
401
415
|
* @memberof TweenSystem.Ease */
|
|
402
416
|
PIECEWISE: (...fns) =>
|
|
403
417
|
{
|
|
@@ -415,7 +429,7 @@ const Ease =
|
|
|
415
429
|
* @param {number} y1
|
|
416
430
|
* @param {number} x2
|
|
417
431
|
* @param {number} y2
|
|
418
|
-
* @
|
|
432
|
+
* @return {function(number):number}
|
|
419
433
|
* @memberof TweenSystem.Ease
|
|
420
434
|
* @example
|
|
421
435
|
* Ease.BEZIER(0.25, 0.1, 0.25, 1) // CSS "ease"
|
|
@@ -465,12 +479,12 @@ const Ease =
|
|
|
465
479
|
* @param {string} propertyPath - Dot-separated path, e.g. `'pos.x'` or `'color'`
|
|
466
480
|
* @param {T} start - Starting value
|
|
467
481
|
* @param {T} end - Ending value
|
|
468
|
-
* @param {number} [duration
|
|
482
|
+
* @param {number} [duration] - Duration in seconds
|
|
469
483
|
* @param {Object} [options] - Same options as the Tween constructor
|
|
470
484
|
* @param {function(number):number} [options.ease] - Easing function (defaults to LINEAR)
|
|
471
485
|
* @param {boolean} [options.useRealTime=false] - Advance even when the game is paused
|
|
472
486
|
* @param {boolean} [options.paused=false] - Start in paused state
|
|
473
|
-
* @
|
|
487
|
+
* @return {Tween<T>}
|
|
474
488
|
* @memberof TweenSystem
|
|
475
489
|
* @example
|
|
476
490
|
* // Numeric: slide an object's x with an ease-out sine curve
|
|
@@ -527,7 +541,7 @@ function tweenNextIteration(tween, passed, continuation)
|
|
|
527
541
|
tweenCarryOvershoot(tween);
|
|
528
542
|
tween.thenCallback = continuation;
|
|
529
543
|
tweenActivate(tween);
|
|
530
|
-
|
|
544
|
+
tweenCall(tween, tween.life); // snap to where the new iteration is
|
|
531
545
|
return true;
|
|
532
546
|
}
|
|
533
547
|
|
|
@@ -591,52 +605,68 @@ function tweenUpdate(gameDelta, realDelta)
|
|
|
591
605
|
// from the next update. Newest first, as the list has always been walked.
|
|
592
606
|
// a callback that calls tweenUpdate itself gets a list of its own, the outer update is still walking this one
|
|
593
607
|
const list = tweenUpdateList.length ? [] : tweenUpdateList, pass = ++tweenUpdatePass;
|
|
608
|
+
let kept = 0;
|
|
594
609
|
for (const t of tweenActive)
|
|
595
|
-
|
|
610
|
+
if (t.active)
|
|
611
|
+
list.push(tweenActive[kept++] = t);
|
|
612
|
+
else
|
|
613
|
+
t.listed = false; // stopped since the last update, out of the list now
|
|
614
|
+
tweenActive.length = kept;
|
|
596
615
|
// the list is let go however the walk ends: a callback that throws must not leave it held, or every
|
|
597
616
|
// update after would take it for an update still going and make a list of its own
|
|
617
|
+
let failed;
|
|
598
618
|
try
|
|
599
619
|
{
|
|
600
620
|
for (let i = list.length; i--;)
|
|
601
621
|
{
|
|
602
622
|
const t = list[i];
|
|
603
|
-
//
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
let dt;
|
|
607
|
-
if (enginePath)
|
|
608
|
-
{
|
|
609
|
-
// a paused tween keeps count too, so it does not jump when resumed
|
|
610
|
-
dt = t.useRealTime ? timeReal - t.lastTimeReal : time - t.lastTime;
|
|
611
|
-
t.lastTime = time;
|
|
612
|
-
t.lastTimeReal = timeReal;
|
|
613
|
-
}
|
|
614
|
-
else
|
|
615
|
-
dt = t.useRealTime ? realDelta : gameDelta;
|
|
616
|
-
if (t.target?.destroyed) { t.stop(); continue; } // its object is gone, paused or not
|
|
617
|
-
if (t.paused || dt <= 0) continue;
|
|
618
|
-
|
|
619
|
-
t.life -= dt;
|
|
620
|
-
if (t.life > 1e-9) // the engine's deltas add up a rounding error short of the duration
|
|
621
|
-
{
|
|
622
|
-
t.callback(t.interp(t.life));
|
|
623
|
-
}
|
|
624
|
-
else
|
|
625
|
-
{
|
|
626
|
-
// Completion: fire end value, remove from active, start the next iteration
|
|
627
|
-
// of a loop or pingPong, or when there is none it has completed, fire onComplete
|
|
628
|
-
t.callback(t.interp(0));
|
|
629
|
-
if (!t.active || t.activePass >= pass)
|
|
630
|
-
continue; // stopped or restarted by its own callback, the run it was on ends without completing
|
|
631
|
-
tweenDeactivate(t);
|
|
632
|
-
const next = t.thenCallback;
|
|
633
|
-
t.thenCallback = undefined;
|
|
634
|
-
if (!(next && next()) && t.onComplete)
|
|
635
|
-
t.onComplete();
|
|
636
|
-
}
|
|
623
|
+
// one that throws does not stop the others: its error comes out once they have all moved
|
|
624
|
+
try { tweenStep(t, pass, enginePath, gameDelta, realDelta); }
|
|
625
|
+
catch (error) { failed ??= {error}; }
|
|
637
626
|
}
|
|
638
627
|
}
|
|
639
628
|
finally { list.length = 0; }
|
|
629
|
+
if (failed)
|
|
630
|
+
throw failed.error;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// move one tween by an update, as tweenUpdate walks them
|
|
634
|
+
function tweenStep(t, pass, enginePath, gameDelta, realDelta)
|
|
635
|
+
{
|
|
636
|
+
// stopped, or started again by a callback this update, or during an update a callback ran inside it, which
|
|
637
|
+
// counts on from this one
|
|
638
|
+
if (!t.active || t.activePass >= pass) return;
|
|
639
|
+
let dt;
|
|
640
|
+
if (enginePath)
|
|
641
|
+
{
|
|
642
|
+
// a paused tween keeps count too, so it does not jump when resumed
|
|
643
|
+
dt = t.useRealTime ? timeReal - t.lastTimeReal : time - t.lastTime;
|
|
644
|
+
t.lastTime = time;
|
|
645
|
+
t.lastTimeReal = timeReal;
|
|
646
|
+
}
|
|
647
|
+
else
|
|
648
|
+
dt = t.useRealTime ? realDelta : gameDelta;
|
|
649
|
+
if (t.target?.destroyed) { t.stop(); return; } // its object is gone, paused or not
|
|
650
|
+
if (t.paused || dt <= 0) return;
|
|
651
|
+
|
|
652
|
+
t.life -= dt;
|
|
653
|
+
if (t.life > 1e-9) // the engine's deltas add up a rounding error short of the duration
|
|
654
|
+
{
|
|
655
|
+
tweenCall(t, t.life, pass);
|
|
656
|
+
}
|
|
657
|
+
else
|
|
658
|
+
{
|
|
659
|
+
// Completion: fire end value, remove from active, start the next iteration
|
|
660
|
+
// of a loop or pingPong, or when there is none it has completed, fire onComplete
|
|
661
|
+
tweenCall(t, 0, pass); // one whose last call throws is ended all the same
|
|
662
|
+
if (!t.active || t.activePass >= pass)
|
|
663
|
+
return; // stopped or restarted by its own callback, the run it was on ends without completing
|
|
664
|
+
tweenDeactivate(t);
|
|
665
|
+
const next = t.thenCallback;
|
|
666
|
+
t.thenCallback = undefined;
|
|
667
|
+
if (!(next && next()) && t.onComplete)
|
|
668
|
+
t.onComplete();
|
|
669
|
+
}
|
|
640
670
|
}
|
|
641
671
|
|
|
642
672
|
/** Stop every active tween, ending loops too, without calling their then-callbacks.
|
|
@@ -645,7 +675,7 @@ function tweenUpdate(gameDelta, realDelta)
|
|
|
645
675
|
function tweenStopAll()
|
|
646
676
|
{
|
|
647
677
|
for (const t of tweenActive)
|
|
648
|
-
t.thenCallback = undefined, t.active = false;
|
|
678
|
+
t.thenCallback = undefined, t.active = t.listed = false;
|
|
649
679
|
tweenActive.length = 0;
|
|
650
680
|
}
|
|
651
681
|
|