littlejsengine 1.18.4 → 1.18.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COPYRIGHT.txt +38 -0
- package/FAQ.md +633 -0
- package/README.md +19 -1
- package/dist/littlejs.d.ts +349 -54
- package/dist/littlejs.esm.js +1469 -448
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +1438 -436
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +1438 -436
- package/package.json +8 -1
- package/plugins/box2d.js +32 -7
- package/{src/engineMedals.js → plugins/medalSystem.js} +248 -196
- package/plugins/pathFinder.js +758 -0
- package/plugins/pluginExport.js +19 -0
- package/plugins/tween.js +1 -1
- package/plugins/tweenSystem.js +509 -0
- package/plugins/uiSystem.js +150 -11
- package/src/engine.js +2 -141
- package/src/engineAudio.js +8 -5
- package/src/engineBuild.mjs +4 -2
- package/src/engineDebug.js +1 -1
- package/src/engineDraw.js +21 -3
- package/src/engineExport.js +12 -12
- package/src/engineInput.js +1 -1
- package/src/engineLogo.js +146 -0
- package/src/engineMath.js +10 -3
- package/src/engineObject.js +11 -5
- package/src/engineParticles.js +6 -3
- package/src/engineRelease.js +1 -1
- package/src/engineSettings.js +0 -47
- package/src/engineUtilities.js +7 -2
- package/src/engineWebGL.js +34 -7
- package/src/jsconfig.json +3 -1
- package/.github/workflows/test.yml +0 -17
- package/AI.md +0 -173
- package/CLAUDE.md +0 -1
- package/examples/box2d/game.js +0 -192
- package/examples/box2d/gameObjects.js +0 -564
- package/examples/box2d/index.html +0 -9
- package/examples/box2d/scenes.js +0 -194
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/game.js +0 -175
- package/examples/breakout/gameObjects.js +0 -147
- package/examples/breakout/index.html +0 -8
- package/examples/breakout/tiles.png +0 -0
- package/examples/breakoutTutorial/README.md +0 -521
- package/examples/breakoutTutorial/game.js +0 -191
- package/examples/breakoutTutorial/images/1.png +0 -0
- package/examples/breakoutTutorial/images/10.png +0 -0
- package/examples/breakoutTutorial/images/11.png +0 -0
- package/examples/breakoutTutorial/images/2.png +0 -0
- package/examples/breakoutTutorial/images/3.png +0 -0
- package/examples/breakoutTutorial/images/4.png +0 -0
- package/examples/breakoutTutorial/images/5.png +0 -0
- package/examples/breakoutTutorial/images/6.png +0 -0
- package/examples/breakoutTutorial/images/7.png +0 -0
- package/examples/breakoutTutorial/images/8.png +0 -0
- package/examples/breakoutTutorial/images/9.png +0 -0
- package/examples/breakoutTutorial/index.html +0 -8
- package/examples/electron/build.mjs +0 -127
- package/examples/electron/electron.js +0 -35
- package/examples/electron/game.js +0 -54
- package/examples/electron/index.html +0 -13
- package/examples/electron/package.json +0 -5
- package/examples/electron/tiles.png +0 -0
- package/examples/empty/game.js +0 -51
- package/examples/empty/index.html +0 -5
- package/examples/empty/tiles.png +0 -0
- package/examples/favicon.png +0 -0
- package/examples/games.jpg +0 -0
- package/examples/htmlMenu/game.js +0 -82
- package/examples/htmlMenu/index.html +0 -45
- package/examples/htmlMenu/tiles.png +0 -0
- package/examples/index.html +0 -62
- package/examples/logo.png +0 -0
- package/examples/logo2.png +0 -0
- package/examples/module/build.mjs +0 -124
- package/examples/module/game.js +0 -132
- package/examples/module/index.html +0 -10
- package/examples/module/tiles.png +0 -0
- package/examples/particles/index.html +0 -426
- package/examples/particles/tiles.png +0 -0
- package/examples/platformer/data/gameLevelData.tmx +0 -143
- package/examples/platformer/data/gameLevelData.tsx +0 -4
- package/examples/platformer/game.js +0 -147
- package/examples/platformer/gameCharacter.js +0 -309
- package/examples/platformer/gameEffects.js +0 -278
- package/examples/platformer/gameLevel.js +0 -205
- package/examples/platformer/gameLevelData.json +0 -171
- package/examples/platformer/gameObjects.js +0 -378
- package/examples/platformer/gamePlayer.js +0 -35
- package/examples/platformer/index.html +0 -9
- package/examples/platformer/tiles.png +0 -0
- package/examples/platformer/tilesLevel.png +0 -0
- package/examples/puzzle/game.js +0 -331
- package/examples/puzzle/index.html +0 -8
- package/examples/puzzle/tiles.png +0 -0
- package/examples/screenshot.jpg +0 -0
- package/examples/shorts/animation.js +0 -18
- package/examples/shorts/base.html +0 -54
- package/examples/shorts/blending.js +0 -14
- package/examples/shorts/box2d.js +0 -51
- package/examples/shorts/box2dCar.js +0 -59
- package/examples/shorts/box2dPool.js +0 -107
- package/examples/shorts/box2dTileLayer.js +0 -48
- package/examples/shorts/cameraDrag.js +0 -22
- package/examples/shorts/clock.js +0 -21
- package/examples/shorts/colors.js +0 -25
- package/examples/shorts/debugDraw.js +0 -37
- package/examples/shorts/empty.js +0 -30
- package/examples/shorts/flappyGame.js +0 -55
- package/examples/shorts/fontImage.js +0 -17
- package/examples/shorts/fps.js +0 -90
- package/examples/shorts/helloWorld.js +0 -11
- package/examples/shorts/hillGlideGame.js +0 -63
- package/examples/shorts/input.js +0 -64
- package/examples/shorts/landerGame.js +0 -57
- package/examples/shorts/maze.js +0 -48
- package/examples/shorts/medals.js +0 -51
- package/examples/shorts/music.js +0 -78
- package/examples/shorts/musicPlayer.js +0 -137
- package/examples/shorts/nineSlice.js +0 -40
- package/examples/shorts/parallax.js +0 -72
- package/examples/shorts/particles.js +0 -28
- package/examples/shorts/piano.js +0 -44
- package/examples/shorts/platformer.js +0 -45
- package/examples/shorts/pongGame.js +0 -41
- package/examples/shorts/postProcess.js +0 -62
- package/examples/shorts/sequencer.js +0 -124
- package/examples/shorts/shader.js +0 -29
- package/examples/shorts/shapes.js +0 -21
- package/examples/shorts/slidingPuzzle.js +0 -52
- package/examples/shorts/song.mp3 +0 -0
- package/examples/shorts/sound.js +0 -36
- package/examples/shorts/spaceGame.js +0 -58
- package/examples/shorts/spriteAtlas.js +0 -31
- package/examples/shorts/starfield.js +0 -15
- package/examples/shorts/texture.js +0 -16
- package/examples/shorts/textureWrapped.js +0 -12
- package/examples/shorts/tileLayer.js +0 -48
- package/examples/shorts/tileRaycast.js +0 -39
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/tiltedView.js +0 -63
- package/examples/shorts/timers.js +0 -53
- package/examples/shorts/topDown.js +0 -38
- package/examples/shorts/tween.js +0 -24
- package/examples/shorts/uiSystem.js +0 -61
- package/examples/shorts/video.webm +0 -0
- package/examples/shorts/videoPlayer.js +0 -34
- package/examples/shorts.js +0 -743
- package/examples/starter/build.bat +0 -7
- package/examples/starter/build.mjs +0 -126
- package/examples/starter/game.js +0 -137
- package/examples/starter/index.html +0 -35
- package/examples/starter/tiles.png +0 -0
- package/examples/stress/index.html +0 -173
- package/examples/style.css +0 -150
- package/examples/tweenSystem/game.js +0 -171
- package/examples/tweenSystem/index.html +0 -10
- package/examples/tweenSystem/tiles.png +0 -0
- package/examples/typescript/build.mjs +0 -60
- package/examples/typescript/game.js +0 -100
- package/examples/typescript/game.ts +0 -132
- package/examples/typescript/index.html +0 -10
- package/examples/typescript/tiles.png +0 -0
- package/examples/typescript/tsconfig.json +0 -17
- package/examples/uiSystem/game.js +0 -139
- package/examples/uiSystem/index.html +0 -10
- package/examples/uiSystem/tiles.png +0 -0
- package/jsconfig.json +0 -12
- package/plugins/desktop.ini +0 -2
- package/reference.md +0 -448
- package/test/math.test.mjs +0 -774
- package/test/setup.mjs +0 -22
- package/test/smoke.test.mjs +0 -274
- package/test/tween.test.mjs +0 -576
- package/test/util.test.mjs +0 -80
package/plugins/pluginExport.js
CHANGED
|
@@ -4,6 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
export
|
|
6
6
|
{
|
|
7
|
+
// Medals
|
|
8
|
+
medals,
|
|
9
|
+
medalsPreventUnlock,
|
|
10
|
+
medalsInit,
|
|
11
|
+
medalsForEach,
|
|
12
|
+
Medal,
|
|
13
|
+
medalDisplayTime,
|
|
14
|
+
medalDisplaySlideTime,
|
|
15
|
+
medalDisplaySize,
|
|
16
|
+
setMedalDisplayTime,
|
|
17
|
+
setMedalDisplaySlideTime,
|
|
18
|
+
setMedalDisplaySize,
|
|
19
|
+
setMedalsPreventUnlock,
|
|
20
|
+
|
|
7
21
|
// Newgrounds
|
|
8
22
|
newgrounds,
|
|
9
23
|
NewgroundsPlugin,
|
|
@@ -29,6 +43,7 @@ export
|
|
|
29
43
|
UICheckbox,
|
|
30
44
|
UISlider,
|
|
31
45
|
UIVideo,
|
|
46
|
+
UILayout,
|
|
32
47
|
|
|
33
48
|
// Box2D Physics
|
|
34
49
|
box2d,
|
|
@@ -67,4 +82,8 @@ export
|
|
|
67
82
|
tweenStopAll,
|
|
68
83
|
tweenUpdate,
|
|
69
84
|
Ease,
|
|
85
|
+
|
|
86
|
+
// Path Finding
|
|
87
|
+
PathFinder,
|
|
88
|
+
PathFinderNode,
|
|
70
89
|
}
|
package/plugins/tween.js
CHANGED
|
@@ -0,0 +1,509 @@
|
|
|
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
|
+
// Time tracking for delta computation between engine plugin calls.
|
|
18
|
+
let lastTime = 0;
|
|
19
|
+
let lastTimeReal = 0;
|
|
20
|
+
|
|
21
|
+
// True if the value is an instance of a class that exposes a numeric-percent
|
|
22
|
+
// `lerp(other, percent)` method (Vector2, Color, or any future class).
|
|
23
|
+
function isLerpable(v) { return v && typeof v.lerp === 'function'; }
|
|
24
|
+
|
|
25
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
26
|
+
|
|
27
|
+
/** A numeric tween: drives a callback with a value interpolated between
|
|
28
|
+
* `start` and `end` over `duration` seconds. Pauses with the game by default.
|
|
29
|
+
* @memberof TweenSystem
|
|
30
|
+
* @example
|
|
31
|
+
* // Animate a fade-out over 2 seconds with an ease-out sine curve.
|
|
32
|
+
* new Tween((v) => obj.alpha = v, 1, 0, 2, { ease: Ease.OUT(Ease.SINE) });
|
|
33
|
+
*/
|
|
34
|
+
class Tween
|
|
35
|
+
{
|
|
36
|
+
/** Create a new tween. The callback fires immediately with `start` so the
|
|
37
|
+
* target snaps to the start value on the same frame the tween is created.
|
|
38
|
+
*
|
|
39
|
+
* `start` and `end` may be numbers, Vector2 instances, Color instances, or
|
|
40
|
+
* any object exposing a `lerp(other, percent) => sameType` method. The
|
|
41
|
+
* callback receives the interpolated value (a number, or a fresh instance
|
|
42
|
+
* for lerp-able types). Both endpoints must be the same type.
|
|
43
|
+
* @param {function(number|Vector2|Color):void} callback - Called with the interpolated value each frame
|
|
44
|
+
* @param {number|Vector2|Color} [start=0] - Starting value
|
|
45
|
+
* @param {number|Vector2|Color} [end=1] - Ending value
|
|
46
|
+
* @param {number} [duration=1] - Duration in seconds
|
|
47
|
+
* @param {Object} [options]
|
|
48
|
+
* @param {function(number):number} [options.ease] - Easing function (defaults to LINEAR)
|
|
49
|
+
* @param {boolean} [options.useRealTime=false] - Advance even when the game is paused (matches Timer's useRealTime)
|
|
50
|
+
* @param {boolean} [options.paused=false] - Start in paused state */
|
|
51
|
+
constructor(callback, start = 0, end = 1, duration = 1, options = {})
|
|
52
|
+
{
|
|
53
|
+
ASSERT(typeof callback === 'function', 'Tween callback must be a function');
|
|
54
|
+
if (isLerpable(start))
|
|
55
|
+
{
|
|
56
|
+
ASSERT(start.constructor === end.constructor,
|
|
57
|
+
'Tween start and end must be the same type');
|
|
58
|
+
}
|
|
59
|
+
else
|
|
60
|
+
{
|
|
61
|
+
ASSERT(isNumber(start), 'Tween start must be a number or have a .lerp method');
|
|
62
|
+
ASSERT(isNumber(end), 'Tween end must be a number when start is a number');
|
|
63
|
+
}
|
|
64
|
+
ASSERT(isNumber(duration) && duration > 0, 'Tween duration must be > 0');
|
|
65
|
+
|
|
66
|
+
this.callback = callback;
|
|
67
|
+
this.start = start;
|
|
68
|
+
this.end = end;
|
|
69
|
+
this.duration = duration;
|
|
70
|
+
this.life = duration;
|
|
71
|
+
this.ease = options.ease || Ease.LINEAR;
|
|
72
|
+
this.useRealTime = !!options.useRealTime;
|
|
73
|
+
this.paused = !!options.paused;
|
|
74
|
+
|
|
75
|
+
/** @private completion callback set by then(), loop(), pingPong(). */
|
|
76
|
+
this.thenCallback = undefined;
|
|
77
|
+
/** @private remaining iterations including the current run (loop/pingPong only). */
|
|
78
|
+
this.loopRemaining = 0;
|
|
79
|
+
|
|
80
|
+
tweenActive.push(this);
|
|
81
|
+
// Snap target to start immediately.
|
|
82
|
+
callback(this.interp(duration));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Set the easing curve and return this for chaining.
|
|
86
|
+
* @param {function(number):number} easeFn
|
|
87
|
+
* @returns {Tween}
|
|
88
|
+
* @memberof TweenSystem */
|
|
89
|
+
setEase(easeFn)
|
|
90
|
+
{
|
|
91
|
+
this.ease = easeFn;
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Set a single completion callback. Calling `then` again replaces the
|
|
96
|
+
* previous callback. Returns this for chaining.
|
|
97
|
+
*
|
|
98
|
+
* Calling `then` after `loop` or `pingPong` overrides the loop chain
|
|
99
|
+
* (last call wins).
|
|
100
|
+
* @param {function():void} callback
|
|
101
|
+
* @returns {Tween}
|
|
102
|
+
* @memberof TweenSystem */
|
|
103
|
+
then(callback)
|
|
104
|
+
{
|
|
105
|
+
this.thenCallback = callback;
|
|
106
|
+
this.loopRemaining = 0;
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Repeat this tween `n` total times. After each iteration finishes, a
|
|
111
|
+
* fresh tween with the same parameters takes over via the `then` slot.
|
|
112
|
+
* `loop()` with no argument loops forever.
|
|
113
|
+
*
|
|
114
|
+
* Mutually exclusive with `pingPong`; calling either replaces the other,
|
|
115
|
+
* and calling `then` after either clears the loop (last call wins).
|
|
116
|
+
* @param {number} [count=Infinity]
|
|
117
|
+
* @returns {Tween}
|
|
118
|
+
* @memberof TweenSystem */
|
|
119
|
+
loop(count = Infinity)
|
|
120
|
+
{
|
|
121
|
+
this.loopRemaining = count;
|
|
122
|
+
this.thenCallback = () => loopContinuation(this);
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Like `loop`, but swap `start` and `end` between iterations so the value
|
|
127
|
+
* bounces back and forth. `pingPong()` with no argument bounces forever.
|
|
128
|
+
*
|
|
129
|
+
* Mutually exclusive with `loop`; calling either replaces the other, and
|
|
130
|
+
* calling `then` after either clears the loop (last call wins).
|
|
131
|
+
* @param {number} [count=Infinity]
|
|
132
|
+
* @returns {Tween}
|
|
133
|
+
* @memberof TweenSystem */
|
|
134
|
+
pingPong(count = Infinity)
|
|
135
|
+
{
|
|
136
|
+
this.loopRemaining = count;
|
|
137
|
+
this.thenCallback = () => pingPongContinuation(this);
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Pause this tween. While paused, tweenUpdate skips it.
|
|
142
|
+
* @memberof TweenSystem */
|
|
143
|
+
pause() { this.paused = true; }
|
|
144
|
+
|
|
145
|
+
/** Resume a paused tween.
|
|
146
|
+
* @memberof TweenSystem */
|
|
147
|
+
resume() { this.paused = false; }
|
|
148
|
+
|
|
149
|
+
/** Reset this tween to the start: life back to duration, pause cleared,
|
|
150
|
+
* re-added to the active list if previously stopped, and the callback
|
|
151
|
+
* re-fired with the start value.
|
|
152
|
+
* @memberof TweenSystem */
|
|
153
|
+
restart()
|
|
154
|
+
{
|
|
155
|
+
this.life = this.duration;
|
|
156
|
+
this.paused = false;
|
|
157
|
+
if (tweenActive.indexOf(this) < 0) tweenActive.push(this);
|
|
158
|
+
this.callback(this.interp(this.duration));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** True if this tween is in the active list and not paused.
|
|
162
|
+
* @returns {boolean}
|
|
163
|
+
* @memberof TweenSystem */
|
|
164
|
+
isActive()
|
|
165
|
+
{
|
|
166
|
+
return !this.paused && tweenActive.indexOf(this) >= 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Get how far this tween has progressed, from 0 (just started) to 1
|
|
170
|
+
* (completed). Clamped — overshoot past completion still reads 1.
|
|
171
|
+
* @returns {number}
|
|
172
|
+
* @memberof TweenSystem */
|
|
173
|
+
getPercent()
|
|
174
|
+
{
|
|
175
|
+
return percent(this.duration - this.life, 0, this.duration);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Get the current interpolated value (the value most recently passed to
|
|
179
|
+
* the callback). Returns a number, Vector2, or Color depending on the
|
|
180
|
+
* tween's start/end types.
|
|
181
|
+
* @returns {number|Vector2|Color}
|
|
182
|
+
* @memberof TweenSystem */
|
|
183
|
+
getValue()
|
|
184
|
+
{
|
|
185
|
+
return this.interp(this.life);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Compute the interpolated value at the given remaining `life`.
|
|
189
|
+
* At life === duration the result is `start`; at life === 0 it is `end`.
|
|
190
|
+
* @param {number} life
|
|
191
|
+
* @returns {number}
|
|
192
|
+
* @memberof TweenSystem */
|
|
193
|
+
interp(life)
|
|
194
|
+
{
|
|
195
|
+
const x = this.ease((this.duration - life) / this.duration);
|
|
196
|
+
if (isLerpable(this.start))
|
|
197
|
+
return this.start.lerp(this.end, x);
|
|
198
|
+
return this.start + (this.end - this.start) * x;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Remove this tween from the active list and prevent any pending then-callback.
|
|
202
|
+
* @memberof TweenSystem */
|
|
203
|
+
stop()
|
|
204
|
+
{
|
|
205
|
+
const i = tweenActive.indexOf(this);
|
|
206
|
+
if (i >= 0) tweenActive.splice(i, 1);
|
|
207
|
+
this.thenCallback = undefined;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Library of named easing curves and direction modifiers.
|
|
212
|
+
* All curves accept `x` in [0,1] and return [0,1] (with possible overshoot
|
|
213
|
+
* for ELASTIC/BACK/SPRING/BOUNCE). Curves are values you pass to `setEase`
|
|
214
|
+
* or compose via the IN/OUT/IN_OUT/PIECEWISE/BEZIER modifiers.
|
|
215
|
+
* @memberof TweenSystem
|
|
216
|
+
* @example
|
|
217
|
+
* // Use a basic curve
|
|
218
|
+
* new Tween(callback, 0, 10, 1).setEase(Ease.SINE);
|
|
219
|
+
* // Use a modifier on a curve
|
|
220
|
+
* new Tween(callback, 0, 10, 1).setEase(Ease.OUT(Ease.BACK));
|
|
221
|
+
*/
|
|
222
|
+
const Ease =
|
|
223
|
+
{
|
|
224
|
+
/** Linear (identity) curve.
|
|
225
|
+
* @param {number} x
|
|
226
|
+
* @returns {number}
|
|
227
|
+
* @memberof TweenSystem */
|
|
228
|
+
LINEAR: (x) => x,
|
|
229
|
+
|
|
230
|
+
/** Power curve factory: `Ease.POWER(n)` returns `x => x**n`.
|
|
231
|
+
* Use n=2 for quadratic, n=3 for cubic, etc.
|
|
232
|
+
* @param {number} n
|
|
233
|
+
* @returns {function(number):number}
|
|
234
|
+
* @memberof TweenSystem */
|
|
235
|
+
POWER: (n) => (x) => x ** n,
|
|
236
|
+
|
|
237
|
+
/** Sine ease-in curve: starts slow, ends fast.
|
|
238
|
+
* @param {number} x
|
|
239
|
+
* @returns {number}
|
|
240
|
+
* @memberof TweenSystem */
|
|
241
|
+
SINE: (x) => 1 - cos(x * (PI / 2)),
|
|
242
|
+
|
|
243
|
+
/** Circular ease-in curve.
|
|
244
|
+
* @param {number} x
|
|
245
|
+
* @returns {number}
|
|
246
|
+
* @memberof TweenSystem */
|
|
247
|
+
CIRC: (x) => 1 - (1 - x * x)**.5,
|
|
248
|
+
|
|
249
|
+
/** Exponential ease-in curve (`2^(10x-10)`).
|
|
250
|
+
* @param {number} x
|
|
251
|
+
* @returns {number}
|
|
252
|
+
* @memberof TweenSystem */
|
|
253
|
+
EXPO: (x) => 2 ** (10 * x - 10),
|
|
254
|
+
|
|
255
|
+
/** Back ease-in: overshoots backward at the start before snapping forward.
|
|
256
|
+
* @param {number} x
|
|
257
|
+
* @returns {number}
|
|
258
|
+
* @memberof TweenSystem */
|
|
259
|
+
BACK: (x) => x * x * (2.70158 * x - 1.70158),
|
|
260
|
+
|
|
261
|
+
/** Elastic ease-in: oscillates with decreasing amplitude.
|
|
262
|
+
* @param {number} x
|
|
263
|
+
* @returns {number}
|
|
264
|
+
* @memberof TweenSystem */
|
|
265
|
+
ELASTIC: (x) =>
|
|
266
|
+
-(2 ** (10 * x - 10)) * sin(((37 - 40 * x) * PI) / 6),
|
|
267
|
+
|
|
268
|
+
/** Spring-like ease-out: oscillates outward after passing the target.
|
|
269
|
+
* @param {number} x
|
|
270
|
+
* @returns {number}
|
|
271
|
+
* @memberof TweenSystem */
|
|
272
|
+
SPRING: (x) =>
|
|
273
|
+
1 -
|
|
274
|
+
(sin(PI * (1 - x) * (0.2 + 2.5 * (1 - x) ** 3)) *
|
|
275
|
+
x ** 2.2 +
|
|
276
|
+
(1 - x)) *
|
|
277
|
+
(1.0 + 1.2 * x),
|
|
278
|
+
|
|
279
|
+
/** Bouncing ease-in: slow ramp with bouncing impacts near the end.
|
|
280
|
+
* Symmetric with the other base curves, which are all ease-in. To get the
|
|
281
|
+
* classic "object falls and hits the ground" shape (bounces near x=1),
|
|
282
|
+
* wrap with `Ease.OUT`: `Ease.OUT(Ease.BOUNCE)`.
|
|
283
|
+
* @param {number} x
|
|
284
|
+
* @returns {number}
|
|
285
|
+
* @memberof TweenSystem
|
|
286
|
+
* @example
|
|
287
|
+
* Ease.BOUNCE // ease-in bounce (slow, then bouncy at end)
|
|
288
|
+
* Ease.OUT(Ease.BOUNCE) // ease-out bounce (object hits ground)
|
|
289
|
+
* Ease.IN_OUT(Ease.BOUNCE) // bounces at both ends
|
|
290
|
+
*/
|
|
291
|
+
BOUNCE: (x) =>
|
|
292
|
+
{
|
|
293
|
+
// Inverted form of the standard easeOutBounce: 1 - bounceOut(1 - x).
|
|
294
|
+
let t = 1 - x, f;
|
|
295
|
+
if (t < 4 / 11) f = 7.5625 * t * t;
|
|
296
|
+
else if (t < 8 / 11) f = 7.5625 * (t -= 6 / 11) * t + 0.75;
|
|
297
|
+
else if (t < 10 / 11) f = 7.5625 * (t -= 9 / 11) * t + 0.9375;
|
|
298
|
+
else f = 7.5625 * (t -= 10.5 / 11) * t + 0.984375;
|
|
299
|
+
return 1 - f;
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
/** Ease-in direction modifier: returns the curve unchanged. Symmetric
|
|
303
|
+
* with `OUT` and `IN_OUT`. Base curves are already ease-in by
|
|
304
|
+
* convention, so wrapping a curve in `IN` is a no-op — useful when
|
|
305
|
+
* picking the direction programmatically.
|
|
306
|
+
* @param {function(number):number} f - Curve to use as ease-in (returned unchanged)
|
|
307
|
+
* @returns {function(number):number}
|
|
308
|
+
* @memberof TweenSystem
|
|
309
|
+
* @example
|
|
310
|
+
* // Pick direction at runtime
|
|
311
|
+
* const dir = bouncyMode ? Ease.OUT : Ease.IN;
|
|
312
|
+
* new Tween(cb, 0, 10, 1).setEase(dir(Ease.BACK));
|
|
313
|
+
*/
|
|
314
|
+
IN: (f) => f,
|
|
315
|
+
|
|
316
|
+
/** Reverse a curve so it eases out instead of in: `x => 1 - f(1 - x)`.
|
|
317
|
+
* @param {function(number):number} f
|
|
318
|
+
* @returns {function(number):number}
|
|
319
|
+
* @memberof TweenSystem
|
|
320
|
+
* @example
|
|
321
|
+
* Ease.OUT(Ease.POWER(2)) // ease-out quadratic
|
|
322
|
+
*/
|
|
323
|
+
OUT: (f) => (x) => 1 - f(1 - x),
|
|
324
|
+
|
|
325
|
+
/** Combine the first half of `f` with `Ease.OUT(f)` for a symmetric curve.
|
|
326
|
+
* Bug-fix vs the original library: the original referenced an undefined
|
|
327
|
+
* global `Piecewise`; this implementation routes through `Ease.PIECEWISE`.
|
|
328
|
+
* @param {function(number):number} f
|
|
329
|
+
* @returns {function(number):number}
|
|
330
|
+
* @memberof TweenSystem */
|
|
331
|
+
IN_OUT: (f) => Ease.PIECEWISE(f, Ease.OUT(f)),
|
|
332
|
+
|
|
333
|
+
/** Split [0,1] into N equal sections and run a different curve in each.
|
|
334
|
+
* Each curve is mapped to its section: section i runs over [i/n, (i+1)/n]
|
|
335
|
+
* and its output is mapped to [i/n, (i+1)/n] of the overall range.
|
|
336
|
+
* @param {...function(number):number} fns
|
|
337
|
+
* @returns {function(number):number}
|
|
338
|
+
* @memberof TweenSystem */
|
|
339
|
+
PIECEWISE: (...fns) =>
|
|
340
|
+
{
|
|
341
|
+
const n = fns.length;
|
|
342
|
+
return (x) =>
|
|
343
|
+
{
|
|
344
|
+
const i = (x * n - 1e-9) >> 0;
|
|
345
|
+
return (fns[i]((x - i / n) * n) + i) / n;
|
|
346
|
+
};
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
/** Cubic Bezier curve solver in the style of CSS `cubic-bezier`.
|
|
350
|
+
* Control points (0,0), (x1,y1), (x2,y2), (1,1).
|
|
351
|
+
* @param {number} x1
|
|
352
|
+
* @param {number} y1
|
|
353
|
+
* @param {number} x2
|
|
354
|
+
* @param {number} y2
|
|
355
|
+
* @returns {function(number):number}
|
|
356
|
+
* @memberof TweenSystem
|
|
357
|
+
* @example
|
|
358
|
+
* Ease.BEZIER(0.25, 0.1, 0.25, 1) // CSS "ease"
|
|
359
|
+
*/
|
|
360
|
+
BEZIER: (x1, y1, x2, y2) =>
|
|
361
|
+
{
|
|
362
|
+
// Parametric cubic Bezier with implicit (0,0) and (1,1) endpoints.
|
|
363
|
+
const curve = (t) =>
|
|
364
|
+
{
|
|
365
|
+
const u = 1 - t;
|
|
366
|
+
const c1 = 3 * u * u * t;
|
|
367
|
+
const c2 = 3 * u * t * t;
|
|
368
|
+
const t3 = t ** 3;
|
|
369
|
+
return [c1 * x1 + c2 * x2 + t3, c1 * y1 + c2 * y2 + t3];
|
|
370
|
+
};
|
|
371
|
+
return (x) =>
|
|
372
|
+
{
|
|
373
|
+
// Binary search for t such that curve(t).x ≈ x, then return curve(t).y.
|
|
374
|
+
let t0 = 0, t1 = 1;
|
|
375
|
+
for (let i = 0; i < 128; i++)
|
|
376
|
+
{
|
|
377
|
+
const tMid = (t0 + t1) / 2;
|
|
378
|
+
const [bx, by] = curve(tMid);
|
|
379
|
+
if (abs(bx - x) < 1e-5) return by;
|
|
380
|
+
if (bx < x) t0 = tMid; else t1 = tMid;
|
|
381
|
+
}
|
|
382
|
+
return curve((t0 + t1) / 2)[1];
|
|
383
|
+
};
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
/** Tween a property on an object by dot-path. Returns the underlying Tween
|
|
388
|
+
* so all chaining methods (`setEase`, `then`, `loop`, `pingPong`, etc.)
|
|
389
|
+
* remain available.
|
|
390
|
+
*
|
|
391
|
+
* `start` and `end` may be numbers, Vector2 instances, Color instances, or
|
|
392
|
+
* any object with a `lerp(other, percent) => sameType` method.
|
|
393
|
+
* @param {Object} target - The object whose property is being animated
|
|
394
|
+
* @param {string} propertyPath - Dot-separated path, e.g. `'pos.x'` or `'color'`
|
|
395
|
+
* @param {number|Vector2|Color} start - Starting value
|
|
396
|
+
* @param {number|Vector2|Color} end - Ending value
|
|
397
|
+
* @param {number} [duration=1] - Duration in seconds
|
|
398
|
+
* @param {Object} [options] - Same options as the Tween constructor
|
|
399
|
+
* @returns {Tween}
|
|
400
|
+
* @memberof TweenSystem
|
|
401
|
+
* @example
|
|
402
|
+
* // Numeric: slide an object's x with an ease-out sine curve
|
|
403
|
+
* tweenProperty(player, 'pos.x', 0, 10, 2).setEase(Ease.OUT(Ease.SINE));
|
|
404
|
+
* // Vector2: animate a position diagonally
|
|
405
|
+
* tweenProperty(player, 'pos', vec2(-5, 0), vec2(5, 3), 2);
|
|
406
|
+
* // Color: pulse between two colors
|
|
407
|
+
* tweenProperty(sprite, 'color', RED, BLUE, 1).pingPong();
|
|
408
|
+
*/
|
|
409
|
+
function tweenProperty(target, propertyPath, start, end, duration = 1, options = {})
|
|
410
|
+
{
|
|
411
|
+
ASSERT(target != null && typeof target === 'object', 'tweenProperty target must be an object');
|
|
412
|
+
ASSERT(isString(propertyPath) && propertyPath.length > 0, 'tweenProperty propertyPath must be a non-empty string');
|
|
413
|
+
|
|
414
|
+
const parts = propertyPath.split('.');
|
|
415
|
+
const lastKey = parts.pop();
|
|
416
|
+
const callback = (value) =>
|
|
417
|
+
{
|
|
418
|
+
let obj = target;
|
|
419
|
+
for (const k of parts) obj = obj[k];
|
|
420
|
+
obj[lastKey] = value;
|
|
421
|
+
};
|
|
422
|
+
return new Tween(callback, start, end, duration, options);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Continuation that schedules the next loop iteration when one finishes.
|
|
426
|
+
// Called from the completed tween's `then` slot. Decrements the counter and
|
|
427
|
+
// only spawns a new tween if more iterations remain.
|
|
428
|
+
function loopContinuation(prev)
|
|
429
|
+
{
|
|
430
|
+
if (prev.loopRemaining !== Infinity && prev.loopRemaining <= 1) return;
|
|
431
|
+
const next = new Tween(prev.callback, prev.start, prev.end, prev.duration,
|
|
432
|
+
{ ease: prev.ease, useRealTime: prev.useRealTime });
|
|
433
|
+
next.loopRemaining = prev.loopRemaining === Infinity
|
|
434
|
+
? Infinity
|
|
435
|
+
: prev.loopRemaining - 1;
|
|
436
|
+
next.thenCallback = () => loopContinuation(next);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Continuation for pingPong: spawns a new tween with start and end swapped.
|
|
440
|
+
function pingPongContinuation(prev)
|
|
441
|
+
{
|
|
442
|
+
if (prev.loopRemaining !== Infinity && prev.loopRemaining <= 1) return;
|
|
443
|
+
const next = new Tween(prev.callback, prev.end, prev.start, prev.duration,
|
|
444
|
+
{ ease: prev.ease, useRealTime: prev.useRealTime });
|
|
445
|
+
next.loopRemaining = prev.loopRemaining === Infinity
|
|
446
|
+
? Infinity
|
|
447
|
+
: prev.loopRemaining - 1;
|
|
448
|
+
next.thenCallback = () => pingPongContinuation(next);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** Engine plugin hook: advance every active tween by the appropriate delta.
|
|
452
|
+
* Called once per render frame by the engine (no arguments). May also be
|
|
453
|
+
* called explicitly with `(gameDelta, realDelta)` to drive tweens manually
|
|
454
|
+
* — useful for headless tests or custom replay/scrubbing systems.
|
|
455
|
+
* @param {number} [gameDelta] - Game-time delta in seconds; default: time - lastTime
|
|
456
|
+
* @param {number} [realDelta] - Real-time delta in seconds; default: timeReal - lastTimeReal
|
|
457
|
+
* @memberof TweenSystem */
|
|
458
|
+
function tweenUpdate(gameDelta, realDelta)
|
|
459
|
+
{
|
|
460
|
+
if (gameDelta === undefined)
|
|
461
|
+
{
|
|
462
|
+
// Engine path: compute deltas from engine time globals.
|
|
463
|
+
gameDelta = time - lastTime;
|
|
464
|
+
realDelta = timeReal - lastTimeReal;
|
|
465
|
+
lastTime = time;
|
|
466
|
+
lastTimeReal = timeReal;
|
|
467
|
+
}
|
|
468
|
+
else if (realDelta === undefined)
|
|
469
|
+
{
|
|
470
|
+
// Manual path with one arg: real and game advance together.
|
|
471
|
+
realDelta = gameDelta;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Iterate in reverse so removals don't disturb iteration.
|
|
475
|
+
for (let i = tweenActive.length; i--;)
|
|
476
|
+
{
|
|
477
|
+
const t = tweenActive[i];
|
|
478
|
+
if (t.paused) continue;
|
|
479
|
+
const dt = t.useRealTime ? realDelta : gameDelta;
|
|
480
|
+
if (dt <= 0) continue;
|
|
481
|
+
|
|
482
|
+
t.life -= dt;
|
|
483
|
+
if (t.life > 0)
|
|
484
|
+
{
|
|
485
|
+
t.callback(t.interp(t.life));
|
|
486
|
+
}
|
|
487
|
+
else
|
|
488
|
+
{
|
|
489
|
+
// Completion: fire end value, remove from active, fire then-callback.
|
|
490
|
+
t.callback(t.interp(0));
|
|
491
|
+
tweenActive.splice(i, 1);
|
|
492
|
+
const cb = t.thenCallback;
|
|
493
|
+
t.thenCallback = undefined;
|
|
494
|
+
if (cb) cb();
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/** Stop every active tween and clear their then-callbacks. Useful for resets
|
|
500
|
+
* on level transitions or when changing scenes.
|
|
501
|
+
* @memberof TweenSystem */
|
|
502
|
+
function tweenStopAll()
|
|
503
|
+
{
|
|
504
|
+
for (const t of tweenActive) t.thenCallback = undefined;
|
|
505
|
+
tweenActive.length = 0;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Register with the engine so tweens auto-advance.
|
|
509
|
+
engineAddPlugin(tweenUpdate);
|