littlejsengine 1.18.29 → 1.20.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/FAQ.md +841 -815
- package/README.md +211 -199
- package/dist/littlejs.d.ts +4383 -812
- package/dist/littlejs.esm.js +31848 -16680
- package/dist/littlejs.esm.min.js +14 -1
- package/dist/littlejs.js +31230 -16166
- package/dist/littlejs.min.js +13 -1
- package/dist/littlejs.release.js +27419 -15457
- package/package.json +10 -5
- package/plugins/audioEffects.js +375 -0
- package/plugins/box2d.js +2516 -2073
- package/plugins/drawUtilities.js +206 -31
- package/plugins/gltf.js +740 -0
- package/plugins/jsconfig.json +19 -0
- package/plugins/lightSystem.js +719 -330
- package/plugins/math3d.js +911 -0
- package/plugins/medalSystem.js +118 -72
- package/plugins/newgrounds.js +400 -199
- package/plugins/pathFinder.js +208 -175
- package/plugins/pluginExport.js +196 -115
- package/plugins/postProcess.js +284 -166
- package/plugins/render3d.js +4072 -0
- package/plugins/render3dDebug.js +109 -0
- package/plugins/render3dExtras.js +1754 -0
- package/plugins/render3dVoxels.js +502 -0
- package/plugins/textureSheet.js +86 -48
- package/plugins/threejs.js +183 -158
- package/plugins/tweakables.js +411 -0
- package/plugins/tweenSystem.js +646 -526
- package/plugins/uiSystem.js +2024 -1687
- package/plugins/zzfxm.js +8 -15
- package/src/engine.js +345 -194
- package/src/engineAudio.js +1022 -777
- package/src/engineBuild.mjs +289 -253
- package/src/engineDebug.js +912 -735
- package/src/engineDraw.js +1774 -1510
- package/src/engineEditor.js +2431 -0
- package/src/engineExport.js +419 -396
- package/src/engineInput.js +1462 -1293
- package/src/engineLogo.js +4 -3
- package/src/engineMath.js +1274 -1183
- package/src/engineObject.js +674 -551
- package/src/engineParticles.js +583 -532
- package/src/engineRelease.js +67 -45
- package/src/engineSettings.js +808 -726
- package/src/engineTileLayer.js +995 -741
- package/src/engineUtilities.js +32 -13
- package/src/engineWebGL.js +1162 -941
- package/src/jsconfig.json +17 -13
- package/plugins/box2d.wasm.js +0 -630
- package/plugins/box2d.wasm.wasm +0 -0
package/plugins/tweenSystem.js
CHANGED
|
@@ -1,526 +1,646 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LittleJS Tween System Plugin
|
|
3
|
-
* - Lightweight tweens for numbers, Vector2, Color, or any .lerp-able type
|
|
4
|
-
* - Chainable easing, looping, and ping-pong
|
|
5
|
-
* - Property-path helper for the common case of animating an object field
|
|
6
|
-
* - Auto-updates via engineAddPlugin; pauses with the game by default
|
|
7
|
-
* @namespace TweenSystem
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
'use strict';
|
|
11
|
-
|
|
12
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
13
|
-
|
|
14
|
-
// Module-private list of tweens currently running.
|
|
15
|
-
const tweenActive = [];
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
this.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
this.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
*
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
*
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
{
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
* @
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
*
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
*
|
|
294
|
-
* @
|
|
295
|
-
* @
|
|
296
|
-
* @
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
* @
|
|
319
|
-
* @
|
|
320
|
-
*
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
*
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
* @
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
*
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
*
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
{
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
* @param {
|
|
473
|
-
* @
|
|
474
|
-
* @memberof TweenSystem
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
1
|
+
/**
|
|
2
|
+
* LittleJS Tween System Plugin
|
|
3
|
+
* - Lightweight tweens for numbers, Vector2, Color, or any .lerp-able type
|
|
4
|
+
* - Chainable easing, looping, and ping-pong
|
|
5
|
+
* - Property-path helper for the common case of animating an object field
|
|
6
|
+
* - Auto-updates via engineAddPlugin; pauses with the game by default
|
|
7
|
+
* @namespace TweenSystem
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
13
|
+
|
|
14
|
+
// Module-private list of tweens currently running, each with its active flag set while it is in it.
|
|
15
|
+
const tweenActive = [];
|
|
16
|
+
const tweenUpdateList = []; // the tweens an update moves, the ones active when it began
|
|
17
|
+
let tweenUpdatePass = 0; // counts the updates, a tween started during one waits for the next
|
|
18
|
+
|
|
19
|
+
// put a tween in the active list, or take it out, keeping its flag in step so a check costs nothing
|
|
20
|
+
function tweenActivate(tween)
|
|
21
|
+
{
|
|
22
|
+
tween.activePass = tweenUpdatePass; // started again, even while active, so this update leaves it alone
|
|
23
|
+
if (tween.active) return;
|
|
24
|
+
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);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// True if the value is an instance of a class that exposes a numeric-percent
|
|
35
|
+
// `lerp(other, percent)` method (Vector2, Color, or any future class).
|
|
36
|
+
function tweenIsLerpable(v) { return v && typeof v.lerp === 'function'; }
|
|
37
|
+
|
|
38
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
39
|
+
|
|
40
|
+
/** A tween: drives a callback with a value interpolated between
|
|
41
|
+
* `start` and `end` over `duration` seconds. Pauses with the game by default.
|
|
42
|
+
* - In TypeScript it is a `Tween<T>` of the type it tweens, which comes from `start` and `end` or
|
|
43
|
+
* the callback's parameter, so `(v: number)=> ...` takes a number
|
|
44
|
+
* @template [T=any]
|
|
45
|
+
* @memberof TweenSystem
|
|
46
|
+
* @example
|
|
47
|
+
* // Animate a fade-out over 2 seconds with an ease-out sine curve.
|
|
48
|
+
* new Tween((v) => obj.alpha = v, 1, 0, 2, { ease: Ease.OUT(Ease.SINE) });
|
|
49
|
+
*/
|
|
50
|
+
class Tween
|
|
51
|
+
{
|
|
52
|
+
/** Create a new tween. The callback fires immediately with `start` so the
|
|
53
|
+
* target snaps to the start value on the same frame the tween is created.
|
|
54
|
+
*
|
|
55
|
+
* `start` and `end` may be numbers, Vector2, Vector3 or Color instances, or
|
|
56
|
+
* any object exposing a `lerp(other, percent) => sameType` method. The
|
|
57
|
+
* callback receives the interpolated value (a number, or a fresh instance
|
|
58
|
+
* for lerp-able types). Both endpoints must be the same type.
|
|
59
|
+
* @param {function(NonNullable<T>):void} callback - Called with the interpolated value each frame
|
|
60
|
+
* @param {T} [start=0] - Starting value
|
|
61
|
+
* @param {T} [end=1] - Ending value
|
|
62
|
+
* @param {number} [duration=1] - Duration in seconds
|
|
63
|
+
* @param {Object} [options]
|
|
64
|
+
* @param {function(number):number} [options.ease] - Easing function (defaults to LINEAR)
|
|
65
|
+
* @param {boolean} [options.useRealTime=false] - Advance even when the game is paused (matches Timer's useRealTime)
|
|
66
|
+
* @param {boolean} [options.paused=false] - Start in paused state */
|
|
67
|
+
constructor(callback, start = /** @type {T} */ (0), end = /** @type {T} */ (1), duration = 1, options = {})
|
|
68
|
+
{
|
|
69
|
+
ASSERT(typeof callback === 'function', 'Tween callback must be a function');
|
|
70
|
+
if (tweenIsLerpable(start))
|
|
71
|
+
{
|
|
72
|
+
ASSERT(start.constructor === end.constructor,
|
|
73
|
+
'Tween start and end must be the same type');
|
|
74
|
+
}
|
|
75
|
+
else
|
|
76
|
+
{
|
|
77
|
+
ASSERT(isNumber(start), 'Tween start must be a number or have a .lerp method');
|
|
78
|
+
ASSERT(isNumber(end), 'Tween end must be a number when start is a number');
|
|
79
|
+
}
|
|
80
|
+
ASSERT(isNumber(duration) && duration > 0, 'Tween duration must be > 0');
|
|
81
|
+
|
|
82
|
+
// the callback's type is NonNullable<T>, which is T, so that TypeScript takes the type from start and end
|
|
83
|
+
// first: a typed callback like (v: number)=> with start 10 then makes a Tween<number>, not a Tween<0|10>
|
|
84
|
+
/** @property {function(T):void} - Called with the interpolated value each frame
|
|
85
|
+
* @type {function(T):void} */
|
|
86
|
+
this.callback = callback;
|
|
87
|
+
/** @property {T} - Starting value
|
|
88
|
+
* @type {T} */
|
|
89
|
+
this.start = start;
|
|
90
|
+
/** @property {T} - Ending value
|
|
91
|
+
* @type {T} */
|
|
92
|
+
this.end = end;
|
|
93
|
+
/** @property {number} - Total duration in seconds */
|
|
94
|
+
this.duration = duration;
|
|
95
|
+
/** @property {number} - Remaining time in seconds (counts down from duration to 0) */
|
|
96
|
+
this.life = duration;
|
|
97
|
+
/** @property {function(number):number} - Easing curve mapping [0,1] -> [0,1] */
|
|
98
|
+
this.ease = options.ease || Ease.LINEAR;
|
|
99
|
+
/** @property {boolean} - If true, advance even when the game is paused */
|
|
100
|
+
this.useRealTime = !!options.useRealTime;
|
|
101
|
+
/** @property {boolean} - If true, stop advancing until cleared */
|
|
102
|
+
this.paused = !!options.paused;
|
|
103
|
+
/** @property {undefined|function():void} - Called once the tween completes: when its last pass ends,
|
|
104
|
+
* the last iteration of a loop or pingPong, and again each time a restart plays through; then() sets it
|
|
105
|
+
* @type {undefined|function():void} */
|
|
106
|
+
this.onComplete = undefined;
|
|
107
|
+
|
|
108
|
+
/** Continuation when a pass ends, set by loop() and pingPong() to start the next iteration.
|
|
109
|
+
* @private */
|
|
110
|
+
this.thenCallback = undefined;
|
|
111
|
+
/** Remaining iterations including the current run (loop/pingPong only).
|
|
112
|
+
* @private */
|
|
113
|
+
this.loopRemaining = 0;
|
|
114
|
+
/** Whether it is in the active list, see isActive
|
|
115
|
+
* @private */
|
|
116
|
+
this.active = false;
|
|
117
|
+
/** The update it was started in, it first moves on the one after
|
|
118
|
+
* @private */
|
|
119
|
+
this.activePass = 0;
|
|
120
|
+
/** Engine time and real time of its last engine update, it moves by what passed since
|
|
121
|
+
* @private */
|
|
122
|
+
this.lastTime = time;
|
|
123
|
+
/** @private */
|
|
124
|
+
this.lastTimeReal = timeReal;
|
|
125
|
+
/** @property {Object|undefined} - The object tweenProperty animates, the tween stops once it is destroyed,
|
|
126
|
+
* even while paused
|
|
127
|
+
* @type {{destroyed?: boolean}|undefined} */
|
|
128
|
+
this.target = undefined;
|
|
129
|
+
|
|
130
|
+
tweenActivate(this);
|
|
131
|
+
// Snap target to start immediately.
|
|
132
|
+
callback(this.interp(duration));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Set the easing curve and return this for chaining.
|
|
136
|
+
* @param {function(number):number} easeFn
|
|
137
|
+
* @returns {Tween<T>} */
|
|
138
|
+
setEase(easeFn)
|
|
139
|
+
{
|
|
140
|
+
this.ease = easeFn;
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Set the completion callback, `onComplete`, and return this for chaining.
|
|
145
|
+
* It is called once the tween completes: when its pass ends, or for a
|
|
146
|
+
* `loop` or `pingPong` when its last iteration ends, so an endless one
|
|
147
|
+
* never calls it. Calling `then` again replaces the previous callback.
|
|
148
|
+
* - It works with `loop` and `pingPong` in either order, neither replaces the other
|
|
149
|
+
* - It is kept by `restart`, so a restarted tween calls it again when it completes
|
|
150
|
+
* - `stop` and `tweenStopAll` end a tween without calling it
|
|
151
|
+
* @param {function():void} callback
|
|
152
|
+
* @returns {Tween<T>} */
|
|
153
|
+
then(callback)
|
|
154
|
+
{
|
|
155
|
+
this.onComplete = callback;
|
|
156
|
+
return this;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Repeat this tween `n` total times. After each iteration finishes, the
|
|
160
|
+
* same tween starts over, so the handle returned stays good for the whole
|
|
161
|
+
* loop: pause or stop it to pause or stop every iteration left.
|
|
162
|
+
* `loop()` with no argument loops forever.
|
|
163
|
+
*
|
|
164
|
+
* Mutually exclusive with `pingPong`; calling either replaces the other.
|
|
165
|
+
* A `then` callback, set before or after, is called when the last
|
|
166
|
+
* iteration ends.
|
|
167
|
+
* @param {number} [count=Infinity]
|
|
168
|
+
* @returns {Tween<T>} */
|
|
169
|
+
loop(count = Infinity)
|
|
170
|
+
{
|
|
171
|
+
this.loopRemaining = count;
|
|
172
|
+
this.thenCallback = () => tweenLoopContinuation(this);
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Like `loop`, but swap `start` and `end` between iterations so the value
|
|
177
|
+
* bounces back and forth. `pingPong()` with no argument bounces forever.
|
|
178
|
+
*
|
|
179
|
+
* Mutually exclusive with `loop`; calling either replaces the other.
|
|
180
|
+
* A `then` callback, set before or after, is called when the last
|
|
181
|
+
* iteration ends.
|
|
182
|
+
* @param {number} [count=Infinity]
|
|
183
|
+
* @returns {Tween<T>} */
|
|
184
|
+
pingPong(count = Infinity)
|
|
185
|
+
{
|
|
186
|
+
this.loopRemaining = count;
|
|
187
|
+
this.thenCallback = () => tweenPingPongContinuation(this);
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Pause this tween. While paused, tweenUpdate skips it. */
|
|
192
|
+
pause() { this.paused = true; }
|
|
193
|
+
|
|
194
|
+
/** Resume a paused tween. */
|
|
195
|
+
resume() { this.paused = false; }
|
|
196
|
+
|
|
197
|
+
/** Reset this tween to the start: life back to duration, pause cleared,
|
|
198
|
+
* re-added to the active list if previously stopped, and the callback
|
|
199
|
+
* re-fired with the start value.
|
|
200
|
+
* It replays one pass: a loop or pingPong that has finished is not started
|
|
201
|
+
* over, a pingPong that ended on its way back plays that way again, and a
|
|
202
|
+
* restart mid loop keeps the iterations left. Call loop or pingPong again
|
|
203
|
+
* after restart to repeat it. The `then` callback is kept and is called
|
|
204
|
+
* again when it completes. */
|
|
205
|
+
restart()
|
|
206
|
+
{
|
|
207
|
+
this.life = this.duration;
|
|
208
|
+
this.paused = false;
|
|
209
|
+
this.lastTime = time;
|
|
210
|
+
this.lastTimeReal = timeReal;
|
|
211
|
+
tweenActivate(this);
|
|
212
|
+
this.callback(this.interp(this.duration));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** True if this tween is in the active list and not paused.
|
|
216
|
+
* @returns {boolean} */
|
|
217
|
+
isActive()
|
|
218
|
+
{
|
|
219
|
+
return !this.paused && this.active;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Get how far this tween has progressed, from 0 (just started) to 1
|
|
223
|
+
* (completed). Clamped — overshoot past completion still reads 1.
|
|
224
|
+
* @returns {number} */
|
|
225
|
+
getPercent()
|
|
226
|
+
{
|
|
227
|
+
return percent(this.duration - this.life, 0, this.duration);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Get the current interpolated value (the value most recently passed to
|
|
231
|
+
* the callback). Returns a number, Vector2, Vector3 or Color depending on the
|
|
232
|
+
* tween's start/end types.
|
|
233
|
+
* @returns {T} */
|
|
234
|
+
getValue()
|
|
235
|
+
{
|
|
236
|
+
return this.interp(this.life);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Compute the interpolated value at the given remaining `life`.
|
|
240
|
+
* At life === duration the result is `start`; at life === 0 it is `end`.
|
|
241
|
+
* - At life 0 it is the end value exactly
|
|
242
|
+
* - A vector goes past its ends as far as the easing does, as a number does; a Color stays between them,
|
|
243
|
+
* so its channels stay in range, and any other type goes as far as its own lerp takes it
|
|
244
|
+
* @param {number} life
|
|
245
|
+
* @returns {T} */
|
|
246
|
+
interp(life)
|
|
247
|
+
{
|
|
248
|
+
// the ends of whatever type it tweens, each kind is handled below
|
|
249
|
+
const s = /** @type {any} */ (this.start), e = /** @type {any} */ (this.end);
|
|
250
|
+
if (life <= 0) // the end exactly, an easing curve may land a rounding error short of it
|
|
251
|
+
return typeof e.copy === 'function' ? e.copy() : e;
|
|
252
|
+
const x = this.ease((this.duration - life) / this.duration);
|
|
253
|
+
// the vectors as their lerp does it, which lands on the end exactly, but without its clamp
|
|
254
|
+
const y = 1 - x;
|
|
255
|
+
if (s instanceof Vector2)
|
|
256
|
+
return /** @type {T} */ (vec2(e.x * x + s.x * y, e.y * x + s.y * y));
|
|
257
|
+
if (typeof Vector3 !== 'undefined' && s instanceof Vector3) // a build may leave out the 3D math
|
|
258
|
+
return /** @type {T} */ (vec3(e.x * x + s.x * y, e.y * x + s.y * y, e.z * x + s.z * y));
|
|
259
|
+
if (tweenIsLerpable(s))
|
|
260
|
+
return s.lerp(e, x);
|
|
261
|
+
return s + (e - s) * x;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Remove this tween from the active list, ending a loop or pingPong too, without calling
|
|
265
|
+
* the then-callback. It keeps the then-callback, so a restart calls it when it completes. */
|
|
266
|
+
stop()
|
|
267
|
+
{
|
|
268
|
+
tweenDeactivate(this);
|
|
269
|
+
this.thenCallback = undefined;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Library of named easing curves and direction modifiers.
|
|
274
|
+
* All curves accept `x` in [0,1] and return [0,1] (with possible overshoot
|
|
275
|
+
* for ELASTIC/BACK/SPRING/BOUNCE). Curves are values you pass to `setEase`
|
|
276
|
+
* or compose via the IN/OUT/IN_OUT/PIECEWISE/BEZIER modifiers.
|
|
277
|
+
* @memberof TweenSystem
|
|
278
|
+
* @example
|
|
279
|
+
* // Use a basic curve
|
|
280
|
+
* new Tween(callback, 0, 10, 1).setEase(Ease.SINE);
|
|
281
|
+
* // Use a modifier on a curve
|
|
282
|
+
* new Tween(callback, 0, 10, 1).setEase(Ease.OUT(Ease.BACK));
|
|
283
|
+
*/
|
|
284
|
+
const Ease =
|
|
285
|
+
{
|
|
286
|
+
/** Linear (identity) curve.
|
|
287
|
+
* @param {number} x
|
|
288
|
+
* @returns {number}
|
|
289
|
+
* @memberof TweenSystem.Ease */
|
|
290
|
+
LINEAR: (x) => x,
|
|
291
|
+
|
|
292
|
+
/** Power curve factory: `Ease.POWER(n)` returns `x => x**n`.
|
|
293
|
+
* Use n=2 for quadratic, n=3 for cubic, etc.
|
|
294
|
+
* @param {number} n
|
|
295
|
+
* @returns {function(number):number}
|
|
296
|
+
* @memberof TweenSystem.Ease */
|
|
297
|
+
POWER: (n) => (x) => x ** n,
|
|
298
|
+
|
|
299
|
+
/** Sine ease-in curve: starts slow, ends fast.
|
|
300
|
+
* @param {number} x
|
|
301
|
+
* @returns {number}
|
|
302
|
+
* @memberof TweenSystem.Ease */
|
|
303
|
+
SINE: (x) => 1 - cos(x * (PI / 2)),
|
|
304
|
+
|
|
305
|
+
/** Circular ease-in curve.
|
|
306
|
+
* @param {number} x
|
|
307
|
+
* @returns {number}
|
|
308
|
+
* @memberof TweenSystem.Ease */
|
|
309
|
+
CIRC: (x) => 1 - (1 - x * x)**.5,
|
|
310
|
+
|
|
311
|
+
/** Exponential ease-in curve (`2^(10x-10)`).
|
|
312
|
+
* @param {number} x
|
|
313
|
+
* @returns {number}
|
|
314
|
+
* @memberof TweenSystem.Ease */
|
|
315
|
+
EXPO: (x) => x === 0 ? 0 : 2 ** (10 * x - 10),
|
|
316
|
+
|
|
317
|
+
/** Back ease-in: overshoots backward at the start before snapping forward.
|
|
318
|
+
* @param {number} x
|
|
319
|
+
* @returns {number}
|
|
320
|
+
* @memberof TweenSystem.Ease */
|
|
321
|
+
BACK: (x) => x * x * (2.70158 * x - 1.70158),
|
|
322
|
+
|
|
323
|
+
/** Elastic ease-in: oscillations that grow toward the end.
|
|
324
|
+
* @param {number} x
|
|
325
|
+
* @returns {number}
|
|
326
|
+
* @memberof TweenSystem.Ease */
|
|
327
|
+
ELASTIC: (x) =>
|
|
328
|
+
x === 0 ? 0 :
|
|
329
|
+
x === 1 ? 1 :
|
|
330
|
+
-(2 ** (10 * x - 10)) * sin(((37 - 40 * x) * PI) / 6),
|
|
331
|
+
|
|
332
|
+
/** Spring ease-in: wobbles around the start before springing to the end;
|
|
333
|
+
* `Ease.OUT(Ease.SPRING)` overshoots and settles on the target.
|
|
334
|
+
* @param {number} x
|
|
335
|
+
* @returns {number}
|
|
336
|
+
* @memberof TweenSystem.Ease */
|
|
337
|
+
SPRING: (x) =>
|
|
338
|
+
1 -
|
|
339
|
+
(sin(PI * (1 - x) * (0.2 + 2.5 * (1 - x) ** 3)) *
|
|
340
|
+
x ** 2.2 +
|
|
341
|
+
(1 - x)) *
|
|
342
|
+
(1.0 + 1.2 * x),
|
|
343
|
+
|
|
344
|
+
/** Bouncing ease-in: small bounces near the start, then a rise to the end.
|
|
345
|
+
* Symmetric with the other base curves, which are all ease-in. To get the
|
|
346
|
+
* classic "object falls and hits the ground" shape (bounces near x=1),
|
|
347
|
+
* wrap with `Ease.OUT`: `Ease.OUT(Ease.BOUNCE)`.
|
|
348
|
+
* @param {number} x
|
|
349
|
+
* @returns {number}
|
|
350
|
+
* @memberof TweenSystem.Ease
|
|
351
|
+
* @example
|
|
352
|
+
* Ease.BOUNCE // ease-in bounce (bouncy at start)
|
|
353
|
+
* Ease.OUT(Ease.BOUNCE) // ease-out bounce (object hits ground)
|
|
354
|
+
* Ease.IN_OUT(Ease.BOUNCE) // bounces at both ends
|
|
355
|
+
*/
|
|
356
|
+
BOUNCE: (x) =>
|
|
357
|
+
{
|
|
358
|
+
// Inverted form of the standard easeOutBounce: 1 - bounceOut(1 - x).
|
|
359
|
+
let t = 1 - x, f;
|
|
360
|
+
if (t < 4 / 11) f = 7.5625 * t * t;
|
|
361
|
+
else if (t < 8 / 11) f = 7.5625 * (t -= 6 / 11) * t + 0.75;
|
|
362
|
+
else if (t < 10 / 11) f = 7.5625 * (t -= 9 / 11) * t + 0.9375;
|
|
363
|
+
else f = 7.5625 * (t -= 10.5 / 11) * t + 0.984375;
|
|
364
|
+
return 1 - f;
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
/** Ease-in direction modifier: returns the curve unchanged. Symmetric
|
|
368
|
+
* with `OUT` and `IN_OUT`. Base curves are already ease-in by
|
|
369
|
+
* convention, so wrapping a curve in `IN` is a no-op — useful when
|
|
370
|
+
* picking the direction programmatically.
|
|
371
|
+
* @param {function(number):number} f - Curve to use as ease-in (returned unchanged)
|
|
372
|
+
* @returns {function(number):number}
|
|
373
|
+
* @memberof TweenSystem.Ease
|
|
374
|
+
* @example
|
|
375
|
+
* // Pick direction at runtime
|
|
376
|
+
* const dir = bouncyMode ? Ease.OUT : Ease.IN;
|
|
377
|
+
* new Tween(cb, 0, 10, 1).setEase(dir(Ease.BACK));
|
|
378
|
+
*/
|
|
379
|
+
IN: (f) => f,
|
|
380
|
+
|
|
381
|
+
/** Reverse a curve so it eases out instead of in: `x => 1 - f(1 - x)`.
|
|
382
|
+
* @param {function(number):number} f
|
|
383
|
+
* @returns {function(number):number}
|
|
384
|
+
* @memberof TweenSystem.Ease
|
|
385
|
+
* @example
|
|
386
|
+
* Ease.OUT(Ease.POWER(2)) // ease-out quadratic
|
|
387
|
+
*/
|
|
388
|
+
OUT: (f) => (x) => 1 - f(1 - x),
|
|
389
|
+
|
|
390
|
+
/** Combine the first half of `f` with `Ease.OUT(f)` for a symmetric curve.
|
|
391
|
+
* @param {function(number):number} f
|
|
392
|
+
* @returns {function(number):number}
|
|
393
|
+
* @memberof TweenSystem.Ease */
|
|
394
|
+
IN_OUT: (f) => Ease.PIECEWISE(f, Ease.OUT(f)),
|
|
395
|
+
|
|
396
|
+
/** Split [0,1] into N equal sections and run a different curve in each.
|
|
397
|
+
* Each curve is mapped to its section: section i runs over [i/n, (i+1)/n]
|
|
398
|
+
* and its output is mapped to [i/n, (i+1)/n] of the overall range.
|
|
399
|
+
* @param {...function(number):number} fns
|
|
400
|
+
* @returns {function(number):number}
|
|
401
|
+
* @memberof TweenSystem.Ease */
|
|
402
|
+
PIECEWISE: (...fns) =>
|
|
403
|
+
{
|
|
404
|
+
const n = fns.length;
|
|
405
|
+
return (x) =>
|
|
406
|
+
{
|
|
407
|
+
const i = (x * n - 1e-9) >> 0;
|
|
408
|
+
return (fns[i]((x - i / n) * n) + i) / n;
|
|
409
|
+
};
|
|
410
|
+
},
|
|
411
|
+
|
|
412
|
+
/** Cubic Bezier curve solver in the style of CSS `cubic-bezier`.
|
|
413
|
+
* Control points (0,0), (x1,y1), (x2,y2), (1,1).
|
|
414
|
+
* @param {number} x1
|
|
415
|
+
* @param {number} y1
|
|
416
|
+
* @param {number} x2
|
|
417
|
+
* @param {number} y2
|
|
418
|
+
* @returns {function(number):number}
|
|
419
|
+
* @memberof TweenSystem.Ease
|
|
420
|
+
* @example
|
|
421
|
+
* Ease.BEZIER(0.25, 0.1, 0.25, 1) // CSS "ease"
|
|
422
|
+
*/
|
|
423
|
+
BEZIER: (x1, y1, x2, y2) =>
|
|
424
|
+
{
|
|
425
|
+
// Parametric cubic Bezier with implicit (0,0) and (1,1) endpoints.
|
|
426
|
+
const curve = (t) =>
|
|
427
|
+
{
|
|
428
|
+
const u = 1 - t;
|
|
429
|
+
const c1 = 3 * u * u * t;
|
|
430
|
+
const c2 = 3 * u * t * t;
|
|
431
|
+
const t3 = t ** 3;
|
|
432
|
+
return [c1 * x1 + c2 * x2 + t3, c1 * y1 + c2 * y2 + t3];
|
|
433
|
+
};
|
|
434
|
+
return (x) =>
|
|
435
|
+
{
|
|
436
|
+
// the ends are exact, a tween starts and ends on its values
|
|
437
|
+
if (x <= 0) return 0;
|
|
438
|
+
if (x >= 1) return 1;
|
|
439
|
+
|
|
440
|
+
// binary search for t such that curve(t).x = x, then return curve(t).y; a fixed count, since stopping
|
|
441
|
+
// once x is close can leave y far off where the curve is steep, 32 halvings put t within 1e-9
|
|
442
|
+
let t0 = 0, t1 = 1;
|
|
443
|
+
for (let i = 32; i--;)
|
|
444
|
+
{
|
|
445
|
+
const tMid = (t0 + t1) / 2;
|
|
446
|
+
if (curve(tMid)[0] < x) t0 = tMid; else t1 = tMid;
|
|
447
|
+
}
|
|
448
|
+
return curve((t0 + t1) / 2)[1];
|
|
449
|
+
};
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
/** Tween a property on an object by dot-path. Returns the underlying Tween
|
|
454
|
+
* so all chaining methods (`setEase`, `then`, `loop`, `pingPong`, etc.)
|
|
455
|
+
* remain available.
|
|
456
|
+
*
|
|
457
|
+
* `start` and `end` may be numbers, Vector2, Vector3 or Color instances, or
|
|
458
|
+
* any object with a `lerp(other, percent) => sameType` method.
|
|
459
|
+
*
|
|
460
|
+
* It stops on its own once the target is destroyed, so a looping tween on an object ends with it; a tween on a
|
|
461
|
+
* value inside the object, like `tweenProperty(obj.pos, 'x')`, or a new Tween with its own callback, has to be
|
|
462
|
+
* stopped by the game.
|
|
463
|
+
* @template [T=any]
|
|
464
|
+
* @param {Object} target - The object whose property is being animated
|
|
465
|
+
* @param {string} propertyPath - Dot-separated path, e.g. `'pos.x'` or `'color'`
|
|
466
|
+
* @param {T} start - Starting value
|
|
467
|
+
* @param {T} end - Ending value
|
|
468
|
+
* @param {number} [duration=1] - Duration in seconds
|
|
469
|
+
* @param {Object} [options] - Same options as the Tween constructor
|
|
470
|
+
* @param {function(number):number} [options.ease] - Easing function (defaults to LINEAR)
|
|
471
|
+
* @param {boolean} [options.useRealTime=false] - Advance even when the game is paused
|
|
472
|
+
* @param {boolean} [options.paused=false] - Start in paused state
|
|
473
|
+
* @returns {Tween<T>}
|
|
474
|
+
* @memberof TweenSystem
|
|
475
|
+
* @example
|
|
476
|
+
* // Numeric: slide an object's x with an ease-out sine curve
|
|
477
|
+
* tweenProperty(player, 'pos.x', 0, 10, 2).setEase(Ease.OUT(Ease.SINE));
|
|
478
|
+
* // Vector2: animate a position diagonally
|
|
479
|
+
* tweenProperty(player, 'pos', vec2(-5, 0), vec2(5, 3), 2);
|
|
480
|
+
* // Color: pulse between two colors
|
|
481
|
+
* tweenProperty(sprite, 'color', RED, BLUE, 1).pingPong();
|
|
482
|
+
*/
|
|
483
|
+
function tweenProperty(target, propertyPath, start, end, duration = 1, options = {})
|
|
484
|
+
{
|
|
485
|
+
ASSERT(target != null && typeof target === 'object', 'tweenProperty target must be an object');
|
|
486
|
+
ASSERT(isStringLike(propertyPath) && propertyPath.length > 0, 'tweenProperty propertyPath must be a non-empty string');
|
|
487
|
+
|
|
488
|
+
const parts = propertyPath.split('.');
|
|
489
|
+
const lastKey = parts.pop();
|
|
490
|
+
let tween;
|
|
491
|
+
const callback = (value) =>
|
|
492
|
+
{
|
|
493
|
+
// a destroyed object ends it, a loop or pingPong would run on it for good and keep it from being freed
|
|
494
|
+
if (target.destroyed) return void tween?.stop();
|
|
495
|
+
let obj = target;
|
|
496
|
+
for (const k of parts)
|
|
497
|
+
{
|
|
498
|
+
obj = obj[k];
|
|
499
|
+
ASSERT(obj != null, 'tweenProperty path does not resolve: ' + propertyPath);
|
|
500
|
+
}
|
|
501
|
+
obj[lastKey] = value;
|
|
502
|
+
};
|
|
503
|
+
tween = new Tween(callback, start, end, duration, options);
|
|
504
|
+
tween.target = target;
|
|
505
|
+
return tween;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Start the next iteration with the time the last one ran over already spent, so a loop keeps its
|
|
509
|
+
// pace; an update that ran over by more than a whole iteration lands where the cycle is, not owing it
|
|
510
|
+
function tweenCarryOvershoot(tween)
|
|
511
|
+
{
|
|
512
|
+
const duration = tween.duration;
|
|
513
|
+
tween.life = duration ? min(tween.life, 0) % duration + duration : 1e-9;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// How many iterations the update that finished one ran through: that one and every whole one after it
|
|
517
|
+
function tweenPassed(tween)
|
|
518
|
+
{
|
|
519
|
+
const duration = tween.duration;
|
|
520
|
+
return duration ? 1 + floor(-min(tween.life, 0) / duration) : 1;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// start the next iteration of a loop or pingPong, the time the last one ran over already spent, true
|
|
524
|
+
function tweenNextIteration(tween, passed, continuation)
|
|
525
|
+
{
|
|
526
|
+
tween.loopRemaining -= passed;
|
|
527
|
+
tweenCarryOvershoot(tween);
|
|
528
|
+
tween.thenCallback = continuation;
|
|
529
|
+
tweenActivate(tween);
|
|
530
|
+
tween.callback(tween.interp(tween.life)); // snap to where the new iteration is
|
|
531
|
+
return true;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Continuation that schedules the next loop iteration when one finishes, true if it did.
|
|
535
|
+
// Reuses the same Tween across iterations, so the handle from `.loop()` pauses or stops the whole loop.
|
|
536
|
+
function tweenLoopContinuation(tween)
|
|
537
|
+
{
|
|
538
|
+
// count every iteration that went by, a finite loop ends once they run out
|
|
539
|
+
const passed = tweenPassed(tween);
|
|
540
|
+
if (tween.loopRemaining <= passed) return false; // Infinity never runs out
|
|
541
|
+
return tweenNextIteration(tween, passed, () => tweenLoopContinuation(tween));
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Continuation for pingPong: swaps start and end on the same tween each iteration, true if it started another.
|
|
545
|
+
function tweenPingPongContinuation(tween)
|
|
546
|
+
{
|
|
547
|
+
// swap the ends once for each iteration that went by, but when they run out the last one keeps its
|
|
548
|
+
// direction, so it ends on the end it really reached and a restart plays that way again
|
|
549
|
+
const passed = tweenPassed(tween);
|
|
550
|
+
const done = tween.loopRemaining <= passed; // Infinity never runs out
|
|
551
|
+
const swaps = done ? max(tween.loopRemaining - 1, 0) : passed;
|
|
552
|
+
if (swaps & 1)
|
|
553
|
+
{
|
|
554
|
+
const tmp = tween.start;
|
|
555
|
+
tween.start = tween.end;
|
|
556
|
+
tween.end = tmp;
|
|
557
|
+
}
|
|
558
|
+
if (done)
|
|
559
|
+
{
|
|
560
|
+
// the completion gave the other end, give the one it finished on
|
|
561
|
+
if (swaps & 1)
|
|
562
|
+
tween.callback(tween.interp(0));
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
return tweenNextIteration(tween, passed, () => tweenPingPongContinuation(tween));
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/** Engine plugin hook: advance every active tween by the appropriate delta.
|
|
569
|
+
* The engine calls it with no arguments on every fixed update, so it can run
|
|
570
|
+
* more than once in a rendered frame, and on paused updates too, where only
|
|
571
|
+
* real time tweens move. May also be called with `(gameDelta, realDelta)` to drive tweens without the engine
|
|
572
|
+
* loop, for headless tests or an engine in manual step that is not stepped; in a running game such a call adds to
|
|
573
|
+
* the engine's own update, and a delta of 0 or less does nothing.
|
|
574
|
+
* @param {number} [gameDelta] - Game-time delta in seconds; default: game time since the tween's last engine update
|
|
575
|
+
* @param {number} [realDelta] - Real-time delta in seconds; default: real time since the tween's last engine update
|
|
576
|
+
* @memberof TweenSystem */
|
|
577
|
+
function tweenUpdate(gameDelta, realDelta)
|
|
578
|
+
{
|
|
579
|
+
// Engine path: each tween moves by the time since its own last update, so one made
|
|
580
|
+
// this update, before or after this call, first moves on the next, like a Timer
|
|
581
|
+
const enginePath = gameDelta === undefined;
|
|
582
|
+
if (!enginePath && realDelta === undefined)
|
|
583
|
+
{
|
|
584
|
+
// Manual path with one arg: real and game advance together.
|
|
585
|
+
realDelta = gameDelta;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Move the tweens that were active when this update began, each once: a callback
|
|
589
|
+
// may stop any tween, even all of them, and one that is stopped is skipped; a tween
|
|
590
|
+
// made or started again during the update, like the next turn of a loop, moves on
|
|
591
|
+
// from the next update. Newest first, as the list has always been walked.
|
|
592
|
+
// a callback that calls tweenUpdate itself gets a list of its own, the outer update is still walking this one
|
|
593
|
+
const list = tweenUpdateList.length ? [] : tweenUpdateList, pass = ++tweenUpdatePass;
|
|
594
|
+
for (const t of tweenActive)
|
|
595
|
+
list.push(t);
|
|
596
|
+
for (let i = list.length; i--;)
|
|
597
|
+
{
|
|
598
|
+
const t = list[i];
|
|
599
|
+
if (!t.active || t.activePass === pass) continue; // stopped, or started again by a callback this update
|
|
600
|
+
let dt;
|
|
601
|
+
if (enginePath)
|
|
602
|
+
{
|
|
603
|
+
// a paused tween keeps count too, so it does not jump when resumed
|
|
604
|
+
dt = t.useRealTime ? timeReal - t.lastTimeReal : time - t.lastTime;
|
|
605
|
+
t.lastTime = time;
|
|
606
|
+
t.lastTimeReal = timeReal;
|
|
607
|
+
}
|
|
608
|
+
else
|
|
609
|
+
dt = t.useRealTime ? realDelta : gameDelta;
|
|
610
|
+
if (t.target?.destroyed) { t.stop(); continue; } // its object is gone, paused or not
|
|
611
|
+
if (t.paused || dt <= 0) continue;
|
|
612
|
+
|
|
613
|
+
t.life -= dt;
|
|
614
|
+
if (t.life > 1e-9) // the engine's deltas add up a rounding error short of the duration
|
|
615
|
+
{
|
|
616
|
+
t.callback(t.interp(t.life));
|
|
617
|
+
}
|
|
618
|
+
else
|
|
619
|
+
{
|
|
620
|
+
// Completion: fire end value, remove from active, start the next iteration
|
|
621
|
+
// of a loop or pingPong, or when there is none it has completed, fire onComplete
|
|
622
|
+
t.callback(t.interp(0));
|
|
623
|
+
if (!t.active || t.activePass === pass)
|
|
624
|
+
continue; // stopped or restarted by its own callback, the run it was on ends without completing
|
|
625
|
+
tweenDeactivate(t);
|
|
626
|
+
const next = t.thenCallback;
|
|
627
|
+
t.thenCallback = undefined;
|
|
628
|
+
if (!(next && next()) && t.onComplete)
|
|
629
|
+
t.onComplete();
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
list.length = 0;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** Stop every active tween, ending loops too, without calling their then-callbacks.
|
|
636
|
+
* Useful for resets on level transitions or when changing scenes.
|
|
637
|
+
* @memberof TweenSystem */
|
|
638
|
+
function tweenStopAll()
|
|
639
|
+
{
|
|
640
|
+
for (const t of tweenActive)
|
|
641
|
+
t.thenCallback = undefined, t.active = false;
|
|
642
|
+
tweenActive.length = 0;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Register with the engine so tweens auto-advance.
|
|
646
|
+
engineAddPlugin(tweenUpdate);
|