littlejsengine 1.19.3 → 1.21.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 -820
- package/README.md +29 -25
- package/dist/littlejs.d.ts +2857 -955
- package/dist/littlejs.esm.js +33428 -20995
- package/dist/littlejs.esm.min.js +4 -3
- package/dist/littlejs.js +33386 -20996
- package/dist/littlejs.min.js +2 -2
- package/dist/littlejs.release.js +27841 -20422
- package/package.json +10 -5
- package/plugins/audioEffects.js +28 -24
- package/plugins/box2d.js +2516 -2073
- package/plugins/drawUtilities.js +206 -31
- package/plugins/gltf.js +786 -0
- package/plugins/jsconfig.json +19 -0
- package/plugins/lightSystem.js +719 -332
- package/plugins/math3d.js +148 -27
- package/plugins/medalSystem.js +326 -280
- package/plugins/newgrounds.js +400 -199
- package/plugins/pathFinder.js +208 -175
- package/plugins/pluginExport.js +25 -0
- package/plugins/postProcess.js +96 -24
- package/plugins/render3d.js +2067 -1632
- package/plugins/render3dDebug.js +110 -0
- package/plugins/render3dEditor.js +1812 -0
- package/plugins/render3dExtras.js +1948 -0
- package/plugins/render3dLevel.js +189 -0
- package/plugins/render3dVoxels.js +514 -0
- package/plugins/sceneSystem.js +58 -0
- package/plugins/textureSheet.js +496 -458
- package/plugins/threejs.js +183 -163
- package/plugins/tweakables.js +411 -0
- package/plugins/tweenSystem.js +265 -145
- package/plugins/uiSystem.js +2024 -1687
- package/plugins/zzfxm.js +159 -159
- package/src/engine.js +378 -97
- package/src/engineAudio.js +231 -127
- package/src/engineBuild.mjs +292 -256
- package/src/engineDebug.js +967 -743
- package/src/engineDraw.js +391 -198
- package/src/engineEditor.js +2452 -0
- package/src/engineExport.js +18 -0
- package/src/engineInput.js +1466 -1293
- package/src/engineLogo.js +19 -0
- package/src/engineMath.js +1274 -1252
- package/src/engineObject.js +197 -82
- package/src/engineParticles.js +583 -532
- package/src/engineRelease.js +79 -45
- package/src/engineSettings.js +115 -29
- package/src/engineTileLayer.js +995 -741
- package/src/engineUtilities.js +19 -13
- package/src/engineWebGL.js +239 -82
- 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
|
@@ -11,21 +11,37 @@
|
|
|
11
11
|
|
|
12
12
|
///////////////////////////////////////////////////////////////////////////////
|
|
13
13
|
|
|
14
|
-
// Module-private list of tweens currently running.
|
|
14
|
+
// Module-private list of tweens currently running, each with its active flag set while it is in it.
|
|
15
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
|
|
16
18
|
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
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
|
+
}
|
|
20
33
|
|
|
21
34
|
// True if the value is an instance of a class that exposes a numeric-percent
|
|
22
35
|
// `lerp(other, percent)` method (Vector2, Color, or any future class).
|
|
23
|
-
function
|
|
36
|
+
function tweenIsLerpable(v) { return v && typeof v.lerp === 'function'; }
|
|
24
37
|
|
|
25
38
|
///////////////////////////////////////////////////////////////////////////////
|
|
26
39
|
|
|
27
|
-
/** A
|
|
40
|
+
/** A tween: drives a callback with a value interpolated between
|
|
28
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]
|
|
29
45
|
* @memberof TweenSystem
|
|
30
46
|
* @example
|
|
31
47
|
* // Animate a fade-out over 2 seconds with an ease-out sine curve.
|
|
@@ -36,22 +52,22 @@ class Tween
|
|
|
36
52
|
/** Create a new tween. The callback fires immediately with `start` so the
|
|
37
53
|
* target snaps to the start value on the same frame the tween is created.
|
|
38
54
|
*
|
|
39
|
-
* `start` and `end` may be numbers, Vector2
|
|
55
|
+
* `start` and `end` may be numbers, Vector2, Vector3 or Color instances, or
|
|
40
56
|
* any object exposing a `lerp(other, percent) => sameType` method. The
|
|
41
57
|
* callback receives the interpolated value (a number, or a fresh instance
|
|
42
58
|
* for lerp-able types). Both endpoints must be the same type.
|
|
43
|
-
* @param {function(
|
|
44
|
-
* @param {
|
|
45
|
-
* @param {
|
|
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
|
|
46
62
|
* @param {number} [duration=1] - Duration in seconds
|
|
47
63
|
* @param {Object} [options]
|
|
48
64
|
* @param {function(number):number} [options.ease] - Easing function (defaults to LINEAR)
|
|
49
65
|
* @param {boolean} [options.useRealTime=false] - Advance even when the game is paused (matches Timer's useRealTime)
|
|
50
66
|
* @param {boolean} [options.paused=false] - Start in paused state */
|
|
51
|
-
constructor(callback, start = 0, end = 1, duration = 1, options = {})
|
|
67
|
+
constructor(callback, start = /** @type {T} */ (0), end = /** @type {T} */ (1), duration = 1, options = {})
|
|
52
68
|
{
|
|
53
69
|
ASSERT(typeof callback === 'function', 'Tween callback must be a function');
|
|
54
|
-
if (
|
|
70
|
+
if (tweenIsLerpable(start))
|
|
55
71
|
{
|
|
56
72
|
ASSERT(start.constructor === end.constructor,
|
|
57
73
|
'Tween start and end must be the same type');
|
|
@@ -63,11 +79,16 @@ class Tween
|
|
|
63
79
|
}
|
|
64
80
|
ASSERT(isNumber(duration) && duration > 0, 'Tween duration must be > 0');
|
|
65
81
|
|
|
66
|
-
|
|
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} */
|
|
67
86
|
this.callback = callback;
|
|
68
|
-
/** @property {
|
|
87
|
+
/** @property {T} - Starting value
|
|
88
|
+
* @type {T} */
|
|
69
89
|
this.start = start;
|
|
70
|
-
/** @property {
|
|
90
|
+
/** @property {T} - Ending value
|
|
91
|
+
* @type {T} */
|
|
71
92
|
this.end = end;
|
|
72
93
|
/** @property {number} - Total duration in seconds */
|
|
73
94
|
this.duration = duration;
|
|
@@ -79,117 +100,137 @@ class Tween
|
|
|
79
100
|
this.useRealTime = !!options.useRealTime;
|
|
80
101
|
/** @property {boolean} - If true, stop advancing until cleared */
|
|
81
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;
|
|
82
107
|
|
|
83
|
-
/**
|
|
108
|
+
/** Continuation when a pass ends, set by loop() and pingPong() to start the next iteration.
|
|
84
109
|
* @private */
|
|
85
110
|
this.thenCallback = undefined;
|
|
86
111
|
/** Remaining iterations including the current run (loop/pingPong only).
|
|
87
112
|
* @private */
|
|
88
113
|
this.loopRemaining = 0;
|
|
89
|
-
|
|
90
|
-
|
|
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);
|
|
91
131
|
// Snap target to start immediately.
|
|
92
132
|
callback(this.interp(duration));
|
|
93
133
|
}
|
|
94
134
|
|
|
95
135
|
/** Set the easing curve and return this for chaining.
|
|
96
136
|
* @param {function(number):number} easeFn
|
|
97
|
-
* @returns {Tween}
|
|
98
|
-
* @memberof TweenSystem */
|
|
137
|
+
* @returns {Tween<T>} */
|
|
99
138
|
setEase(easeFn)
|
|
100
139
|
{
|
|
101
140
|
this.ease = easeFn;
|
|
102
141
|
return this;
|
|
103
142
|
}
|
|
104
143
|
|
|
105
|
-
/** Set
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* Calling `then`
|
|
109
|
-
*
|
|
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
|
|
110
151
|
* @param {function():void} callback
|
|
111
|
-
* @returns {Tween}
|
|
112
|
-
* @memberof TweenSystem */
|
|
152
|
+
* @returns {Tween<T>} */
|
|
113
153
|
then(callback)
|
|
114
154
|
{
|
|
115
|
-
this.
|
|
116
|
-
this.loopRemaining = 0;
|
|
155
|
+
this.onComplete = callback;
|
|
117
156
|
return this;
|
|
118
157
|
}
|
|
119
158
|
|
|
120
|
-
/** Repeat this tween `n` total times. After each iteration finishes,
|
|
121
|
-
*
|
|
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.
|
|
122
162
|
* `loop()` with no argument loops forever.
|
|
123
163
|
*
|
|
124
|
-
* Mutually exclusive with `pingPong`; calling either replaces the other
|
|
125
|
-
*
|
|
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.
|
|
126
167
|
* @param {number} [count=Infinity]
|
|
127
|
-
* @returns {Tween}
|
|
128
|
-
* @memberof TweenSystem */
|
|
168
|
+
* @returns {Tween<T>} */
|
|
129
169
|
loop(count = Infinity)
|
|
130
170
|
{
|
|
131
171
|
this.loopRemaining = count;
|
|
132
|
-
this.thenCallback = () =>
|
|
172
|
+
this.thenCallback = () => tweenLoopContinuation(this);
|
|
133
173
|
return this;
|
|
134
174
|
}
|
|
135
175
|
|
|
136
176
|
/** Like `loop`, but swap `start` and `end` between iterations so the value
|
|
137
177
|
* bounces back and forth. `pingPong()` with no argument bounces forever.
|
|
138
178
|
*
|
|
139
|
-
* Mutually exclusive with `loop`; calling either replaces the other
|
|
140
|
-
*
|
|
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.
|
|
141
182
|
* @param {number} [count=Infinity]
|
|
142
|
-
* @returns {Tween}
|
|
143
|
-
* @memberof TweenSystem */
|
|
183
|
+
* @returns {Tween<T>} */
|
|
144
184
|
pingPong(count = Infinity)
|
|
145
185
|
{
|
|
146
186
|
this.loopRemaining = count;
|
|
147
|
-
this.thenCallback = () =>
|
|
187
|
+
this.thenCallback = () => tweenPingPongContinuation(this);
|
|
148
188
|
return this;
|
|
149
189
|
}
|
|
150
190
|
|
|
151
|
-
/** Pause this tween. While paused, tweenUpdate skips it.
|
|
152
|
-
* @memberof TweenSystem */
|
|
191
|
+
/** Pause this tween. While paused, tweenUpdate skips it. */
|
|
153
192
|
pause() { this.paused = true; }
|
|
154
193
|
|
|
155
|
-
/** Resume a paused tween.
|
|
156
|
-
* @memberof TweenSystem */
|
|
194
|
+
/** Resume a paused tween. */
|
|
157
195
|
resume() { this.paused = false; }
|
|
158
196
|
|
|
159
197
|
/** Reset this tween to the start: life back to duration, pause cleared,
|
|
160
198
|
* re-added to the active list if previously stopped, and the callback
|
|
161
199
|
* re-fired with the start value.
|
|
162
|
-
*
|
|
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. */
|
|
163
205
|
restart()
|
|
164
206
|
{
|
|
165
207
|
this.life = this.duration;
|
|
166
208
|
this.paused = false;
|
|
167
|
-
|
|
209
|
+
this.lastTime = time;
|
|
210
|
+
this.lastTimeReal = timeReal;
|
|
211
|
+
tweenActivate(this);
|
|
168
212
|
this.callback(this.interp(this.duration));
|
|
169
213
|
}
|
|
170
214
|
|
|
171
215
|
/** True if this tween is in the active list and not paused.
|
|
172
|
-
* @returns {boolean}
|
|
173
|
-
* @memberof TweenSystem */
|
|
216
|
+
* @returns {boolean} */
|
|
174
217
|
isActive()
|
|
175
218
|
{
|
|
176
|
-
return !this.paused &&
|
|
219
|
+
return !this.paused && this.active;
|
|
177
220
|
}
|
|
178
221
|
|
|
179
222
|
/** Get how far this tween has progressed, from 0 (just started) to 1
|
|
180
223
|
* (completed). Clamped — overshoot past completion still reads 1.
|
|
181
|
-
* @returns {number}
|
|
182
|
-
* @memberof TweenSystem */
|
|
224
|
+
* @returns {number} */
|
|
183
225
|
getPercent()
|
|
184
226
|
{
|
|
185
227
|
return percent(this.duration - this.life, 0, this.duration);
|
|
186
228
|
}
|
|
187
229
|
|
|
188
230
|
/** Get the current interpolated value (the value most recently passed to
|
|
189
|
-
* the callback). Returns a number, Vector2, or Color depending on the
|
|
231
|
+
* the callback). Returns a number, Vector2, Vector3 or Color depending on the
|
|
190
232
|
* tween's start/end types.
|
|
191
|
-
* @returns {
|
|
192
|
-
* @memberof TweenSystem */
|
|
233
|
+
* @returns {T} */
|
|
193
234
|
getValue()
|
|
194
235
|
{
|
|
195
236
|
return this.interp(this.life);
|
|
@@ -197,23 +238,34 @@ class Tween
|
|
|
197
238
|
|
|
198
239
|
/** Compute the interpolated value at the given remaining `life`.
|
|
199
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
|
|
200
244
|
* @param {number} life
|
|
201
|
-
* @returns {
|
|
202
|
-
* @memberof TweenSystem */
|
|
245
|
+
* @returns {T} */
|
|
203
246
|
interp(life)
|
|
204
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;
|
|
205
252
|
const x = this.ease((this.duration - life) / this.duration);
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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;
|
|
209
262
|
}
|
|
210
263
|
|
|
211
|
-
/** Remove this tween from the active list
|
|
212
|
-
*
|
|
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. */
|
|
213
266
|
stop()
|
|
214
267
|
{
|
|
215
|
-
|
|
216
|
-
if (i >= 0) tweenActive.splice(i, 1);
|
|
268
|
+
tweenDeactivate(this);
|
|
217
269
|
this.thenCallback = undefined;
|
|
218
270
|
}
|
|
219
271
|
}
|
|
@@ -234,53 +286,54 @@ const Ease =
|
|
|
234
286
|
/** Linear (identity) curve.
|
|
235
287
|
* @param {number} x
|
|
236
288
|
* @returns {number}
|
|
237
|
-
* @memberof TweenSystem */
|
|
289
|
+
* @memberof TweenSystem.Ease */
|
|
238
290
|
LINEAR: (x) => x,
|
|
239
291
|
|
|
240
292
|
/** Power curve factory: `Ease.POWER(n)` returns `x => x**n`.
|
|
241
293
|
* Use n=2 for quadratic, n=3 for cubic, etc.
|
|
242
294
|
* @param {number} n
|
|
243
295
|
* @returns {function(number):number}
|
|
244
|
-
* @memberof TweenSystem */
|
|
296
|
+
* @memberof TweenSystem.Ease */
|
|
245
297
|
POWER: (n) => (x) => x ** n,
|
|
246
298
|
|
|
247
299
|
/** Sine ease-in curve: starts slow, ends fast.
|
|
248
300
|
* @param {number} x
|
|
249
301
|
* @returns {number}
|
|
250
|
-
* @memberof TweenSystem */
|
|
302
|
+
* @memberof TweenSystem.Ease */
|
|
251
303
|
SINE: (x) => 1 - cos(x * (PI / 2)),
|
|
252
304
|
|
|
253
305
|
/** Circular ease-in curve.
|
|
254
306
|
* @param {number} x
|
|
255
307
|
* @returns {number}
|
|
256
|
-
* @memberof TweenSystem */
|
|
308
|
+
* @memberof TweenSystem.Ease */
|
|
257
309
|
CIRC: (x) => 1 - (1 - x * x)**.5,
|
|
258
310
|
|
|
259
311
|
/** Exponential ease-in curve (`2^(10x-10)`).
|
|
260
312
|
* @param {number} x
|
|
261
313
|
* @returns {number}
|
|
262
|
-
* @memberof TweenSystem */
|
|
314
|
+
* @memberof TweenSystem.Ease */
|
|
263
315
|
EXPO: (x) => x === 0 ? 0 : 2 ** (10 * x - 10),
|
|
264
316
|
|
|
265
317
|
/** Back ease-in: overshoots backward at the start before snapping forward.
|
|
266
318
|
* @param {number} x
|
|
267
319
|
* @returns {number}
|
|
268
|
-
* @memberof TweenSystem */
|
|
320
|
+
* @memberof TweenSystem.Ease */
|
|
269
321
|
BACK: (x) => x * x * (2.70158 * x - 1.70158),
|
|
270
322
|
|
|
271
|
-
/** Elastic ease-in:
|
|
323
|
+
/** Elastic ease-in: oscillations that grow toward the end.
|
|
272
324
|
* @param {number} x
|
|
273
325
|
* @returns {number}
|
|
274
|
-
* @memberof TweenSystem */
|
|
326
|
+
* @memberof TweenSystem.Ease */
|
|
275
327
|
ELASTIC: (x) =>
|
|
276
328
|
x === 0 ? 0 :
|
|
277
329
|
x === 1 ? 1 :
|
|
278
330
|
-(2 ** (10 * x - 10)) * sin(((37 - 40 * x) * PI) / 6),
|
|
279
331
|
|
|
280
|
-
/** Spring
|
|
332
|
+
/** Spring ease-in: wobbles around the start before springing to the end;
|
|
333
|
+
* `Ease.OUT(Ease.SPRING)` overshoots and settles on the target.
|
|
281
334
|
* @param {number} x
|
|
282
335
|
* @returns {number}
|
|
283
|
-
* @memberof TweenSystem */
|
|
336
|
+
* @memberof TweenSystem.Ease */
|
|
284
337
|
SPRING: (x) =>
|
|
285
338
|
1 -
|
|
286
339
|
(sin(PI * (1 - x) * (0.2 + 2.5 * (1 - x) ** 3)) *
|
|
@@ -288,15 +341,15 @@ const Ease =
|
|
|
288
341
|
(1 - x)) *
|
|
289
342
|
(1.0 + 1.2 * x),
|
|
290
343
|
|
|
291
|
-
/** Bouncing ease-in:
|
|
344
|
+
/** Bouncing ease-in: small bounces near the start, then a rise to the end.
|
|
292
345
|
* Symmetric with the other base curves, which are all ease-in. To get the
|
|
293
346
|
* classic "object falls and hits the ground" shape (bounces near x=1),
|
|
294
347
|
* wrap with `Ease.OUT`: `Ease.OUT(Ease.BOUNCE)`.
|
|
295
348
|
* @param {number} x
|
|
296
349
|
* @returns {number}
|
|
297
|
-
* @memberof TweenSystem
|
|
350
|
+
* @memberof TweenSystem.Ease
|
|
298
351
|
* @example
|
|
299
|
-
* Ease.BOUNCE // ease-in bounce (
|
|
352
|
+
* Ease.BOUNCE // ease-in bounce (bouncy at start)
|
|
300
353
|
* Ease.OUT(Ease.BOUNCE) // ease-out bounce (object hits ground)
|
|
301
354
|
* Ease.IN_OUT(Ease.BOUNCE) // bounces at both ends
|
|
302
355
|
*/
|
|
@@ -317,7 +370,7 @@ const Ease =
|
|
|
317
370
|
* picking the direction programmatically.
|
|
318
371
|
* @param {function(number):number} f - Curve to use as ease-in (returned unchanged)
|
|
319
372
|
* @returns {function(number):number}
|
|
320
|
-
* @memberof TweenSystem
|
|
373
|
+
* @memberof TweenSystem.Ease
|
|
321
374
|
* @example
|
|
322
375
|
* // Pick direction at runtime
|
|
323
376
|
* const dir = bouncyMode ? Ease.OUT : Ease.IN;
|
|
@@ -328,18 +381,16 @@ const Ease =
|
|
|
328
381
|
/** Reverse a curve so it eases out instead of in: `x => 1 - f(1 - x)`.
|
|
329
382
|
* @param {function(number):number} f
|
|
330
383
|
* @returns {function(number):number}
|
|
331
|
-
* @memberof TweenSystem
|
|
384
|
+
* @memberof TweenSystem.Ease
|
|
332
385
|
* @example
|
|
333
386
|
* Ease.OUT(Ease.POWER(2)) // ease-out quadratic
|
|
334
387
|
*/
|
|
335
388
|
OUT: (f) => (x) => 1 - f(1 - x),
|
|
336
389
|
|
|
337
390
|
/** Combine the first half of `f` with `Ease.OUT(f)` for a symmetric curve.
|
|
338
|
-
* Bug-fix vs the original library: the original referenced an undefined
|
|
339
|
-
* global `Piecewise`; this implementation routes through `Ease.PIECEWISE`.
|
|
340
391
|
* @param {function(number):number} f
|
|
341
392
|
* @returns {function(number):number}
|
|
342
|
-
* @memberof TweenSystem */
|
|
393
|
+
* @memberof TweenSystem.Ease */
|
|
343
394
|
IN_OUT: (f) => Ease.PIECEWISE(f, Ease.OUT(f)),
|
|
344
395
|
|
|
345
396
|
/** Split [0,1] into N equal sections and run a different curve in each.
|
|
@@ -347,7 +398,7 @@ const Ease =
|
|
|
347
398
|
* and its output is mapped to [i/n, (i+1)/n] of the overall range.
|
|
348
399
|
* @param {...function(number):number} fns
|
|
349
400
|
* @returns {function(number):number}
|
|
350
|
-
* @memberof TweenSystem */
|
|
401
|
+
* @memberof TweenSystem.Ease */
|
|
351
402
|
PIECEWISE: (...fns) =>
|
|
352
403
|
{
|
|
353
404
|
const n = fns.length;
|
|
@@ -365,7 +416,7 @@ const Ease =
|
|
|
365
416
|
* @param {number} x2
|
|
366
417
|
* @param {number} y2
|
|
367
418
|
* @returns {function(number):number}
|
|
368
|
-
* @memberof TweenSystem
|
|
419
|
+
* @memberof TweenSystem.Ease
|
|
369
420
|
* @example
|
|
370
421
|
* Ease.BEZIER(0.25, 0.1, 0.25, 1) // CSS "ease"
|
|
371
422
|
*/
|
|
@@ -382,14 +433,17 @@ const Ease =
|
|
|
382
433
|
};
|
|
383
434
|
return (x) =>
|
|
384
435
|
{
|
|
385
|
-
//
|
|
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
|
|
386
442
|
let t0 = 0, t1 = 1;
|
|
387
|
-
for (let i =
|
|
443
|
+
for (let i = 32; i--;)
|
|
388
444
|
{
|
|
389
445
|
const tMid = (t0 + t1) / 2;
|
|
390
|
-
|
|
391
|
-
if (abs(bx - x) < 1e-5) return by;
|
|
392
|
-
if (bx < x) t0 = tMid; else t1 = tMid;
|
|
446
|
+
if (curve(tMid)[0] < x) t0 = tMid; else t1 = tMid;
|
|
393
447
|
}
|
|
394
448
|
return curve((t0 + t1) / 2)[1];
|
|
395
449
|
};
|
|
@@ -400,15 +454,23 @@ const Ease =
|
|
|
400
454
|
* so all chaining methods (`setEase`, `then`, `loop`, `pingPong`, etc.)
|
|
401
455
|
* remain available.
|
|
402
456
|
*
|
|
403
|
-
* `start` and `end` may be numbers, Vector2
|
|
457
|
+
* `start` and `end` may be numbers, Vector2, Vector3 or Color instances, or
|
|
404
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]
|
|
405
464
|
* @param {Object} target - The object whose property is being animated
|
|
406
465
|
* @param {string} propertyPath - Dot-separated path, e.g. `'pos.x'` or `'color'`
|
|
407
|
-
* @param {
|
|
408
|
-
* @param {
|
|
466
|
+
* @param {T} start - Starting value
|
|
467
|
+
* @param {T} end - Ending value
|
|
409
468
|
* @param {number} [duration=1] - Duration in seconds
|
|
410
469
|
* @param {Object} [options] - Same options as the Tween constructor
|
|
411
|
-
* @
|
|
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>}
|
|
412
474
|
* @memberof TweenSystem
|
|
413
475
|
* @example
|
|
414
476
|
* // Numeric: slide an object's x with an ease-out sine curve
|
|
@@ -425,8 +487,11 @@ function tweenProperty(target, propertyPath, start, end, duration = 1, options =
|
|
|
425
487
|
|
|
426
488
|
const parts = propertyPath.split('.');
|
|
427
489
|
const lastKey = parts.pop();
|
|
490
|
+
let tween;
|
|
428
491
|
const callback = (value) =>
|
|
429
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();
|
|
430
495
|
let obj = target;
|
|
431
496
|
for (const k of parts)
|
|
432
497
|
{
|
|
@@ -435,92 +500,147 @@ function tweenProperty(target, propertyPath, start, end, duration = 1, options =
|
|
|
435
500
|
}
|
|
436
501
|
obj[lastKey] = value;
|
|
437
502
|
};
|
|
438
|
-
|
|
503
|
+
tween = new Tween(callback, start, end, duration, options);
|
|
504
|
+
tween.target = target;
|
|
505
|
+
return tween;
|
|
439
506
|
}
|
|
440
507
|
|
|
441
|
-
//
|
|
442
|
-
//
|
|
443
|
-
|
|
444
|
-
// the entire chain instead of just the current iteration.
|
|
445
|
-
function loopContinuation(tween)
|
|
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)
|
|
446
511
|
{
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
tween.life = tween.duration;
|
|
450
|
-
tween.thenCallback = () => loopContinuation(tween);
|
|
451
|
-
tweenActive.push(tween);
|
|
452
|
-
// snap to start for the new iteration (matches Tween constructor behavior)
|
|
453
|
-
tween.callback(tween.interp(tween.duration));
|
|
512
|
+
const duration = tween.duration;
|
|
513
|
+
tween.life = duration ? min(tween.life, 0) % duration + duration : 1e-9;
|
|
454
514
|
}
|
|
455
515
|
|
|
456
|
-
//
|
|
457
|
-
function
|
|
516
|
+
// How many iterations the update that finished one ran through: that one and every whole one after it
|
|
517
|
+
function tweenPassed(tween)
|
|
458
518
|
{
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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));
|
|
468
566
|
}
|
|
469
567
|
|
|
470
568
|
/** Engine plugin hook: advance every active tween by the appropriate delta.
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
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
|
|
476
576
|
* @memberof TweenSystem */
|
|
477
577
|
function tweenUpdate(gameDelta, realDelta)
|
|
478
578
|
{
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
realDelta = timeReal - lastTimeReal;
|
|
484
|
-
lastTime = time;
|
|
485
|
-
lastTimeReal = timeReal;
|
|
486
|
-
}
|
|
487
|
-
else if (realDelta === undefined)
|
|
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)
|
|
488
583
|
{
|
|
489
584
|
// Manual path with one arg: real and game advance together.
|
|
490
585
|
realDelta = gameDelta;
|
|
491
586
|
}
|
|
492
587
|
|
|
493
|
-
//
|
|
494
|
-
|
|
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--;)
|
|
495
597
|
{
|
|
496
|
-
const t =
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
if (
|
|
598
|
+
const t = list[i];
|
|
599
|
+
// stopped, or started again by a callback this update, or during an update a callback ran inside it, which
|
|
600
|
+
// counts on from this one
|
|
601
|
+
if (!t.active || t.activePass >= pass) continue;
|
|
602
|
+
let dt;
|
|
603
|
+
if (enginePath)
|
|
604
|
+
{
|
|
605
|
+
// a paused tween keeps count too, so it does not jump when resumed
|
|
606
|
+
dt = t.useRealTime ? timeReal - t.lastTimeReal : time - t.lastTime;
|
|
607
|
+
t.lastTime = time;
|
|
608
|
+
t.lastTimeReal = timeReal;
|
|
609
|
+
}
|
|
610
|
+
else
|
|
611
|
+
dt = t.useRealTime ? realDelta : gameDelta;
|
|
612
|
+
if (t.target?.destroyed) { t.stop(); continue; } // its object is gone, paused or not
|
|
613
|
+
if (t.paused || dt <= 0) continue;
|
|
500
614
|
|
|
501
615
|
t.life -= dt;
|
|
502
|
-
if (t.life >
|
|
616
|
+
if (t.life > 1e-9) // the engine's deltas add up a rounding error short of the duration
|
|
503
617
|
{
|
|
504
618
|
t.callback(t.interp(t.life));
|
|
505
619
|
}
|
|
506
620
|
else
|
|
507
621
|
{
|
|
508
|
-
// Completion: fire end value, remove from active,
|
|
622
|
+
// Completion: fire end value, remove from active, start the next iteration
|
|
623
|
+
// of a loop or pingPong, or when there is none it has completed, fire onComplete
|
|
509
624
|
t.callback(t.interp(0));
|
|
510
|
-
|
|
511
|
-
|
|
625
|
+
if (!t.active || t.activePass >= pass)
|
|
626
|
+
continue; // stopped or restarted by its own callback, the run it was on ends without completing
|
|
627
|
+
tweenDeactivate(t);
|
|
628
|
+
const next = t.thenCallback;
|
|
512
629
|
t.thenCallback = undefined;
|
|
513
|
-
if (
|
|
630
|
+
if (!(next && next()) && t.onComplete)
|
|
631
|
+
t.onComplete();
|
|
514
632
|
}
|
|
515
633
|
}
|
|
634
|
+
list.length = 0;
|
|
516
635
|
}
|
|
517
636
|
|
|
518
|
-
/** Stop every active tween
|
|
519
|
-
* on level transitions or when changing scenes.
|
|
637
|
+
/** Stop every active tween, ending loops too, without calling their then-callbacks.
|
|
638
|
+
* Useful for resets on level transitions or when changing scenes.
|
|
520
639
|
* @memberof TweenSystem */
|
|
521
640
|
function tweenStopAll()
|
|
522
641
|
{
|
|
523
|
-
for (const t of tweenActive)
|
|
642
|
+
for (const t of tweenActive)
|
|
643
|
+
t.thenCallback = undefined, t.active = false;
|
|
524
644
|
tweenActive.length = 0;
|
|
525
645
|
}
|
|
526
646
|
|